Skip to content

Latest commit

 

History

History
212 lines (163 loc) · 9.68 KB

File metadata and controls

212 lines (163 loc) · 9.68 KB

06 — Executables

python3 tools/hunkinfo.py <file>notes/hunkinfo.txt.

24 files begin 0x000003F3. Seven are Commodore commands in /c, four are the shipped libraries, three are font files (which are hunk files by design), and ten are game programs.

Structure and relocation density

Step 36: a file beginning 0x000003F3 need not be a program, and the tell is the ratio of relocations to code bytes.

program bytes CODE DATA BSS relocs per KB of code
SuperFrog/SuperFrogCD 363,744 211,476 116,040 1,230,088 8,996 43.56
SuperFrog/SFIntro 751,852 53,656 690,968 403,120 1,730 33.02
s/T17Menu 125,368 1,452 123,620 0 55 38.79
Pool/POOL 158,708 132,328 20,028 0 1,569 12.14
SuperFrog/T17Man 24,172 22,732 980 0 90 4.05
SuperStardust/MAINDEMO1 528,316 506,968 0 1,001,192 5,318 10.74
SuperStardust/MAINDEMO2 528,524 507,188 0 1,001,192 5,315 10.73
SuperStardust/TUNNELDEMO 1,479,404 1,475,712 0 278,016 904 0.63

Two entries stand out at the two ends.

TUNNELDEMO at 0.63 relocations per KB is seventy times below the disc's own densest program and is the cd32rez signature from Gunship, on a different disc, a different studio and a different country. All 904 relocations fall in hunk offsets 0x80x90F8, that is in the first 2.51 % of a 1,475,712-byte "code" hunk. The remaining 1.44 MB is data living in a CODE hunk. Independent confirmation: 27 IFF 8SVX samples are embedded in it, the largest at resident offset 1,378,212 (08-audio.md) — deep inside the region with no relocations.

So step 36 now has two instances across two discs, and the diagnostic is the ratio, not the magic.

T17Menu at 1,452 bytes of code against 123,620 of data is the same observation from the other side: a program that is 98.8 % data. Its 55 relocations all fall in hunk offsets 0x2C0x566, inside the code hunk. It is a real program — it has enough relocations for its size — but almost all of the file is the menu's artwork.

SuperFrogCD at 43.56 per KB is the normal-program baseline for this disc, and its relocations span 0x20x33A06, i.e. the whole code hunk, which is what a real program looks like.

No re-implemented LoadSeg

Two discs in the set (Legends, Universe) re-implement LoadSeg. Checked here: every non-packed program file on this disc begins 0x000003F3, and no file whose first longword is not 0x000003F3 and which is not a validated packed stream has a first longword that is a valid in-file offset. Nothing on this disc loads its own executables.

The dispatch table, and what it is not

python3 tools/dispatch.py over all 311 resident pieces (notes/dispatch.txt): 309 pieces return zero, two return sevenSuperFrogCD and SFData/PARTF, which turn out to hold two builds of the same engine.

The site, disassembled from the bytes at resident offset 0xEB70 of SuperFrog_SuperFrogCD+gap0.bin:

00eb68  43 fa 0c 70    lea.l   $f7da(pc), a1     ; table of longword deltas
00eb6c  47 fa 0c 78    lea.l   $f7e6(pc), a3     ; the handler table
00eb70  42 a8 00 0e    clr.l   $e(a0)
00eb74  d5 d9          adda.l  (a1)+, a2         ; step a2 to the next record
00eb76  30 12          move.w  (a2), d0          ; read the record's first word
00eb78  02 40 00 3f    andi.w  #$3f, d0          ; low six bits select a handler
00eb7c  d0 40          add.w   d0, d0
00eb7e  d0 40          add.w   d0, d0            ; x4
00eb80  42 87          clr.l   d7
00eb82  4e b3 00 00    jsr     (a3, d0.w)

Read from the raw bytes, not from a disassembler's rendering of the constants (the checklist's warning about Capstone on this code applies; every constant above was re-read from the encoding).

Is this the Universe-style bytecode VM? No, and the difference is worth stating precisely because the encoding is superficially the same shape.

Universe Superfrog
index derivation neg.b + subq + andi #$ff — the complement of the opcode andi.w #$3f — the opcode's low six bits
what the pointer walks a byte stream, advanced by each instruction's own length records, advanced by a fixed table of longword deltas read with (a1)+
program counter explicit, saved and restored none — a1 is a compile-time constant list
control flow a loop over the stream straight-line: adda, dispatch, test, adda, dispatch, …

a1 is the giveaway. In a VM the step size comes from the data being interpreted; here it comes from a table baked into the executable, so the sequence of records visited is fixed at build time. This is a 64-way type dispatch over object records, not an interpreter.

