Skip to content

Resource Forks — the .res container

Every .res file in all five games is a classic Macintosh resource fork, stored as a flat file. Melbourne House and Synergistic both developed on the Mac and ported outward, and the resource system came along for the ride — including to platforms that had no such concept.

The container structure is identical on every platform. The byte order is not, and neither is the data section. Amiga files are big-endian with a u32 length prefix on each resource; DOS and Apple IIGS files are little-endian with no prefix at all, and their type codes are the Amiga codes byte-reversed. Writing one parser for both is correct — but see the reference-list correction below for the field-position trap that costs you if you assume “byte order” means only “multi-byte integers”.

┌─────────────────────────────────┐ offset 0
│ File header (16 bytes) │
├─────────────────────────────────┤ offset 0x10
│ Data section │
│ (sequence of resource blobs) │
├─────────────────────────────────┤ offset = mapOffset
│ Resource map │
│ (type list + reference list │
│ + name list) │
└─────────────────────────────────┘ EOF
+0x00 u32 dataOffset always 0x10
+0x04 u32 mapOffset offset from file start to the resource map
+0x08 u32 dataLength byte length of the data section
+0x0C u32 mapLength byte length of the resource map

Two invariants hold on every file checked, and are worth asserting in a parser: dataOffset + dataLength == mapOffset, and mapOffset + mapLength == fileSize.

Amiga (big-endian) entries are length-prefixed — a u32 length followed by that many bytes. The reference list points at the length prefix.

DOS and Apple IIGS (little-endian) entries have no prefix; the reference list points straight at the data, and each resource’s size is the gap to the next reference entry.

+0x00 16 copy of the 16-byte file header
+0x10 u32 nextMapHandle always 0
+0x14 u16 fileRefNum always 0
+0x16 u16 fileAttributes typically 0x0003
+0x18 u16 typeListOffset from map start (typically 0x1C)
+0x1A u16 nameListOffset from map start

Type list — a u16 holding numTypes − 1, then one 8-byte entry per type:

Offset Size Field Notes
+0x00 4 typeCode FourCC — see the table below
+0x04 2 numResources − 1 Count, minus one
+0x06 2 refListOffset From the type list start, not the map start

Reference list — 12 bytes per resource, on every platform:

Offset Size Field Notes
+0x00 2 resourceId Unique within its type
+0x02 2 nameOffset Into the name list; 0xFFFF = unnamed
+0x04 1 attributes Big-endian files only — see the correction
+0x05 3 dataOffset u24, from the data section start
+0x08 4 reserved Runtime handle; always 0 on disk

Name list — Pascal strings (u8 length, then that many characters). Resources whose nameOffset is 0xFFFF have no name.

DOS and IIGS codes are the Amiga codes byte-reversed — the native byte order of the same 4-character ASCII constant stored in a u32.

Amiga (BE) DOS / IIGS (LE) Meaning
IMAG GAMI Bitmap image
FRML LMRF Sprite animation frames
MMAP PAMM Strategic map terrain data
CSTR RTSC Null-terminated text string
CHAR RAHC Terrain tileset
FONT TNOF Font data
SCEN NECS Scene / room definition
SMUS — Music score (IFF FORM SMUS)

The little-endian reference-list correction

Section titled “The little-endian reference-list correction”

The reference-entry table above — attributes at +0x04, dataOffset at +0x05–+0x07 — is confirmed correct for big-endian (Amiga) files only.

On every little-endian file checked (Conan DOS SCENES.RES, Warriors of Legend furn.res and sfx.res, WIME DOS ASCENE.RES) the real layout is dataOffset as a u24 LE at +0x04–+0x06, followed by attributes at +0x07. The whole 4-byte {attributes, dataOffset} word is byte-reversed as a unit — not just the 3-byte offset inside it.

The parser read the big-endian field positions unconditionally, which produced per-entry offsets tens of thousands of bytes past the real data section on every LE file. That was masked by a sequential-scan fallback that ignores per-entry offsets and just walks the data section blob by blob — so most resources still came out right, and the bug survived. It only surfaced through the minority of entries whose bogus offset happened to land in bounds and look plausible, which paired the right resource id with the wrong bytes.

Two consequences worth carrying:

  • Several previously-recorded “confirmed” findings on DOS-platform data were pointing at the wrong bytes. The ids were right; the content behind them wasn’t. The corrected findings are in docs/legend/plan.md § LMRF and § XFSD.
  • A wider audit of DOS-platform findings built on the old mis-pairing has not been done. Anything internally inconsistent in DOS Legend, WIME or Conan data should be re-verified against the corrected parser before it is trusted.

A fallback that hides a bug this cleanly is worth a second look in any parser: it turned a loud failure into a quiet, partial one.

docs/formats/resource-fork.md in the main repository (not part of this site) carries the byte-level spec, the correction’s full write-up, and the parseResourceFork() implementation notes.