GetSpellByID only matches its caller when it lives in a separate file: MSVC 7.1 tracks callee register use within a translation unit, so TU boundaries show up in the generated code. docs/MATCHING.md collects these lessons. Co-Authored-By: Claude Opus 5.5 <[email protected]>
2.8 KiB
2.8 KiB
Matching notes (MSVC 7.1, eqgame.exe)
These are things learned while matching. Each one changes the generated bytes.
Translation units matter
- MSVC 7.1 compiles a .cpp's functions callee-first and remembers which registers
each callee clobbers. Calls to a callee in the same file can skip reloads
(
ecx, …) or useadd esp,4instead ofpop ecx. Calls to a callee in another file assume the worst.- Example:
0x401bb1only matches whenGetSpellByID(0x401b28) is in a different file, even though the two are adjacent in.text. - Example (EQPlayer):
0x480130only matches whenEQPlayer::HairType(0x47a523) is defined in the same file.
- Example:
- So callers and callees tell you where file boundaries are. Own whole files, not address ranges.
- Inline/template functions (header code) are placed by the linker in the first
object that emitted them. Put them in
include/with annotations; any object may emit them.
Code generation clues
| Original shows | Source had |
|---|---|
and dword ptr [x],0, push 0x6; pop eax, pop ecx after cdecl calls |
/O1 (global flag) |
mov eax,cookie … call @__security_check_cookie@4 |
/GS with a char buffer (global flag) |
mov eax,handler; call __EH_prolog |
EH frame under /O1: an object with a dtor, or new of a class with a ctor |
delete[] calls 0x60d682 rather than 0x60d0d5 |
#include <new> (declares operator delete[]) |
only a vptr store, no mov eax,ecx |
a destructor; a ctor returns this |
| vptr of the derived class only, no base vptr store | base class is __declspec(novtable) |
ret N but ecx never read |
a member function that doesn't use this |
push ebp; mov ebp,esp in a small function, param slot reused for a local |
member function taking the address of a local/param; let MSVC coalesce, don't fake it |
| member store order differs from declaration order | assign in the body, not the init list |
an if/else whose tails merge after a hoisted load |
write the else branch explicitly rather than a fall-through if (!x) x = … |
two jmp-only functions in a row (jmp Reset) |
out-of-line copies of inline dtors, used by EH unwind funclets |
Library code
python3 tools/eq.py libscan tools/probes/stl.cpp --apply marks copies of
Dinkumware STL instances as lib. They need no decompiling, because any file
that includes the same headers and uses the same types emits them. A probe
function only counts if every callee is itself library code. That stops game
classes' deleting destructors (same bytes, different callee) from being
misclassified.
Some std::basic_stringbuf members (e.g. seekoff at 0x40224b) differ from
the probe's copy in register allocation. Most likely the same-file callee effect
above, so the probe's surrounding code matters. These are still listed as fn.