# M0 — shim bootstrap (proxy `binkw32.dll` + one hook) **Result (2026-09-07): proven end to end on the real game.** The proxy DLL loads, forwards all Bink calls, relocates the target RVA against the ASLR'd exe base, hooks `Mars::Application::Initialize`, logs the call, and the game plays its intro and reaches the main menu. ## What was built - `src/shim/binkw32.def` — 66 forwarders (`_BinkOpen@8 = binkw32_real._BinkOpen@8 @41` style), names and ordinals mirrored from the real DLL. Note: the real DLL has **66** exports including `_RADTimerRead@0`, which earlier lists missed. The exe imports 12 of them **by name** (no ordinals). - `src/shim/main.cpp` — `DllMain` → `Shim_Init`: opens `\shim.log`, logs banner (build id, exe path, exe base + ASLR delta, provenance line lifted from `sots_addresses.h`), reads `shim.cfg` (`hooks=trace|off`), `MH_Initialize`, one MinHook hook on `exeBase + sots::addr::Mars_Application_Initialize`, `MH_EnableHook`. Detach → `MH_Uninitialize`. - `third_party/minhook/` — MinHook vendored (BSD-2, see `VENDOR.txt` for commit). - `CMakeLists.txt`, `cmake/toolchain-mingw-i686.cmake`, `CMakePresets.json` (`shim` = i686 MinGW cross, `host` = Linux placeholder test `tests/addr_smoke.cpp`). - `tools/build-shim.sh` (build box), `tools/sync-build.sh` (dev box → build box), `tools/deploy.ps1`, `tools/undeploy.ps1` (game VM). ## Build & deploy ``` # dev box: push the tree to spicy:/bulk-storage/re-lab/build/sots-engine (== CT111 /srv/re-lab/...) # and run tools/build-shim.sh inside CT111 (needs g++-mingw-w64-i686 binutils-mingw-w64-i686 cmake ninja-build) tools/sync-build.sh # -> builds build-shim/binkw32.dll, diffs its export-name table against the real DLL (fails on mismatch), # stages binkw32.dll + shim.cfg + deploy.ps1 + undeploy.ps1 + BUILD_ID in /srv/re-lab/shim/dist (= Z:\shim\dist) # game VM (re@192.168.10.139): Z: is per-logon, so pass share creds (or set RELAB_USER/RELAB_PASS) powershell -ExecutionPolicy Bypass -File deploy.ps1 -ShareUser re -SharePass # stops the game, keeps the original as C:\SOTS\binkw32_real.dll (only if not already a proxy), # copies the proxy, writes shim.cfg only if absent, relaunches via scheduled task SOTS powershell -ExecutionPolicy Bypass -File undeploy.ps1 # put the original back ``` Logs: `C:\SOTS\shim.log` (append mode, one banner per run). Screenshots: `qm monitor 140` screendump. ## Evidence (`/bulk-storage/re-lab/shim/logs/`) - `m0.log` — the shim log. Key lines from the passing run (build `4a15301-20260907T2118Z`): ``` ==== sots-engine shim (binkw32 proxy) build 4a15301-20260907T2118Z ==== exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=3808 shim=730c0000 addresses: Source: sots-re ghidra/addresses.json @ daea98f, generated 2026-09-07 by tools/gen_addresses.py hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50 hook: MH_Initialize -> MH_OK / MH_CreateHook -> MH_OK (trampoline=00e50fe0) / MH_EnableHook -> MH_OK Application::Initialize called (this=035c8128) ``` - `m0-main-menu.png` — main menu with the hook live (intro skipped with 3× Esc, ~35 s after launch). - Export table: `build-shim/exports.txt`; `tools/build-shim.sh` printed `exports: 66 names, identical to binkw32.dll`. - `hooks=off` run (first banner in `m0.log`, pid 6396): forwarders alone play the Bink intro — proxy proven separately from hooking. - `tools/clean_room_check.sh` → OK. ## Gotchas 1. **Do not call the original from a C++ `thiscall` detour.** v1 did exactly that (`void __thiscall Detour(void* self) { log; orig(self); }`) and the game died inside Initialize with "Mars: Application error encountered" (`m0-crash-hookv1.{log,png}`). The prototype in `sots_addresses.h` is `[unverified]`; if Initialize takes stack args, returns via EAX or relies on EDX (fastcall-like), a calling wrapper corrupts the stack/registers on return. The shipped detour is an asm stub: `pushfl/pushal`, call a C logger with ECX, `popal/popfl`, **`jmp` to the trampoline**. It's correct for any convention but cannot log the return. Until a prototype is verified, use this pattern for every trace hook; only replace-hooks (that never call the original) may be plain C++. 2. Hooking from `DllMain` worked (process is single-threaded at that point; MinHook only touches kernel32). The fallback (install from the first forwarded Bink call) was not needed. 3. `--kill-at` / `--enable-stdcall-fixup` must NOT be passed to ld — they would strip the `@N` suffixes the exe imports by name. With a plain `.def` MinGW exports the names verbatim. 4. `objdump -p` lists `+base[` rows in both the address table and the name table; compare the `[Ordinal/Name Pointer] Table` section only (forwarder rows print `Forwarder RVA -- target`). 5. rsync `--exclude 'build-*'` also matches `build-shim.sh`; use `/build-*/`. 6. The CT is unprivileged: files written on the host must be `--chown=100000:100000` or the CT can't write its build dir. `Z:` mappings are per logon on the VM; SSH sessions start without it. 7. `shim.log` is held open by the running game; move/rotate it only after `Stop-Process`.