我正在做反向引擎的东西,并通过DLL修补游戏的内存。通常我坚持使用相同的旧方法在一个或多个函数中修补所有内容。但是感觉可以通过使用结构数组来更好地实现它,该结构数组定义了需要进行的内存写入并一次性循环遍历它们。更容易管理,IMO。
但是,我想让它保持不变。所以数据都是一次性的(在.rdata中)而不是为每个补丁动态分配内存,这是一个带有'bytesize'数据的简单任务,例如:struct struc_patch
{
BYTE val[8]; // max size of each patch (usually I only use 5 bytes anyway for call and jmp writes)
// I can of course increase this if really needed
void *dest;
char size;
} patches[] =
{
// simply write "01 02 03 04" to 0x400000
{{0x1, 0x2, 0x3, 0x4}, (void*)0x400000, 4},
};
//[...]
for each(struc_patch p in patches)
{
memcpy(p.dest, p.val, p.size);
}
但是当我想要获得更好的类型时,我发现无法指定像“0x90909090”这样的整数作为字节数组“90 90 90 90”。所以这不起作用:
struct struc_patch
{
BYTE val[8]; // max size of each patch (usually I only use 5 bytes anyway for call and jmp writes)
// I can of course increase this if really needed
void *dest;
char size;
} patches[] =
{
// how to write "jmp MyHook"? Here, the jmp offset will be truncated instead of overlapping in the array. Annoying.
{{0xE9, (DWORD)&MyHook - 0x400005}, (void*)0x400000, 5},
};
当然主要的问题是& MyHook必须由编译器解决。是否有其他方法可以获得所需的结果并将其保持为常量?
说实话,我对STL没什么经验。因此,如果有使用该解决方案的解决方案,我可能需要对其进行详细解释才能正确理解代码。我是一个很大的C / C ++ / WinAPI迷瘾大声笑,但这是一个类似性质的游戏,所以它适合。
答案 0 :(得分:2)
我认为STL中的任何内容都不会帮助您,而不是在编译时。 使用宏可能有一种奇特的方式来处理模板。 (用逗号分隔字节)
但我建议做一些像这样简单的事情:
struct jump_insn
{
unsigned char opcode;
unsigned long addr;
} jump_insns[] = {
{0xe9, (unsigned long)&MyHook - 0x400005}
};
struct mem
{
unsigned char val[8];
} mems[] = {
{1,2,3,4}
};
struct struc_patch
{
unsigned char *val; // max size of each patch (usually I only use 5 bytes anyway for call and jmp writes)
// I can of course increase this if really needed
void *dest;
char size;
} patches[] =
{
// simply write "01 02 03 04" to 0x400000
{(unsigned char*)(&mems[0]), (void*)0x400000, 4},
// how to write "jmp MyHook"? Here, the jmp offset will be truncated instead of overlapping in the array. Annoying.
{(unsigned char*)(&jump_insns[0]), (void*)0x400000, 5},
};
你不能内联所有内容,你需要新类型的不同类型的补丁,但它们可以任意长(不只是8个字节),一切都将在.rodata。
答案 1 :(得分:0)
处理这种情况的更好方法是动态计算地址差异。例如(source):
#define INST_CALL 0xE8
void InterceptLocalCode(BYTE bInst, DWORD pAddr, DWORD pFunc, DWORD dwLen)
{
BYTE *bCode = new BYTE[dwLen];
::memset(bCode, 0x90, dwLen);
DWORD dwFunc = pFunc - (pAddr + 5);
bCode[0] = bInst;
*(DWORD *)&bCode[1] = dwFunc;
WriteBytes((void*)pAddr, bCode, dwLen);
delete[] bCode;
}
void PatchCall(DWORD dwAddr, DWORD dwFunc, DWORD dwLen)
{
InterceptLocalCode(INST_CALL, dwAddr, dwFunc, dwLen);
}
dwAddr是放入调用指令的地址,dwFunc是要调用的函数,dwLen是要替换的指令的长度(主要用于计算要放入多少NOP)。
答案 2 :(得分:0)
总结一下,我的解决方案(感谢Nicolas的建议):
#pragma pack(push)
#pragma pack(1)
#define POFF(d,a) (DWORD)d-(a+5)
struct jump_insn
{
const BYTE opcode = 0xE9;
DWORD offset;
};
struct jump_short_insn
{
const BYTE opcode = 0xEB;
BYTE offset;
};
struct struc_patch
{
void *data;
void *dest;
char size;
};
#pragma pack(pop)
并在使用中:
// Patches
jump_insn JMP_HOOK_LoadButtonTextures = {POFF(&HOOK_LoadButtonTextures, 0x400000)};
struc_patch patches[] =
{
{&JMP_HOOK_LoadButtonTextures, IntToPtr(0x400000)},
};
使用类成员const我可以更容易和更清晰地定义所有内容,它可以简单地全部记忆。 pack pragma当然需要确保memcpy不会复制BYTE操作码和DWORD值之间的3个对齐字节。
谢谢大家,帮助我使修补方法更加健壮。