Reverse-engineering notes on Guardian (Acid Software, 1994) for the Amiga
CD32 — a third-person polygonal shooter, mastered 1994-08-04, that fills its
triangles with one Blitter cookie-cut per scanline straight into four
interleaved bitplanes, drives a 24-bit per-scanline sky from a 7,144-byte
copper list, reads all seven pad buttons on both ports with no
lowlevel.library, and touches Akiko not at all.
This repository documents a 1,193-sector volume — 0.40 % of a CD, with 41.3 %
given to twelve Red Book tracks — holding 61 files, none of them compressed;
a 72-byte freeanim that is the smallest wrapper on the format; a
startup-sequence.info and a setpatch that are byte-identical to Gloom's; a
twelfth zone's worth of assets the shipped zone table cannot reach; and an
A1200 floppy release 24 of whose data files are byte-identical to this disc's.
Documentation only. No game asset, no extracted art, no audio and no
executable code is committed here. The tools in tools/ reproduce every
figure, table and image in these pages from your own legally obtained copy.
| Doc | Contents |
|---|---|
| 00-overview.md | The disc in one table, and the hunk/file offset convention |
| 01-disc-and-filesystem.md | ISOCD 1.04, an empty preparer, the ninth identical .TM block, and no front gap |
| 02-timestamps-and-dating.md | A seven-second copy of 60 files, and one file from the MS-DOS epoch |
| 03-boot-chain.md | Three lines in the root, a .bak that names sw, and an icon shared with Gloom |
| 04-executable.md | 3 hunks, 5,113 relocations, nothing compressed — and nine RLE images inside the program |
| 05-display-and-akiko.md | Akiko measured to zero, eleventh of eleven; a six-plane panel over a four-plane 3D view |
| 06-the-3d-engine.md | One Blitter cookie-cut per polygon scanline, all four planes at once |
| 07-data-formats.md | The zone table, dither sheets, 4,096-entry 24-bit skies, sprite banks |
| 08-audio.md | Twelve Red Book tracks, every one reachable, played out of the disc's own TOC |
| 09-input-and-saves.md | Seven buttons, two ports, no lowlevel.library; a save that stores 8 and reads 80 |
| 10-two-releases.md | 24 files byte-identical inside the A1200 data floppy, and what that says about compression |
| 11-archaeology.md | PIRATES FUCK OFF, a live hElP canary, a paint file, and a zone nobody can load |
| 12-open-questions.md | Twelve things unresolved, with the measurement beside each |
| notes/file-inventory.md | All 61 files with LBA, size, timestamp, entropy, SHA-1 and A1200 match |
| cd32-platformnotes-doc | Platform checklist — what to look for on any CD32 or CDTV disc. Shared by every Amiga CD pipeline in this series; this repository does not keep a copy |
Raw tool output — the ISO listing, the sector map, the entropy census, the hunk
and relocation tables, the register and LVO histograms, the copper list, the
Akiko scan, the audio-track measurements, the floppy listings, the cross-release
diff and the string dumps — is in notes/. The predictions written
before any measurement are in
notes/predictions.md, and doc 12 scores them.
This pipeline writes its predictions down before it measures, and for the first time in eleven discs the prediction was positive: Guardian is CD32-first, it has a genuine real-time polygon renderer, and the argument that killed every earlier candidate ("the same binary has to run on an A1200") did not apply when the code was written.
$00B80038 $00B8003C $C0DE0000 pointer loads
raw image 0 0 0 0
all 61 extracted files 0 0 0 0
The prediction was wrong, and the reason is the interesting part. The
question that matters is not "does it touch $B80000" but where does the frame
end up — and here the frame is already bitplanes. There is no chunky buffer
anywhere in the program, no bset/bclr plotter, and no BLTCON1 write of any
kind. A chunky-to-planar converter would have nothing to convert.
The rasteriser is set up once per polygon and then issues one Blitter operation per scanline, which paints all four bitplanes at the same time because the bitmap is interleaved:
BLTCON0 = $07CA USEB|USEC|USED, minterm D = (A AND B) OR (NOT A AND C)
BLTADAT = $FFFF A is a constant; only BLTAFWM/BLTALWM modulate it
BLTBPT = the dither pattern for this face's shade
BLTCPT = BLTDPT = the screen
BLTBMOD = BLTCMOD = BLTDMOD = 40 - width
BLTSIZE height = 4 <- the four interleaved planes of one row
BLTAFWM and BLTALWM come out of a 43,520-byte lookup table indexed by the
span's ends, so the inner loop turns an X coordinate into a Blitter set-up with
four table reads and no shifts. The routine steals a7 to hold that table's
base, saving the stack pointer to a variable first, and runs with no usable stack
at all.
Two details worth carrying. Every face has two colour bytes, one per facing —
the winding test does not discard the back face, it does sge.b and then picks
the other byte, and a colour byte of zero means "do not draw this side". And the
dither pattern pointer advances by ±160 bytes per scanline, which is exactly
the row stride, so the dither is screen-aligned rather than polygon-aligned
and two adjacent faces of the same shade tile without a seam.
raster 48 – 86 39 lines the HUD panel: 320 x 38, SIX bitplanes, interleaved
raster 87 – 286 200 lines the 3D view: 320 x 200, FOUR bitplanes, interleaved
239 lines = DIWSTOP 287 - DIWSTRT 48
Four independent witnesses agree on the 3D view's geometry: BPL1MOD = 200, a
bitplane-pointer loop with moveq #3 and a 40-byte step, a mulu.w #$a0 in the
rasteriser, and a 32,000-byte buffer size. The panel's is moveq #5, a 40-byte
step and a 9,120-byte stored image whose own header reads 320, 38, 6.
The colour is genuine 24-bit AGA and it never touches registers 0–127:
BPLCON4's BPLAM = $80 XORs the six-plane index into the range 128–191,
which is exactly the two BPLCON3 banks the copper list writes, each twice with
LOCT toggled. LoadRGB4 and LoadRGB32 are both never called — the third
zero-zero disc in this series.
Each data/splits/splitNN is exactly 16,384 bytes: 4,096 entries of two
big-endian words — an AGA LOCT pair. Read as 12-bit $0RGB they are noise;
read as LOCT pairs they are gradients that change by one low nibble at a
time, which is the whole reason for the 24-bit path. The copper list pours them
out at two colours per scanline over roughly 205 lines, from a ~205-instance
template of a 28-byte unit the game patches every frame.
files at entropy >= 7.0: 0 of 61
RNC / IMP! / ATN! / PP20 / XPKF / CrM2 / LZX / SQSH: 0 hits in 2.75 MB
MMD0..3 / M.K. / 8SVX: 0 hits. FORM: one, and it is an artist's paint file
The prediction — Guardian is the first title here whose floppy SKU exists but arrives later, so at master time there was no floppy loader to inherit and the rule predicts no compression — is confirmed. And the A1200 release turns the correlation into a controlled experiment: 24 of this disc's data files are byte-identical inside the A1200 data floppy, laid out in eleven 24,832-byte groups, while the sprite banks — 1.35 MB that will not fit on 880 KB — are packed into the floppy's second half at entropy 7.9. One data set, two media, packed on one and raw on the other.
The split files appear on the floppy in the order 1, 2, 4, 9, 8, 5, 11, 3, 6, 7, 10, which is exactly the permutation in the CD32 disc's own zone table. Two files, two build steps, two media, agreeing on an arbitrary eleven-element permutation.
s/startup-sequence.bak — the previous boot script, pressed beside the live
one — runs sw m1 f where the live one runs game m1 f. The A1200 release's
executable is called sw. So a build called sw existed before 1994-08-04 and
had already been renamed here.
That is as far as the disc goes, and this repository says so. There is no
$VER: anywhere in /game, the executable's own file record carries the
MS-DOS epoch, and the only A1200 images available are a cracked December 1994
release whose dates are the cracker's. Which SKU shipped first is item 1 of
12-open-questions.md.
SHA-1 8b6cf0011d6a55754c8b762eec1e808576e6c246 10,964 B /setpatch 40.3 (10.5.93)
SHA-1 6bb943b7dac227070ce10d44f82a9ed85f51a74e 396 B /s/startup-sequence.info
Both are byte-identical to Gloom's — Black Magic Software / Guildhall, ten
months later — and the setpatch is also byte-identical to the one on Guardian's
own A1200 floppy. The icon is a Workbench project icon on the boot script with
DefaultTool = blitz2:blitz2: two unrelated studios shipping the same Blitz
Basic 2 project icon, byte for byte. On Gloom the credits name Blitz; on
Guardian this icon is the only evidence it was in the build chain at all.
(C) 1994 ACID SOFTWAREimmediately followed byPIRATES FUCK OFF, at the very top of the code hunk, referenced by nothing.hElP—$68456c50— written at the last longword of seven chip allocations, and verified at run time by sevencmpi.lsites. A working buffer-overrun canary, still armed in the retail build.- A twelfth zone's worth of assets nobody can load:
split12,spr12(the largest sprite bank on the disc),sprhead12andmap12, plusmap00,map99,dither00anddither98— 179,796 bytes, 8.0 % of the disc, against a zone table with eleven records. dither99.iff— the artist's Deluxe Paint working file for the dither sheet, with itsGRABhot-spot and sixCRNGcolour-cycle ranges intact, pressed beside the stripped binary the game actually reads. It is the onlyFORMon the disc.WASandGON— four printable bytes each, sitting immediately in front of the two strings the save code does address, and addressed by nothing.- A title screen that draws
(C)1994 ACID SOFTWAREin pixels and, across a planet, a hand-lettered signature ending in94— the only personal mark on a disc with an empty preparer field, no credits screen and no name in any string.
python tools/isoread.py <track01.iso> -x _work/files # extract the volume
python tools/sectormap.py <track01.iso> # the 32-sector run
python tools/census.py _work/files # entropy, zlib, magic
python tools/akiko.py <track01.iso> _work/files/* # the headline measurement
python tools/relocs.py _work/files/game --at 0x2580 # hunk/file offsets
python tools/regscan.py _work/files/game # both register scans
python tools/copperdump.py _work/files/game 0x63674 # the copper list
python tools/rleimg.py _work/files/game --scan # the nine images
python tools/rleimg.py _work/files/game 0x55ebe t.png # decode and render one
python tools/planarimg.py _work/files/game 0x53fce t.png # the four raw ones
python tools/strdump.py _work/files/game 5 # strings with offsets
python tools/adfread.py <disk1.adf> -x _work/a1200 # the other release
python tools/audiotracks.py *.wav # the twelve tracks
Every tool is plain Python 3 with no dependencies except capstone, which only
m68kdis.py needs. The Capstone M68K backend prints wrong-but-plausible
immediates on this code; every constant quoted in these pages was re-read from
the raw byte column, and tools/relocs.py --at was used to decide which hunk each
address belongs to.