5.1 KiB
5.1 KiB
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 @41style), 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 fromsots_addresses.h), readsshim.cfg(hooks=trace|off),MH_Initialize, one MinHook hook onexeBase + sots::addr::Mars_Application_Initialize,MH_EnableHook. Detach →MH_Uninitialize.third_party/minhook/— MinHook vendored (BSD-2, seeVENDOR.txtfor commit).CMakeLists.txt,cmake/toolchain-mingw-i686.cmake,CMakePresets.json(shim= i686 MinGW cross,host= Linux placeholder testtests/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 (build4a15301-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.shprintedexports: 66 names, identical to binkw32.dll. hooks=offrun (first banner inm0.log, pid 6396): forwarders alone play the Bink intro — proxy proven separately from hooking.tools/clean_room_check.sh→ OK.
Gotchas
- Do not call the original from a C++
thiscalldetour. 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 insots_addresses.his[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,jmpto 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++. - Hooking from
DllMainworked (process is single-threaded at that point; MinHook only touches kernel32). The fallback (install from the first forwarded Bink call) was not needed. --kill-at/--enable-stdcall-fixupmust NOT be passed to ld — they would strip the@Nsuffixes the exe imports by name. With a plain.defMinGW exports the names verbatim.objdump -plists+base[rows in both the address table and the name table; compare the[Ordinal/Name Pointer] Tablesection only (forwarder rows printForwarder RVA -- target).- rsync
--exclude 'build-*'also matchesbuild-shim.sh; use/build-*/. - The CT is unprivileged: files written on the host must be
--chown=100000:100000or the CT can't write its build dir.Z:mappings are per logon on the VM; SSH sessions start without it. shim.logis held open by the running game; move/rotate it only afterStop-Process.