sots-engine/docs/M0.md

72 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `<dll dir>\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 <pw>
# 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`.