That is the alternative Gunship pointed at — game structure living in a data format rather than in bytecode — appearing on a platformer exactly where the checklist said to look for it.

P9 is therefore half right: no VM of the Universe form, and the count is reported rather than the absence merely asserted.

How much of the vocabulary is real

Five tables, 0xED0E, 0xEE0E, 0xEF0E, 0xF7E6, 0xF8E6, each 64 entries of four bytes, every entry a bra.w — 320 slots. python3 tools/jumptable.py:

table distinct targets default's share
0xED0E 17 48/64 = 75 %
0xEE0E 19 46/64 = 72 %
0xEF0E 18 47/64 = 73 %
0xF7E6 2 62/64 = 97 %
0xF8E6 2 62/64 = 97 %

Across all five: 24 distinct handlers over 320 slots, and one address — 0x00F9E6 — takes 265 of the 320, or 82.8 %. So the opcode space is 64 wide and the implemented vocabulary is 23 handlers; four fifths of every table is a shared do-nothing default. The two 97 % tables are near-empty variants selected by different object state.

That is the "empty slot" measurement the checklist asks for on string tables, applied to a jump table: the slots are there, most of them are unused, and the count of unused ones is the result.

PARTF carries a second build of the engine

SFData/PARTF unpacks to a 209,504-byte piece that holds the same seven dispatch sites at a constant offset of 0x728 below SuperFrogCD's, and the code at the site is byte-identical; over 4 KB from the dispatch point the two agree in 3,802 of 4,096 bytes (92.8 %). So the particle/intro payload PARTF embeds a variant build of the game engine rather than referencing it.

That is 209,504 resident bytes of engine stored twice, and it is invisible to a file-level duplicate check because one copy is inside a compressed container.

Two builds, diffed properly — a method note for open item 23

MAINDEMO1 and MAINDEMO2 are the case open item 23 asks about, and they show why the naive comparison is useless.

Byte level. First difference at file offset 23, inside the hunk size table: 01 EF 16 against 01 EF 4D, i.e. 126,742 against 126,797 longwords. End-align the two code hunks and zero bytes match. A byte diff reports two unrelated files.

Content level. The BSS hunk is identical (1,001,192 bytes both). The code hunks are 506,968 and 507,188 — a 220-byte difference — and diverge at code offset 0x24, which is lea $00016FFC,a0 against lea $000170D2,a0: an address shifted by 214 bytes. Everything after that is shifted too, so nothing matches.

Relocation-masked. Zero every longword covered by a RELOC32 entry (5,318 in MAINDEMO1, 5,315 in MAINDEMO2) and compare again:

the two code hunks agree for the first 6,083 bytes
then:  A  33 fc 00 01 ....   move.w #$0001,<relocated address>
       B  33 fc 00 02 ....   move.w #$0002,<relocated address>

The first real difference between the two builds is an immediate 1 against an immediate 2, almost certainly the demo number, at a point where the naive diff had already given up 6,059 bytes earlier.

So the procedure for the next disc: mask the relocations before diffing two builds. It turns "unrelated" into "one word". It is not perfect — non-relocated PC-relative displacements and absolute literals still shift, which is why the masked images agree for 6 KB rather than for the whole file — but it is the difference between a useless answer and a specific one.

T17Man and T17Man.lace — ten bytes

The cleanest two-build pair in the set. Both 24,172 bytes, same length, so none of the size-table arithmetic above applies.

They differ in 10 bytes across 2 runs:

0x00039E  9 bytes   "9.8.94)\0)"   vs   "10.8.94)\0"     the $VER: date
0x0015E1  1 byte    0x00           vs   0x04

Read the second from the encoding. At file offset 0x15DE (CODE hunk starts at file offset 36, so hunk offset 0x15BA):

2f 3c 80 00 00 32    move.l #$80000032,-(sp)     ; SA_DisplayID
2f 3c 00 02 90 00    move.l #$00029000,-(sp)     ; T17Man
2f 3c 00 02 90 04    move.l #$00029004,-(sp)     ; T17Man.lace

$80000032 is SA_DisplayID (TAG_USER + 0x32), pushed with its value alongside $80000029 (SA_Colors) and the rest of an OpenScreenTagList tag array being built on the stack.

$00029000 is PAL_MONITOR_ID | HIRES = 0x00021000 | 0x8000. $00029004 adds LACE = 0x0004. So:

  • T17Man opens PAL:HighRes
  • T17Man.lace opens PAL:HighRes-Interlaced

One byte, and it is a DisplayID, not a BPLCON0 bit. Neither build is NTSC — open item 23 asks for a PAL/NTSC pair and this is a PAL/PAL-interlaced pair, which is a different axis. The disc ships both and the manual system picks between them; nothing here adapts to the machine's video standard.

The version strings date the two a day apart, 9 and 10 August 1994, so the interlaced build is the later one.