Colin McRae Rally 2.0 demo (Codemasters, 2000)

lib/apps.js id cmr2_demo (localhost-only), exe test/binaries/win98-games-a-d/Colin-Mcrae-Rally2-demo-D3D/extracted/CMR2Demo.exe, mounted at c:\cmr2demo\. Manifest: node tools/gen-win98-games-a-d-manifests.js. Direct3D 7 (IDirect3DDevice7 on a 640x480x16 flip chain), DirectInput keyboard, Bink (binkw32.dll) for movies.

Install (host-side)

The fixture is an InstallShield 6 Disk1 (Setup.exe, data1.hdr, data1.cab, data2.cab, ikernel.ex_). IS6 starts its engine ikernel.exe as an out-of-process COM server (CLSCTX_LOCAL_SERVER), which the emulator does not provide, so Setup stops at "failed to launch installation engine". Extract the cabinets on the host instead:

node tools/is-cab.js test/binaries/win98-games-a-d/Colin-Mcrae-Rally2-demo-D3D \
  --extract=test/binaries/win98-games-a-d/Colin-Mcrae-Rally2-demo-D3D/extracted

221 files, each checked against its descriptor's MD5.

The game quits at once ("Program finished normally") unless HKLM\Software\Codemasters\Colin McRae Rally 2 holds Sku_Type and Install_Version as well as the two paths; startupRegistry in lib/apps.js writes what the installer would (Game_HDPath/Game_CDPath = c:\cmr2demo, Install_Version = Full, Sku_Type = EUROPE).

Emulator fix it needed (1817ff91)

Every texture is created as a DXT5 staging surface (DDSD_LINEARSIZE, DDPF_FOURCC), filled through Lock, then Blt into an ARGB4444 texture: the game relies on the driver to decompress on Blt. DirectDraw now maps the DXT1-5 FourCCs to their own surface formats, reports LINEARSIZE + the FourCC from Lock/GetSurfaceDesc, and decompresses on Blt into a 16/32-bpp destination. Before it, every texture in the game was static noise. test/test-directdraw-dxt-blt.js checks it against an independent decoder.

Running it

The menus time out into the attract demo

The front end works. Its main menu (rally | arcade | options | quit) has an idle timer that drops into an attract demo, and at the default 200 ms/batch it fires within ~200 batches of the language-screen Enter, so a capture never catches the menu. Slow the clock before the Enter: 29000:tick-ms:5. (Switching at 30300 is already too late: the timer has run out by then.)

Route to gameplay (2026-10-06, local CLI, ~45 s)

node test/run.js --app=cmr2_demo --quiet-api --max-batches=421000 --max-seconds=120 \
  --watch=0x80b94c --watch-log --input=29000:tick-ms:5,30000:di-keydown:13,30003:di-keyup:13,\
32000:keydown:13,32020:keyup:13,165000:keydown:38,181000:keyup:38,\
182000:keydown:39,182020:keyup:39,186000:keydown:39,186020:keyup:39,190000:keydown:39,190020:keyup:39,\
195000:keydown:13,195020:keyup:13,340000:di-keydown:38

Language Enter, main menu Enter (rally), the rally hub (Information | Set Up | Repair | Race | Quit, Australia stage 5), Right x3 to Race, Return, the stage loads and the start lights go green by ~330k. Holding Up (DirectInput) accelerates: off the line, 50 mph by 360k. Without it the car sits on the line. Menu presses need ~2000+ batches between them or they land during a page transition and are lost. The route was captured with that --watch on; it changes block granularity, and without it the same inputs were seen to land in the attract demo, so keep it until the route is re-timed without it. Evidence: scratch/runs/20261006T1650Z-cmr2_demo-gameplay-w6.

Car lighting (fixed 9d35dc49)

The car is FVF 0x2d2 (XYZ|NORMAL|DIFFUSE|SPECULAR|TEX2) drawn with LIGHTING=TRUE, DIFFUSEMATERIALSOURCE=MATERIAL and a zero diffuse dword. The light setup at 0x4b68b0 switches on a pass number: case 2 (the car) sets AMBIENT from [0x6d4fe4] (0xff698299 on Australia 5) and enables light 0, the sun; cases 0/1 use [0x6d5878] (never written, so 0) and point lights 1/2, which [0x51d024] (a per-stage table value read at 0x46492e, headlights) switches off in daylight. Device7 SetLight/LightEnable/SetMaterial were stored but never applied, so the car kept its zero diffuse dword. Evidence: scratch/runs/20261006T1715Z-cmr2_demo-car-lighting-w6.

Open