- ff_extract.py: .FF (FlashFile) archive extractor with music song assembly (WAVE+PLST), ogg/mp3 conversion, and FL Studio pack export (samples + per-song MIDI + mapping). - docs/FORMATS.md: reverse-engineered file formats (.FF, WAVE/PLST music, world data + binary notes). - Original game data is gitignored (copyright Reality Bytes, 1995). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
73 lines
3.3 KiB
Markdown
73 lines
3.3 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` cockpit/HUD/menu bitmaps — format TBD |
|
||
|
||
## 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).
|
||
|
||
## 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 |
|