# 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) 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.DAT` PCM segments, `PLST.DAT` playlists, `FFCREATE.EXE` | | `SOUND.FF` | 65 | `WAVE.DAT` sound effects, `FFCREATE.EXE` | | `OBJECTS.FF` | 526 | `ASND.DAT` (enemy/pickup models + object sounds — format TBD) | | `INTRFACE.FF` | 547 | `BitB.dat` headers, `BitR.dat` rasters, `XPAL.dat` palettes, `STR#.dat` string tables, `RTbl.dat` remap tables, `xpXl.dat` palette variants | ## Music: `WAVE` segments + `PLST` playlists ✅ - `WAVE.DAT` — standard `RIFF`/`WAVE`, PCM **mono, 22050 Hz, 8-bit**. - `PLST.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` 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.dat` — bitmap header (4 bytes) ``` uint16 width (big-endian) uint16 height (big-endian) ``` ### `BitR.dat` — raw bitmap raster Raw 8-bit palette-indexed pixels, row-major, `width * height` bytes total. No header. ### `XPAL.dat` / `xpal.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#.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 `` = 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.MAP` | tile/heightmap grid, 1 byte per cell | layout confirmed (byte grid); dims TBD | | `LAND.DAT` | terrain geometry/tile defs | TBD | | `GRAFIX0N.DAT` | 64x64 texture tilesets | ✅ cracked, see below (`tools/grafix_extract.py`) | | `STUF.DAT` | entity placement (contains `Item` records) | TBD | ### `GRAFIX0N.DAT` — texture tileset ✅ implemented in `tools/grafix_extract.py` Big-endian (Mac-origin data, byte-swapped by the in-game loader at VA `0x444db0`). `GRAFIX00.DAT` = 29 graphics: an explosion sprite (id 901, 8 frames), a smoke sprite (id 911, 8 frames), and 27 single-frame 64x64 structure/wall textures (ids 8001-8028: metal panels, hazard stripes, warning bars, arrows, circuit patterns). ``` Header: 0x00 8 bytes reserved (0) 0x08 u32 BE total file size 0x0c u16 BE entry count N 0x20 N*16 entry records Entry record (16 bytes): 0x00 u32 BE absolute file offset of the graphic block 0x04 u16 BE id 0x06 10 bytes reserved Graphic block (at record offset): 0x00 u16 BE width (64) 0x02 u16 BE height (64) 0x04 u16 BE frame count 0x06 u16 BE id 0x08 8 bytes reserved 0x10 w*h*frames bytes 8-bit palette indices ``` Pixels are 8-bit indices into a 256-colour palette (768-byte RGB, e.g. the `XPAL` palettes in `INTRFACE.FF`). Index 1 (magenta `ff 00 ff`) is the transparency key. The exact world palette per tileset is still TBD; UI `XPAL0000` renders them plausibly. ```sh python tools/grafix_extract.py WORLDS/GRAFIX00.DAT -p tools/out_intrface/raw/XPAL0000.DAT ``` ## 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 |