Structural documentation of the Amiga CD32 release of
Fire & Ice: The Daring Adventures of Cool Coyote (Graftgold / Renegade,
1994). Documentation only: no game code, no assets, no audio and no graphics
are committed here. Every figure and every image in these notes is reproduced
by a tool in tools/ from a disc image you supply yourself.
This is the thirteenth disc documented against the shared CD32/CDTV platform checklist.
The preparer/trailing-run correlation goes three for three. Two of the
previous twelve discs left a 232-sector unclaimed run at the end of the volume
instead of the usual 32, and those two were also the only two whose PVD
preparer field read D J Pocock. The prediction was written down before the
volume was parsed. The preparer is D J Pocock and the run is 232 sectors
— on a 1,270-sector volume, so the anomaly is now 18.3 % of the whole
volume, across a 65:1 range of volume sizes inside the group. Every
size-based explanation was already dead; this makes the run a property of who
cut the master.
A seventh cruncher, and it packs 72 % of the disc. PowerPacker 2.0,
never seen on the previous twelve. Twenty-one files, all with the same
[9,10,12,13] efficiency table, all decoding to exactly their declared
length — the only validation there is, because PowerPacker carries no
checksum at all. tools/pp20.py is a fresh implementation.
PowerPacker inside PowerPacker, and entropy finds none of it. Every one of the seven world files, once unpacked, holds two to five further PP20 streams as records; the two executables hold one each. Twenty-nine embedded streams, and not a single 8 KB window anywhere on the disc exceeds 7.5 bits/byte. The largest is 3,896 bytes and the smallest 496, against files of 105 KB to 410 KB — too small to move an average. The entropy heuristic that caught Guardian and Banshee is a sufficient signal, not a necessary one; what found all 29 was a magic scan at every offset validated by actually running the decompressor.
c/SetPatch 40.14 retires a claim. Byte-identical to Dragonstone's and
Banshee's, 13,200 bytes, 00d74a35…. That hash was previously held by two
discs and the checklist called it "the only same-label pair in the table" —
both Core Design. Fire & Ice is Graftgold, published by Renegade. Three discs,
two studios, two labels.
The studio is only knowable from inside the executable. No ISO 9660 field
names a developer. XOR every byte with 0x1b and the disc says
Graftgold Presents, (c) Graftgold 1990,94, 68000 Game System, and — in
the seventh world file, unpacked — a fifteen-name staff roll ending
Fire and Ice AGA / T h e E n d. There is no $VER: anywhere on the game
binaries; the version is a bare banner in a developer diagnostic panel that
shipped, and it reads AGA 1.04.
Eight bitplanes that read as zero. BPLCON0 is $0611, whose bits 14-12
are 000. The fourth plane-count bit is BPU3 at bit 4, and the screen is an
eight-plane dual playfield — 320 pixels of foreground over a 960-pixel-wide
parallax layer, both four interleaved planes, both derived from the bitplane
modulos. The palette is 24-bit AGA loaded entirely from the copper
(LoadRGB4 and LoadRGB32 are called zero times), and playfield 2's sixteen
colours are reloaded 193 times, once per scanline of the play area.
Eleven absolute custom-chip writes in 92 KB of code, none of them to a
display register. The 68000 builds a copper list, points COP1LC at it and
stops touching the hardware.
Akiko is untouched in both of its roles, as predicted: zero pointer loads
of $00B80000, zero references to the $00B80038 C2P port, zero occurrences
of $C0DE0000. The two raw 00 B8 00 38 byte hits are at odd offsets inside
the chip data hunk, surrounded by F8/FC/E0 bitmask runs — planar sprite
mask, not an address.
The blitter answers boringly and the answer had to be checked. Minterm
$CA seven times, but with USEA on in all seven, which makes it the
ordinary masked bob rather than Guardian's cookie-cut (same minterm, USEA
off). BLTCON1 is written twice and is $0000 both times: no fill, no
descending blit, no line mode.
Twenty-two Red Book tracks, and the disc never reads its own TOC. Music is
played as CD_PLAYTRACK with io_Offset = index + 2, a constant baked into
the executable. The options panel offers Off / On Chip / On CD — the
player chooses between an in-house Paula replayer and the CD.
Two loud leftovers. A floppy disk-swap prompt (Please put volume <n> in any drive and press FIRE or SPACE.) and a manual-lookup copy protection
prompt (Please match the key at location: F*7), both in a 1994 CD32
executable. The Workbench script assigns Fire:Libs, a directory that is not
on the disc. And the default top high score is 68000 points to AEB — the
CPU part number and the programmer's initials, in a retail product.
One prediction came back negative and is reported as a result. The disc was
expected to be the set's first chance at an intra-volume ECS/AGA control. There
is no ECS asset set: one tile format, one copper template, one plane count. It
does, however, ship two whole executables, FirePAL and FireNTSC, built
2 minutes 8 seconds apart and differing in 23 scanlines and one string.
| Data track | 1,357 sectors in the image, 1,270 declared |
| Audio tracks | 22, 227,506 sectors, 50:33, all distinct |
| Share of a 74-minute CD | game 0.41 %, music 68.3 % |
| Files / directories | 29 / 8, of which 2 are empty |
| Bytes on disc | 2,035,183 (1.94 MB) |
| Bytes resident after unpacking | 2,906,735 (2.77 MB) |
| Bytes actually used | 2,898,523 — buffer slack 0.3 % |
| Files packed | 21 of 29, all PowerPacker 2.0 |
| Embedded streams a file census misses | 29 |
| Trailing unclaimed run | 232 sectors, LBA 1038-1269, all zero |
The 2.25-13.3 MB band, quoted all three ways as the checklist now requires: on disc 1.94 MB, resident 2.77 MB, used 2.76 MB. Read on disc this is below the band's floor and is the smallest in the set; read unpacked it is inside it near the bottom. That is the HeroQuest II pattern — compressed below, unpacked inside — and it is now two discs, so the on-disc reading of the band has two exceptions and the unpacked reading has none.
Fixed once, up front, because the two are not interchangeable. An AmigaDOS
hunk file's absolute constants are hunk offsets that LoadSeg relocates; the
same numbers read as file offsets point elsewhere.
FirePAL hunk 0 CODE 92,236 B body at file offset 0x000024
hunk 1 DATA 309,252 B body at file offset 0x018A40 (chip)
Every address in these documents says which it is. tools/relocs.py --at N
resolves one and, importantly, reports when a constant is not covered by a
relocation — in which case it is a literal and reading it as an address is the
error.
| 00-overview.md | What the disc is, and the headline results |
| 01-disc-image.md | PVD, sector map, the 232-sector run, audio |
| 02-filesystem.md | Files, two clocks, two empty directories |
| 03-boot-chain.md | Boot script, AskBoot, SetPatch, the icon |
| 04-compression.md | PowerPacker 2.0, nesting, the entropy result |
| 05-executables.md | Hunk layout, offset convention, PAL vs NTSC |
| 06-text-and-credits.md | The XOR 0x1b table, credits, version |
| 07-graphics.md | Copper list, AGA palette, derived geometry |
| 08-audio.md | Paula replayer, cd.device, Red Book |
| 09-resources.md | Cores, Graphics, Sets, the slot table |
| 10-hardware.md | Akiko, register scans, the blitter |
| 11-leftovers.md | What shipped that should not have |
| 12-open-questions.md | What is unsettled, with the measurement |
Python 3, standard library only except Pillow for the renderers and
capstone for dis68k.py.
isodump.py |
ISO 9660 volume, .TM pointer, directory tree, sector map |
inventory.py |
Every directory record with size, date and SHA-1 |
census.py |
Container magic and Shannon entropy per file |
pp20.py |
PowerPacker 2.0 decruncher (new) |
ppscan.py |
Embedded PP20 streams and windowed entropy (new) |
xortext.py |
Recovers the XOR key by scoring, then dumps text (new) |
textdump.py |
Obfuscated-text dump over a tree (new) |
copperfind.py |
Finds copper lists; computes BPU including BPU3 (new) |
copperpal.py |
Reconstructs 24-bit AGA palette state from LOCT pairs (new) |
coredir.py |
Parses and compares the world resource directories (new) |
geomguess.py |
Recovers bitmap stride from the data (new) |
tilesheet.py |
Renders a tile bank to PNG (new) |
dffscan.py |
Both $dff000 base-load forms plus the raw histogram (new) |
audiotracks.py |
Red Book census with duplicate detection |
relocs.py, regscan.py, akiko.py, copper.py, strings.py, dis68k.py |
carried from the sibling repositories |
rnc.py |
carried over; imported by census.py and inventory.py so the container table stays complete, though nothing on this disc uses RNC |
dis68k.py recomputes branch targets from the raw encoding, because
Capstone's M68K backend prints wrong-but-plausible constants on this code.
Every immediate, displacement and absolute address quoted in these documents
was re-read from the bytes.
python3 tools/isodump.py "<track 01>.iso" --extract _work/iso
python3 tools/inventory.py "<track 01>.iso" > notes/file-inventory.md
python3 tools/census.py _work/iso
python3 tools/pp20.py --dir _work/iso --out _work/unpacked
python3 tools/ppscan.py _work/unpacked/Cores/*.bin --window 8192
python3 tools/textdump.py _work/unpacked --key 0x1b
python3 tools/coredir.py _work/unpacked/Cores/*.bin
python3 tools/copperfind.py _work/iso/FirePAL --dump 0x1a60
python3 tools/tilesheet.py _work/unpacked/Sets/set1.bin set1.png
notes/ holds the generated output that the documents cite. _work/ is not
committed.
Dragonstone · Marvin's Marvellous Adventure · Prey · The Speris Legacy · Legends · Liberation · Microcosm · Gloom · HeroQuest II · Guardian · Banshee · platform notes