havoc-remaster/docs/FORMATS.md
pyr0ball 27174a1c79 feat: NOCD patch boots Havoc on modern Windows (defeats CD copy protection)
Diagnosed and fixed the crash that killed HAVOC_NOCD.EXE ~1s after launch
(dgVoodoo window appears black, then the process exits). It was not a clean
exit: an ACCESS_VIOLATION (null-pointer read at VA 0x444E6A) during init.

Root cause is a copy-protection check in the WORLDS\ data-file loader
(0x42a480). It opens each file (GRAFIX/LAND/MAP/STUF) from both a local
"WORLDS\<name>" path and a CD-drive path (HAVOC.INI [SETUP] DRIVE=D:\), and
returns a valid stream only when the local open fails and the CD-drive open
succeeds. With files present locally and no D: drive it returned NULL, and the
GRAFIX loader (0x444db0) dereferenced that NULL without checking.

Fix: 14-byte patch at FO 0x29a2b rewrites the return decision to hand back the
successfully-opened local object. The game now boots to the title screen
("HAVOC(tm) by Reality Bytes"), responsive, main loop running.

- docs/PATCHES.md: full patch table (28 patches) + crash write-up
- tools/: RE + patching scripts (r2pipe disasm, minidump parser, ctypes
  debugger, PE/IAT/import analysis)
- run_probe.bat / run_and_log.bat: reliable native launch for repro
- .gitignore: exclude CD images, Ghidra install/project, dgVoodoo, *.ini

Diagnosed via WER minidumps (%LOCALAPPDATA%\CrashDumps) parsed in pure Python
(no cdb/windbg available).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
2026-07-03 09:29:59 -07:00

6.6 KiB
Raw Blame History

Havoc file formats (reverse-engineered)

All offsets/sizes verified against the retail data set (Reality Bytes, Inc., 1995).

Binaries

File Type Notes
HAVOC.EXE PE32 i386, GUI Imports DDRAW, DSOUND, WINMM, WSOCK32, GDI32, USER32, KERNEL32. No Direct3D — 3D is a software rasterizer drawing into a DirectDraw surface. Contains a CD-presence check ("...Extra Player CD...").
LAUNCHER.EXE PE32 i386, GUI Front-end menu (see havoc_9 screenshot).
SETUP.EXE PE32 i386, GUI Config/setup.
DIRECTX/ DX5-era Ships 16-bit DSETUP16.DLL/DDRAW16.DLL — the 16-bit installer path is what breaks on 64-bit Windows, not the game itself.

.FF — "FlashFile" archive implemented in tools/ff_extract.py

Used by MUSIC.FF, SOUND.FF, OBJECTS.FF, INTRFACE.FF. Built by the bundled DOS tool FFCREATE.EXE (found inside MUSIC.FF/SOUND.FF).

uint32  file_count                      (little-endian)
repeat file_count:
    uint32  absolute_offset             (little-endian, from start of .FF)
    char[]  name                        (NUL-terminated, variable length)
<concatenated payloads>                 entry size = next_offset - this_offset

Notes:

  • The table may contain a trailing empty-name entry — skip it.
  • Entry payloads are raw and self-describing (e.g. RIFF/WAVE, MZ, bitmaps).

Archive contents

Archive Count Entry kinds
MUSIC.FF 116 WAVE<id>.DAT PCM segments, PLST<id>.DAT playlists, FFCREATE.EXE
SOUND.FF 65 WAVE<id>.DAT sound effects, FFCREATE.EXE
OBJECTS.FF 526 ASND<id>.DAT (enemy/pickup models + object sounds — format TBD)
INTRFACE.FF 547 BitB<id>.dat headers, BitR<id>.dat rasters, XPAL<id>.dat palettes, STR#<id>.dat string tables, RTbl<id>.dat remap tables, xpXl<id>.dat palette variants

Music: WAVE segments + PLST playlists

  • WAVE<id>.DAT — standard RIFF/WAVE, PCM mono, 22050 Hz, 8-bit.
  • PLST<id>.DAT — a song = an ordered sequence of WAVE segments. Payload is big-endian (note: the .FF table around it is little-endian — mixed by design):
uint16  segment_count        (big-endian; how many WAVE segments belong to this group)
uint16  step_count           (big-endian)
uint16[step_count]  sequence  (big-endian; 1-based segment index)

