Nine hunk files: four on the disc as files, five inside the packed level blobs.
| File | Hunks | Code | Alloc | Mem | Reloc32 |
|---|---|---|---|---|---|
/Legends |
2 | 37,848 | 37,848 + 1,016 BSS | chip | 1,020 |
/InstallHD |
3 | 8 | 8 + 4,076 DATA + 1,628 BSS | any | 113 |
/c/SetPatch |
2 | 6,640 | 6,640 + 32 DATA | any | 154 |
/c/Assign |
1 | 3,184 | 3,184 | any | 0 |
/c/ShutDown |
1 | 44 | 44 | any | 1 |
Level1/Legends.Pak |
1 | 572,984 packed | 1,531,604 | chip | 0 |
Level2/Legends.Pak |
1 | 506,560 packed | 1,469,276 | chip | 0 |
Level3/Legends.Pak |
1 | 328,444 packed | 1,143,092 | chip | 0 |
Level4/Legends.Pak |
1 | 337,116 packed | 1,142,964 | chip | 0 |
Level5/Legends.Pak |
1 | 298,964 packed | 1,085,840 | chip | 0 |
None of them carries a HUNK_SYMBOL table and none carries HUNK_DEBUG, so
there are no symbols to read anywhere on the disc.
One code hunk, 37,848 bytes, entirely in chip RAM, 1,020 relocations, and
the whole thing is compiled to absolute addressing: move.l $1aa.l,d0 rather
than register-relative. Entry point is jmp $9330 at offset 0, jumping over a
table of hardware register/value pairs that sits immediately after it.
It identifies itself, once, at hunk offset 0x41AD:
Legends A1200 V1.00
A1200, on a CD32 disc. The five level executables disagree with it -- see below.
tools/m68k.py finds sixty 4E AE instructions. Sorted, they are a small
and very deliberate set:
| Call | Times | On |
|---|---|---|
OldOpenLibrary (-552) |
3 | exec |
Open (-30), Read (-42), Seek (-66), Close (-36) |
1 each | dos |
OpenDevice (-444), DoIO (-456), SendIO (-462), AbortIO (-480), WaitIO (-474) |
1-2 each | exec |
OwnBlitter / DisownBlitter / WaitBlit |
5 / 5 / 4 | graphics |
AllocMem (-198), FreeMem (-210) |
1 each | exec |
One Open and one Read in the entire program. There is exactly one file
loader, and it is the routine at hunk 0x749E:
749E bsr lock_screen
74A2 jsr DisownBlitter(GfxBase) ; give the blitter back to the OS
74AC move.l #name,d1
74B2 movem.l d1-d7/a0-a6,-(a7) ; save everything: the game runs
74B6 move.l #1005,d2 ; with its own register conventions
74BC jsr Open(DOSBase) ; MODE_OLDFILE
74D6 jsr Seek(file, 0, OFFSET_END)
74EA jsr Seek(file, 0, OFFSET_BEGINNING) -> d3 = the file's size
7526 jsr Read(file, heap, d3)
753C jsr Close(file)
7542 movem.l (a7)+,d1-d7/a0-a6
754C jsr OwnBlitter(GfxBase) ; take the blitter back
7556 jsr WaitBlit(GfxBase)
The size comes from seeking to the end and then to the start: the second
Seek returns the previous position. The game owns the blitter
permanently and hands it back to the operating system only for the duration
of a DOS call.
At hunk 0x728C:
move.l #$200000,d6 ; ask for 2 MB
.retry:
subi.l #$400,d6 ; ... 1 KB less each time
move.l d6,d0
move.l #MEMF_CHIP|MEMF_CLEAR,d1
jsr AllocMem(SysBase)
tst.l d0
beq.b .retry
The largest chip block it can get, up to 2 MB, in 1 KB steps. On a CD32
that is essentially all of chip RAM. Everything the game loads goes into that
block through a bump allocator ($1AA is the pointer, $1AE the size of the
last thing loaded).
At hunk offset 0 -- overwriting its own entry point once the game is running -- the program builds a table of up to 64 entries tagged with four-byte names:
'RAMC' chip block 'RAMP' public
'RAMF' fast 'RAMD' (a fourth class)
The lookup at 0x7108 scans the table for the tag RA M, sums the size
field at +6 of each match, and a second routine at 0x7128 finds an entry by
full tag and returns its base and length. It is a 64-slot memory directory
built by hand on top of one AllocMem.
The only CD32-specific subsystem in the program. At hunk 0x47F6:
lea cd_device_name,a0 ; "cd.device"
lea ioreq,a1
moveq #0,d0 ; unit 0
moveq #0,d1
jsr OpenDevice(SysBase)
...
move.w #35,IO_COMMAND(a1) ; read the table of contents
move.l #100,IO_LENGTH(a1)
jsr DoIO(SysBase)
and playback, at 0x812A:
jsr AbortIO(SysBase) ; stop whatever is playing
jsr WaitIO(SysBase)
move.l 8(a0),d1 ; end of the wanted track
move.l 2(a0),d0 ; start of the wanted track
sub.l d0,d1 ; length
move.w #39,IO_COMMAND(a1) ; play
move.l d0,IO_OFFSET(a1)
move.l d1,IO_LENGTH(a1)
jsr SendIO(SysBase) ; asynchronous -- the game does not wait
The game asks cd.device for the TOC, reads a track's start and end out of
it, and plays by absolute position rather than by track number. Stopping is
AbortIO plus WaitIO. That is the whole of the CD32 audio support: about
120 bytes of code.
At hunk 0x4798 the front end saves INTENA, INTREQ and CIA-B's control
register B, installs its own level-2 autovector at $68, enables every
DMA channel except audio (DMACON = $87F0), and runs. On exit it clears
INTENA and DMACON with $7FFF, restores the saved values and puts the old
vector back. ciaa.resource and ciab.resource are both named in the string
table, so the CIA timers are reserved properly rather than simply grabbed.
AmigaDOS stays alive underneath all of it -- it has to, because the only way
the game loads a file is dos.library.
The front end never calls LoadSeg -- there is no jsr -150(a6) anywhere
in it -- and it loads five hunk files anyway. At hunk 0x6E02 it implements
the loader itself:
6E02 lea $80,a5 ; the loader's state block
6E08 bsr $7018 ; point the stream reader at the file
6E0C .next: bsr $702C ; read a longword
6E10 subi.w #$3E7,d0 ; HUNK_UNIT is the first entry
6E14 add.w d0,d0
6E16 add.w d0,d0
6E18 jsr $6E28(pc,d0.w) ; a 15-entry dispatch table
6E1C bpl.b .next
The table runs from HUNK_UNIT (0x3E7) to 0x3F5. UNIT and NAME are
skipped, CODE and DATA share a handler, BSS, RELOC32 and END have
their own, HEADER has its own, and RELOC16, RELOC8, EXT, SYMBOL and
DEBUG all land on the same rejection stub.
The reason for writing it out is in the CODE/DATA handler:
6E7E bsr $6F7E ; allocate the hunk; d0 = its base
6E84 subi.l #$20,d0 ; 32 bytes below it
6E8A movea.l d0,a0
6E8C move.l 8(a5),d1 ; the longword count out of the stream
6E90 .copy: bsr $702C
6E94 move.l d0,(a0)+ ; the *packed* body, landing 32 bytes low
6E98 bne.b .copy
6E9C lea -$20(a0),a1
6EA0 bsr $703E ; unpack, in place, into the hunk
followed, only then, by the ordinary RELOC32 loop:
6EB8 move.l $C(a5,d0.w),d2 ; base of the target hunk
6EC2 .l: bsr $702C ; an offset
6EC6 add.l d2,(a0,d0.l) ; relocate
6ECC bne.b .l
Decompression has to happen between reading the hunk body and applying the
relocations, and AmigaDOS does those two things back to back. The level
files' relocation offsets run as high as 940,974 while their packed bodies are
only 572,984 bytes, so LoadSeg would be writing into memory that the
decruncher had not filled yet and then having its work overwritten. Krisalis's
answer was to re-implement LoadSeg in about four hundred bytes with the
decruncher spliced into the middle of it. The 32-byte back-off is so that the
.Pak header sits just below the hunk base and the unpacked data lands exactly
on it.
The decruncher is called from ten places in the front end, so it is the general asset codec as well as the level loader.
Each LevelN/Legends.Pak is a well-formed hunk file -- one HUNK_CODE
holding a packed stream, a HUNK_RELOC32 block, and HUNK_END:
| Level | Reloc32 entries | Offsets | Hunk allocation |
|---|---|---|---|
| 1 | 3,982 | 2 - 940,974 | 1,531,604 |
| 2 | 3,726 | 2 - 925,664 | 1,469,276 |
| 3 | 2,863 | 2 - 787,842 | 1,143,092 |
| 4 | 3,324 | 2 - 762,134 | 1,142,964 |
| 5 | 2,443 | 2 - 742,448 | 1,085,840 |
The first relocation on each is at offset 2 -- the operand of the jmp the
unpacked blob starts with. The front end locks the file, Examines it for
fib_Size, places it sixteen kilobytes below the top of its heap, runs its
own loader over it, and then calls the level with jsr (a0) and gets control
back when the level is done.
Unpacked, each blob starts with jmp and is therefore code, not data:
Level1 4E F9 000E 5212 jmp $e5212
They are five builds of one program. Every one of them names:
Legends 1200/CD32 Version 0.99
cd.device intuition.library graphics.library
trackdisk.device dos.library arp.library
ciaa.resource ciab.resource DiskDirectory
Version 0.99, on a shipped disc, against the front end's V1.00. And
trackdisk.device, arp.library and DiskDirectory are floppy-era names
that survive into a CD build -- arp.library is the AmigaDOS Replacement
Project library from the late 1980s.
Each level also names exactly one floppy volume, and it is a different one per level. That, and what it reconstructs, is in 11-leftovers.md.
The odd one out. Three hunks: a HUNK_CODE of 8 bytes, a HUNK_DATA of
4,076, and 1,628 of BSS. The code hunk is a single relocated jmp into the
data hunk, so the whole program lives in a hunk marked as data. It is the
A1200 hard-disk installer, and it is documented in
11-leftovers.md.