-
Notifications
You must be signed in to change notification settings - Fork 12
How to Add Patches & Pointers
When writing a patch that replaces game code with an interception, use the following format:
std::make_shared<D2InterceptorPatch>(D2Offset(D2TEMPLATE_DLL_FILES::DLL, {
{ GameVersion::VERSION, offset },
{ GameVersion::VERSION, offset },
}), OpCode::OPCODE, (void*) FUNCTION, Number of Bytes to Override)
However, for patches that utilize the original D2Template way of patching, follow this format:
std::make_shared<D2AnyPatch>(D2Offset(D2TEMPLATE_DLL_FILES::DLL, {
{ GameVersion::VERSION, offset },
{ GameVersion::VERSION, offset },
}), Patch Data, Relative Addressing (true/false), Number of Bytes to Patch)
Note that if a particular entry is left empty, then the game won't patch at all or will crash for that particular version.
Sometimes, you might have interception code that only works on certain versions of the game. Instead of rewriting your code to fulfill the requirements of the two different versions, simply add an entry for that version and specify the offset to be D2Patch::NO_PATCH, like so:
std::make_shared<D2InterceptorPatch>(D2Offset(D2TEMPLATE_DLL_FILES::D2DLL_D2CLIENT, {
{ GameVersion::VERSION_107, D2Patch::NO_PATCH },
{ GameVersion::VERSION_108, D2Patch::NO_PATCH },
{ GameVersion::VERSION_109, D2Patch::NO_PATCH },
{ GameVersion::VERSION_109b, D2Patch::NO_PATCH },
{ GameVersion::VERSION_109c, D2Patch::NO_PATCH },
{ GameVersion::VERSION_109d, D2Patch::NO_PATCH },
{ GameVersion::VERSION_110, D2Patch::NO_PATCH },
{ GameVersion::VERSION_111, D2Patch::NO_PATCH },
{ GameVersion::VERSION_111b, D2Patch::NO_PATCH },
{ GameVersion::VERSION_112, D2Patch::NO_PATCH },
{ GameVersion::VERSION_113c, 0xDEAD },
{ GameVersion::VERSION_113d, 0xBEEF },
}), OpCode::CALL, (void*) SomeFunction, 0xDEADBEEF)
Do not abuse this feature to make the game not crash on unsupported versions. Doing so will make it ambiguous as to whether a NO_PATCH is intentional. The game should crash if the version is unsupported.
All pointers take on the following syntax:
D2FUNC(DLL, NAME, RETURN, CONV, ARGS, OFFSETS)
D2VAR(DLL, NAME, TYPE, OFFSETS)
D2PTR(DLL, NAME, OFFSETS)
// For example:
D2VAR(D2CLIENT, SomeD2Variable, int, (
{ GameVersion::VERSION_112, 1.12_ptr },
{ GameVersion::VERSION_113c, 1.13c_ptr },
{ GameVersion::VERSION_113d, 1.13d_ptr },
{ GameVersion::VERSION_114d, 1.14d_ptr })
);
D2FUNC(D2GFX, SomeD2Function, int, __stdcall, (), (
{ GameVersion::VERSION_112, 1.12_ptr },
{ GameVersion::VERSION_113c, 1.13c_ptr },
{ GameVersion::VERSION_113d, 1.13d_ptr },
{ GameVersion::VERSION_114d, 1.14d_ptr })
);
There are 3 types of pointers that can be added: Variables, Functions, and Pointers.
Pointer types should be named and typed as accurately as possible to their actual function in the game. For example:
mov eax, [D2Client.dll + 1234]
cmp eax, 0
jg [D2Client.dll + 6861A]
In this example, the x86 instruction jg indicates that the variable at D2Client.dll + 0x1234 is an int. If it were an unsigned int, the instruction would have been ja. In other words, don't indiscriminately give integer variables the DWORD type.