Segment resolution: a playlist named PLST<id> has base id = int(id, 16); step value N maps to wave id base + N - 1 (e.g. PLST07D1 step 2 → WAVE07D2). Concatenating the referenced segments' PCM reproduces the song exactly (verified by ear).

INTRFACE.FF bitmap system implemented in tools/ff_extract.py

All entries share a 4-digit hex ID that links related assets (e.g. 797C = main menu): BitB797C.dat (dimensions) + BitR797C.dat (pixels) + xpal797c.dat (palette) + STR#797C.dat (strings).

BitB<id>.dat — bitmap header (4 bytes)

uint16  width   (big-endian)
uint16  height  (big-endian)

BitR<id>.dat — raw bitmap raster

Raw 8-bit palette-indexed pixels, row-major, width * height bytes total. No header.

XPAL<id>.dat / xpal<id>.dat — palette (768 bytes)

256 RGB triplets, 8-bit per channel (NOT 6-bit VGA). One palette per screen/sprite group.

Critical runtime patch: the game engine always forces palette index 2 to (0, 0, 0) black at runtime. The stored XPAL files may contain any value there (often #e78484 salmon from the development environment). Any renderer must override index 2 to black after loading. Index 0 and 1 are the transparency/key color (#ff00ff magenta in most palettes).

Discovery method: comparing XPAL7A12 (correct black background) vs XPAL7A1C (wrong salmon background) revealed only 3 differing entries out of 256 -- indices 0, 1, and 2.

STR#<id>.dat — string table

Length-prefixed strings for the screen's UI labels. First byte = string length, then UTF-8 text. Example: STR#797C contains all main menu button labels and the copyright notice.

Two special-purpose string tables live in INTRFACE.FF at fixed IDs:

ID Content
7D02 Pickup banner text -- all item and weapon pickup notification strings
7D03 HUD active weapon name display strings

See docs/WEAPONS.md for the complete weapon list derived from these tables.

Identified screens (640x480 full-screen bitmaps)

ID Content (from STR# strings)
797C Main menu
79E0 Network options
79AE Load/save game
7A44 Settings menu
7A4E Sound/music settings
7A58 Graphics settings
7A62 Keyboard settings
7A6C Joystick settings
7ADA Info/help
7A12 Network client/server status
79F4 Network client options
79FE Network server options

Identified sprites

Size Count Content
640x480 32 Full-screen menu/UI backgrounds
512x236, 512x230, 512x218 3 Cockpit windshield frames (color variants)
428x196 12 Vehicle selection screens with 3D-rendered thumbnails
104x432 3 Cockpit side panel strips
420x46, 228x46 29 Button/UI strip elements
48x48 57 Vehicle HUD icons (monochrome green, multiple vehicles/angles)
18x18 13 Small UI icons

World data (WORLDS/) — TODO (3D terrain)

Per-level sets <NN> = 01..06. Six worlds confirmed from EXE string table + player memory:

File prefix World name Biome
LAND01xx Badlands Desert
LAND02xx Fallout Ice/Snow
LAND03xx Tyrak Alien
LAND04xx Wasteland Desert (harder)
LAND05xx Malterra Ice (harder)
LAND06xx Overlord Alien (harder)

Level suffix codes within each world: 01=Level 1, 02=Level 2, 03=Level 3, 04=Boss, 0B=Bonus. Network variants use 0N, 1N, 2N prefixes for the three biomes at Easy/Medium/Hard.

Music track ID is stored at offset 18 as big-endian uint16 in each LAND*.DAT file.

Pattern Guess Status
MAP<lvl>.MAP tile/heightmap grid, 1 byte per cell layout confirmed (byte grid); dims TBD
LAND<lvl>.DAT terrain geometry/tile defs TBD
GRAFIX0N.DAT terrain/texture tilesets TBD
STUF<lvl>.DAT entity placement (contains Item records) TBD

Other top-level data — TODO

File Notes
BIGFILE.DAT (64 MB) starts Copyright 1995, Reality Bytes, Inc. then embedded RIFF data — bulk audio/stream pool
STRDATA.DAT length-prefixed UI string table (Out of memory., Not enough disk space., …)
HAVOC.ICO 32×32 4-bit icon