Liquid War 5.6.2
Binary: test/binaries/candidates/liquid-war/LW5/lwwin.exe, image base
0x00400000.
DirectDraw startup across guest threads
Liquid War creates its only window (hwnd 0x18001 cooperatively, 0x20001
with real Workers) on Allegro thread T1. Video-mode selection later runs on the
main guest thread. Allegro bridges the two with a synchronous private window
message:
0x00445f60callsSendMessageA(hwnd, 0x004bbfa4, callback, 0).- Its callback at
0x0045d470invokesIDirectDraw2::SetCooperativeLeveland the mode-selection operations on the HWND owner thread. 0x0045d390treats a zero/failed callback result as an unavailable mode, so all fullscreen and windowed candidates eventually become “Unable to initialize graphics.”
Before cross-thread SendMessageA routing, the callback happened to execute on
the caller's WebAssembly instance. Correct owner-thread routing exposed that
DirectDraw's display width, height, depth, selected-mode flag, cooperative HWND,
exclusive flag, and primary palette were mutable WebAssembly globals. Each guest
thread has a separate instance, so T1 successfully selected 640x480x8 while the
main thread still observed its private 640x480x16 defaults and rejected the
driver.
Those fields now live in the atomic shared-memory DX_PROCESS_STATE record.
The window callback therefore retains Win32 thread affinity while subsequent
DirectDraw calls and GetSystemMetrics see the same process device state.
test/test-cross-thread-send.js writes the record from a real owner Worker and
asserts that a peer instance reads every field.
Acceptance on 2026-08-26:
test/test-liquid-war-candidate.js: 640x480 textured menu, 63 colors, packed data/custom assets loaded, and fullscreenDirectDraw accelmode succeeds.- Node
--threads: three guest threads in Workers, live 640x480x8 primary with 59 sampled colors, and the same textured menu. - Isolated Chrome Worker backend: status reports
3 threads in workers; the visibleLiquid War 5.6.2window reaches the textured Play/Map/Options menu without compatibility errors. Diagnostic screenshot:/private/tmp/lw-browser-worker-fixed.png.
Acceptance update on 2026-09-01:
__p___initenvis now an MSVCRT cdecl alias for the same narrow environment vector as__p__environ; the original server no longer stops during CRT startup.strncatis implemented as a bounded cdecl append helper; the original client no longer stops after the network connect path starts.test/test-liquid-war-candidate.jsnow drives the highlighted Play item with Enter and captures a single-player arena frame at batch 65000. The current proof frame is 640x480 with the expected red/yellow teams, blue map, white walls, and timer. Ad-hoc screenshot:/private/tmp/lw-single3-65000.png.
Network startup
- The executable imports MSVCRT
_beginthreadthrough IAT VA0x0046e114.node tools/xrefs.js .../lwwin.exe 0x46e114 --codefinds its five call sites. - MSVCRT's runtime
CreateThreadentry is always its wrapper. In the on-disktest/binaries/dlls/msvcrt.dll, original VA0x7800b93ecalls the real routine stored at private-block offset+0x48with the argument at+0x4c. This was obtained withtools/disasm_fn.jsafter translating the traced runtime VA by the DLL load delta. - The network retry worker's real entry is
0x00414d40. It calls0x00419090, which creates a TCP socket, binds it, connects it, applies socket options, and enablesFIONBIO. Its argument structure contains the server address inline at+0x04and the port at+0x14. 0x00414c50allocates that structure, starts the worker through0x0041aeb0, waits on status at+0x18, and reads the result at+0x24.
Ruled out
The short-lived thread stream seen after entering Net game is not a thread
scheduler failure. It is Liquid War retrying 127.0.0.1:8035 after a queued
Enter remains logically down across the transition to the network-settings
menu and activates Start game there too. Loopback is deliberately local to one
emulator process, so that address cannot reach the separate server process.
For headless input, a one-batch keydown followed by di-keyup releases the
DirectInput state without leaving a delayed WM_KEYUP. This reaches the
settings screen, where 10.77.0.1 can be entered before Start game. The fixed
reproduction is encoded in test/test-vlan-match.js.
The current two-process path reaches the waiting room with the player listed, and the server accepts and exchanges protocol bytes. It has not yet been promoted to a gameplay test because the lobby's "Start now"/"Play" activation still needs a reliable headless route.