The archaeology. What shipped on a retail disc that was not meant to be read.
cdi_app contains a symbol called line_disassemble at 0x2fb8, and the next
symbol is at 0x3d22. 3,434 bytes of disassembler, in a 23,236-byte game
module — 14.8 % of the executable.
It is not a stub. The full 68000 mnemonic table is in the binary:
ADD AND BCD CHG CHK CLR CMP DIV EOR EXG EXT ILLEGAL JMP JSR LEA LINK
MOVE MUL NEG NEGX NOP NOT OR PEA RESET RTE RTR RTS STOP SET SUB SWAP
TAS TRAP TRAPV TST UNLK CCR SR USP
and so is the condition-code table, packed two characters to a slot in the order the 68000 encodes them:
T F HILSCCCSNEEQVCVSPLMIGELTGTLE
T F HI LS CC CS NE EQ VC VS PL MI GE LT GT LE
Around it is a complete exception handler:
init_catcher set_catcher call_catcher catch_display catch_dump_regs
make_error signal_catch critical critical_no_sys
critical_intro critical_game_int
AllRegs (74 bytes) currentpc in_debugger except_catch
with the exception names it prints:
Bus Address Illegal Divide by zero CHK TrapV
Privilage violation Trace Line A Line F Format Error
Uninitialized Vector Interrupt Spurious Interrupt Unknown %04x
Privilage. Two misspellings on one disc, in two different files, by two
different people — the credit roll says Interacive and the debugger says
Privilage.
AllRegs is 74 bytes: sixteen 32-bit registers is 64, plus PC (4), SR (2) and
USP (4) is 74 exactly. The handler dumps every register and disassembles the
instruction at the fault address.
/nvr/CATCHER_ON
"not "
"catcher %sinstalled\r"
"0.52"
"Exception Catcher %s "
/nvr is the CD-i player's non-volatile RAM, the same place the save file
lives. The game checks for a file called CATCHER_ON there, and if it finds
one it installs the debugger and prints catcher installed; if it does not, it
prints catcher not installed.
So the crash handler is not dead code that the linker failed to strip. It is a
deliberately retained development tool with a runtime switch, and the switch
is on the player rather than on the disc — which means it survives pressing and
can, in principle, be turned on by anyone who can write to their player's NVRAM.
The catcher even has its own version number, 0.52.
cdi_loader and cdi_invaders carry a second such switch, /nvr/DISPLAY_ON.
What it does is not readable from the strings.
The save file /nvr/Apprentice holds a high-score table, and its factory
contents are compiled into cdi_app as the initial value of nvr_buffer at
file offset 0x5690:
u16 u16 00 01 00 01
then 20 x
char[2] rank, "01".."20"
u32 score, big-endian
char[6] name
244 bytes, ending exactly where the string music.rtf begins, which is what
closes the reading.
python tools/cdinvr.py
01 200,000 | LUKE | 11 100,000 |DENNIS|
02 190,000 | TIM | 12 90,000 | OLU |
03 180,000 | ARJEN| 13 80,000 |SIBBY |
04 170,000 |STEFAN| 14 70,000 |DANION|
05 160,000 | LUC | 15 60,000 | SAM |
06 150,000 |JOOST | 16 50,000 | MARC |
07 140,000 |JORIS | 17 40,000 | DAVE |
08 130,000 |JOOP | 18 30,000 |ELLIS |
09 120,000 | JSB | 19 20,000 | RIK |
10 110,000 | ROB | 20 10,000 | ERIC |
Scores descend by exactly 10,000 a place, so they are placeholders. The names are not.
Cross-reference them against the rest of the disc and the top five light up:
python tools/cdinvr.py cross
LUKE bibliographic, volume:data_preparer
TIM bibliographic, volume:data_preparer
ARJEN volume:data_preparer
STEFAN -- nowhere else on the disc
LUC volume:data_preparer
JOOST bibliographic
The volume descriptor's data-preparer field reads Tim, Luke, Luc and Arjen. Four names, and four of the top five scores. JOOST at sixth is
Joost Egmond, the composer, who is in the credits. STEFAN at fourth is in
neither.
The credit roll names three people. The high-score table names twenty, in what is plausibly seniority order, and fifteen of them appear nowhere else on the disc at all. It is the closest thing to a staff list The Vision Factory left on this pressing, and it is in the one place a player would see it — the first screen after the attract mode.
(What is measured: the twenty names, their order, and which of them occur in other fields. What is inferred: that the order means anything. Someone had to type twenty names into a table and the obvious orders are seniority, desk position or whoever was in the room.)
The last game assets written before the volume was closed, at 14:56:32 and
14:56:33 on 1994-10-19, are map7_1.dat through map7_5.dat.
Every other map file on the disc is 116,736 or 118,784 bytes and is a
three-chunk container: layout, tile sheet, palette. These five are 16,384
bytes each, with no container header at all — a bare u16 array, the same
period-6 layout as a real map's chunk 0, with:
- every attribute word zero, where a real map has values in
0x40–0x62 - 1,453 to 1,640 non-zero bytes out of 16,384, so 82 % empty
- five different files, so they are not copies of one stub
And there is no level7.dat, no intro7.dat and no l7_em.dat. Levels one to
six each have an intro, a level file, three maps and an enemy file. Level seven
has five maps and nothing else.
cdi_app names them: the string map7_%d.dat is in the binary, immediately
after cr_gfx.dat and immediately before levelb.dat. So the game loads them.
Five rather than three also breaks the pattern — every other level has exactly
three maps.
What they are is not determinable from the disc. A seventh level cut down to its maps, an endgame sequence that reuses the map loader for something that is not a level, or a test fixture that was still being edited an hour before mastering are all consistent with the bytes. It is the single most suggestive thing on the volume and it does not resolve.
The block loader prints:
start_blockload(): Warning: aborting current blockload
loading block rtf from path %d
loading block rtf %s
blockload: null a_list%s; skipping
start_blockload(): srqcmem(): %R
start_blockload(): cd_play(): %R
blockload finished
blockload aborted
blockload: load error (%R)
blockload: null blocknext, signal ignored
%R is Microware's error-code format. function(): call(): %R is the shape of
a message written for a terminal, on a console that has none.
cdi_invaders goes further and prints its own configuration:
cdi_config at %x
cdi_config module has bad magic
compat=%d startline=%d pal_flag=%d hz=%d (%dHz display)
chdir %s: %R
chxdir %s: %R
dc_crlct A dc_crlct B dc_crfct A dc_crfct B
dc_crlct A2 dc_crlct B2 dc_exec
The dc_* set are MCD212 display-control register names — line control table
and frame control table, per plane — printed by name.
cdi_loader contains:
APPL00/CMDS/cdi_appl00
There is no APPL00 directory on this volume; all four applications are in one
/CMDS/. The loader writes decimal digits into that template, so it supports
up to a hundred applications, each in its own APPLnn directory, and
cdi_start and cdi_invaders carry the matching chdir / chxdir / %s/CMDS
strings.
This is the studio's general-purpose CD-i loader, carried onto a disc that needed one directory and four slots. It is the kind of leftover that tells you about the tooling rather than about the title.
cdi_factory, the module that plays The Vision Factory's own logo, contains:
DThe Vision Factory Leader v0.55
Shipped at 0.55, alongside an exception catcher at 0.52. Neither reached 1.0.
/CMDS/philips.rtf is 625 sectors of Form 2 real-time data — RL7 video, DYUV,
Level B stereo audio, three channels — and its directory entry has file
number 0. Its sibling factory.rtf, which is the same kind of file, has 1.
Every other real-time file on the disc is marked correctly. The one that was supplied by the publisher rather than built by the studio is not.
Worth recording, because the absence is informative:
-
No all-zero files, on a disc using 19.9 % of its capacity. Room does not produce dead files.
-
No abandoned localisation. Everything on the disc is English, and there is only one text file per role.
-
No unreferenced assets, and the check closes. Twenty filename templates are embedded across the six modules:
con_gfx.dat cr_gfx.dat demo%d.dat go_gfx.dat hi_gfx.dat int_gfx.dat intro%d.dat invaders.dat l%d_em.dat level%d.dat levelb.dat map%d_%d.dat map7_%d.dat tit_gfx.dat treas1.dat endmus.rtf factory.rtf music.rtf othermus.rtf philips.rtfExpand them and they match all 56
.datand.rtffiles on the volume, with none left over in either direction. Not one asset on this disc is unreachable, and not one name in the binary points at a file that is not there. -
No personal data. Twenty first names and one address-free credit roll.
-
No SCCS keywords. Merlin's three text files all open with
@(#); none of these three do. -
No MPEG, no Digital Video cartridge requirement.