# 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 use `add esp,4` instead of `pop ecx`. Calls to a callee in **another file** assume the worst. - Example: `0x401bb1` only matches when `GetSpellByID` (`0x401b28`) is in a different file, even though the two are adjacent in `.text`. - Example (EQPlayer): `0x480130` only matches when `EQPlayer::HairType` (`0x47a523`) is defined in the same file. - 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. ## Finding file boundaries Every game .cpp includes a header with a `static const int[5] = {2000, 2500, 3000, 4000, 10000}`, so each object's `.rdata` starts with that table: 261 of them. `python3 tools/eq.py tus` writes `config/eqgame/tus.txt`, which maps each block to the code that references its strings and vtables. Read `CODE_LO` as where the file starts, and the end as the next file's start. Shared constants and pooled literals (`/GF`) are filtered out, but treat the estimate as a hint and confirm it with the register-usage rule above. ## Flags Global: `/O1 /MT /GX /GS /Gy`. `/MT` matters: without `_MT`, Dinkumware's locale/stream code compiles without locks and STL instances don't match. ## Template and compiler-generated instances Write the real STL usage (e.g. `std::list` with `push_back`, `remove`, `sort`) and let MSVC emit the instances. Then `python3 tools/eq.py pair src/eqgame/X.cpp --range 0xSTART-0xEND --write` appends `// FUNCTION:` / `// LIBRARY:` annotations for every compiled function with a unique byte match in the range. It checks relocations to tell ICF-folded twins apart, and `std::` members become `LIBRARY`. ## 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 ` (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 | | a `std::string` temporary lands in a different stack slot | construct from `const char*` directly (`istringstream in(p)`), let the conversion make the temp | | a small local shares a slot with a dead one | the local is smaller than you think, e.g. a 4-byte event base struct, not the 12-byte derived one | | a `bool` result kept in `bl` across two returns | those returns return a variable, the others a constant | | 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`.