WORLDS/GRAFIX0N.DAT is a big-endian (Mac-origin) tileset: an 8-byte header (file size at 0x08, entry count at 0x0c), 16-byte entry records (BE offset + id), and per-graphic blocks with a BE width/height/frames/id header followed by w*h*frames 8-bit palette indices. Confirmed against the in-game loader at 0x444db0 (16-byte record stride, BE->LE dword swap). GRAFIX00.DAT decodes to 29 graphics: explosion (id 901, 8 frames), smoke (id 911, 8 frames), and 27 single-frame 64x64 structure/wall textures (ids 8001-8028). tools/grafix_extract.py renders them to PNG with any 768-byte RGB palette (the XPAL palettes from INTRFACE.FF work). - tools/grafix_extract.py: parser + PNG renderer - docs/FORMATS.md: GRAFIX0N.DAT spec - README: status update Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TN4Ytn3gdWRonNmHpisWQv
7.9 KiB
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— standardRIFF/WAVE, PCM mono, 22050 Hz, 8-bit.PLST<id>.DAT— a song = an ordered sequence of WAVE segments. Payload is big-endian (note: the.FFtable 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 |
64x64 texture tilesets | ✅ cracked, see below (tools/grafix_extract.py) |
STUF<lvl>.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.
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 |