BitB/BitR/XPAL/STR# system fully reverse-engineered: - BitB<id>.dat: big-endian uint16 width/height header - BitR<id>.dat: raw 8-bit palette-indexed pixels (no header) - XPAL<id>.dat: 256 RGB triplets, 8-bit per channel - STR#<id>.dat: length-prefixed UI string tables - Runtime patch: index 2 must be forced to black (0,0,0) -- the game engine overwrites this at runtime but stored values vary Discovery: XPAL7A12 (correct render) vs XPAL7A1C (salmon background) differ in only 3 palette entries (0,1,2); index 2 is the canvas black. All 32 full-screen menu backgrounds, 57 vehicle HUD icons, cockpit windshield frames, vehicle selection screens, and button strips now render correctly. Documented in docs/FORMATS.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
135 lines
5.7 KiB
Markdown
135 lines
5.7 KiB
Markdown
# 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.
|
||
|
||
### 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 plus `N`/`N2` variants (biomes: desert, snow/ice, lava, ocean).
|
||
|
||
| 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 |
|