Each game object's .rdata starts with a header's static int[5]
{2000,2500,3000,4000,10000}: 261 blocks. config/eqgame/tus.txt maps
each block to the code referencing its own (non-pooled) strings and vtables.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
76 lines
4.4 KiB
Markdown
76 lines
4.4 KiB
Markdown
# 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<Record>` 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 <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 |
|
|
| 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`.
|