Emulating game rules on a world built by hand
What to know
2026-10-24, Hardwar's pilots_truth.py (pilots and Police).
The Police and plan routines take pointers to pilot and building records and read a dozen globals. They can be run on their own under pe32.py without loading a game:
- Records at known addresses. Map one region, put pilot i at
base + i × 0x400and the null record at index 15, and point the globals at them (the null-pilot and null-building copies0x582138/0x595ad8and0x582134/0x595ad4, the faction HQs0x585a7cto0x585a8c, the building list0x58214c, the limbo building0x582150with its installed flag0x582148). Writing results back as indices (pilot 0x1000 + i, building 0x2000 + j) makes them comparable with a port that uses indices. - Stub callees with a code hook. A hook on the callee's first instruction records its registers, sets EAX, pops the return address into EIP. For a callee that ends in
RET n, move the return address up bynbytes first (VhGetPower0x47f00c takes one stack word). - Stack arguments. Watcom passes four registers (EAX, EDX, EBX, ECX) and the rest on the stack, cleared by the callee: write them above the return address before
Emu32.call. - Map tables by running the code. Rather than reading a binary-search
switch, call the routine for every value:PilPlanName0x4652c8 for each plan type gives the names, and the plan executor 0x45e2e8 with a hook on the DoPc range gives which action each type runs. - Stack garbage in records. Some routines build a 24-byte plan on the stack and fill only the words they use; the rest is whatever the stack held, so fill the stack with a marker before the call and leave those words out of the comparison (and mark the port's choice
Deviation:).
Ghidra's decompiler drops most register arguments of these routines (it shows them as placeholders named after the registers), so read them as machine code: capstone over the image, restarting after any byte it cannot decode, gives a whole-program listing in a few seconds.