Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cdi-theapprentice-doc

Reverse-engineering notes on The Apprentice (The Vision Factory / Philips Interactive Media International, 1994) — the CD-i's best-regarded action game, and a disc that turns out to be a very different object from the four CD-i titles documented before it.

This repository documents the European pressing: a CD-i Ready disc where the entire game lives inside the pregap of track 1 and the other 74 % of the pressing is 22 tracks of Red Book audio. It ships the linker's symbol table for the shipping build, a 68000 disassembler with an NVRAM switch to turn it on, and a default high-score table naming twenty people where the credits name three.

Documentation only. No game assets, no extracted images, no audio, no executable code is committed here. The tools in tools/ reproduce every figure, table and claim in these pages from your own legally obtained copy.

What is documented

Doc Contents
00-overview.md The release, the disc, and where 62 minutes of CD go
01-disc-image.md CD-i Ready, a dump that is entirely scrambled, and two identical filler regions
02-filesystem.md Green Book descriptor, 93 entries, and the CD-DA attribute bit that was wrong
03-executables.md Nine OS-9 modules, four applications, and the loader that chains them
04-symbol-table.md cdi_app.stb — 521 named symbols, shipped by accident
05-dat-containers.md The .dat format, proved by chaining 45 files to the byte
06-graphics.md CLUT7 and CLUT8, two line pitches, palettes, tile maps
07-audio-and-music.md The soundtrack shipped twice, and the 132-byte table that maps one onto the other
08-game-architecture.md What 521 symbols say the game is
09-leftovers.md A debugger, a switch to turn it on, twenty names, and five empty maps
10-cross-disc.md 714 KB byte-identical to another studio's disc, and a third filler recording
11-open-questions.md What is measured, what is inferred, what did not resolve
notes/file-inventory.md All 93 entries, the volume descriptor, the sector budget
notes/symbol-inventory.md All 521 symbols in address order, by section
notes/chunk-inventory.md All 155 .dat chunks, and the eight that are shared
notes/music-map.md 22 CD-DA tracks against 22 ADPCM channels
notes/string-inventory.md Every readable string in all six modules
cdi-platformnotes-doc Platform checklist — what to look for on any CD-i disc. Shared by every CD-i pipeline; this repo links it rather than forking it

Highlights

It is a CD-i Ready disc, and the dump is entirely scrambled. There is no data track. All 22 tracks in the TOC are AUDIO, and track 1 has a 15:22 pregap — 69,150 sectors — which is where the whole CD-i volume lives, hidden from any audio CD player that starts at INDEX 01. Every ripper therefore reads the game as audio and hands back raw channel data: correct 12-byte sync marks and nothing else recoverable. Grep the image for CD-RTOS and you get nothing. The fix is one XOR, and the proof is four bytes:

  LBA 0    stored 01 82 00 62
           want   00 02 00 02     (MSF 00:02:00, mode 2, BCD)
           ECMA-130 scrambler
                  01 80 00 60
           xor    01 82 00 62     <-- matches

Seven probes across four orders of magnitude of LBA, seven exact matches. python tools/cdiready.py extract writes an ordinary Mode 2 image.

The soundtrack ships twice, and the disc carries the table. 22 Red Book tracks (45.85 min, 73.9 % of the pressing) and 22 Level C stereo ADPCM channels (46.0 min) across three real-time files, one to one. The mapping is a 132-byte global the symbol file calls music_table, and comparing the two sides closes:

python tools/cdimusic.py check
21 of 22 durations agree to within 0.25 s

The interleave is timed to the drive: a channel gets one sector in eight at 0.1067 s each, so its span in sectors equals its length as a CD-DA track. music.rtf channel 3 spans 21,449 sectors and CD-DA track 7 is 21,449 sectors.

Track 15 is the one that does not agree — 66.61 s as CD-DA, 93.44 s as ADPCM, a single record either way. One of the two masters was cut.

The Microware linker's symbol file is on the retail disc. /CMDS/cdi_app.stb, 10,584 bytes, is an OS-9 Data module holding 521 symbols with their source-level names, mastered fourteen seconds after the executable it describes. Four independent checks settle the address model:

btext   -> 0x00000000    bname   -> 0x00000048  = M$Name
_cstart -> 0x00000050    etext   -> 0x00005ac4  = M$Size, 23,236

and one further constant, file_offset = addr + 48296, makes the initialised value of every global readable — which is how the next two findings were recovered.

A 68000 disassembler shipped, with a switch on the player to turn it on. line_disassemble is 3,434 bytes — 14.8 % of the game module — and it is real: the full mnemonic table (ADD AND BCD CHG CHK CLR … TRAPV TST UNLK) and the condition-code table (T F HILSCCCSNEEQVCVSPLMIGELTGTLE) are both in the binary, next to a register dumper and thirteen named exception vectors. And:

/nvr/CATCHER_ON
"catcher %sinstalled\r"
"Exception Catcher %s  "   with %s = "0.52"

/nvr is the player's non-volatile RAM. Create a file called CATCHER_ON there and the retail game installs its debugger. One of the exception names is Privilage violation.

Twenty names in the default high-score table, where the credits name three. nvr_buffer is the factory contents of the save file, 244 bytes, twenty records of rank + score + six-character name, scores descending by exactly 10,000:

01  200,000   LUKE      06  150,000  JOOST     11  100,000  DENNIS
02  190,000   TIM       07  140,000  JORIS     12   90,000   OLU
03  180,000   ARJEN     08  130,000  JOOP      13   80,000  SIBBY
04  170,000  STEFAN     09  120,000   JSB      14   70,000  DANION
05  160,000   LUC       10  110,000   ROB      ...   ...    SAM MARC DAVE ELLIS RIK ERIC

The volume descriptor's data-preparer field reads Tim, Luke, Luc and Arjen — four names, and four of the top five scores. Sixth is Joost Egmond, the credited composer. The credit roll names three people; this table names twenty, and fifteen of them appear nowhere else on the disc.

714 KB of the disc is byte-identical to Link: The Faces of Evil. /CMDS/philips.rtf and Link's bumper stream are the same file — 121 RL7 video sectors, 149 Level B stereo audio sectors, 40 DYUV sectors, all identical, across two studios, two continents and eighteen months. Only the one descriptor sector differs, and only in the 37 bytes after its first 2,048. The DYUV picture is 92,160 bytes = 384 × 240 exactly, and the container's magic is 0xBABEFACE with the field names cluts, count, filenum, frame_sizes.

A third filler recording, and the first disc to write it twice. The 2,250 sectors in front of the file system are the standard CD-i filler block, with the standard 00 00 20 00 subheader and zeroed EDC — but the audio is neither the 1993–95 recording shared by Link, Origami and Merlin nor Soccer's 1997 one. And it is written a second time behind the volume:

head  18-2267      5,229,000 B  md5 4e61f608e1f1455d9ad5b2a0615dbbd3
tail  57868-60117  5,229,000 B  md5 4e61f608e1f1455d9ad5b2a0615dbbd3
head == tail: True

Because this dump was never descrambled, the filler audio reads in the clear — which confirms from the opposite side the mechanism the other pipelines had to infer, and the boundary-jump test still gives the same 28 bytes destroyed per sector.

The .dat container closes 45 times out of 45. A 2,048-byte directory — u16 count, then one u32 size per chunk — followed by chunks each padded to a sector boundary. Chain them and the last one ends exactly on the file size, on every container from 106 KB to 776 KB:

python tools/cdidat.py check
45 containers close exactly, 0 do not, 8 are not containers

Chunk 0 of every level and screen file is a BRA.W jump table — 68000 code overlays, which is why the game module is only 23,236 bytes. All eighteen map files share one 768-byte palette; all six level files share one 6,720-byte 320 × 21 status panel.

Five empty maps for a seventh level. map7_1.datmap7_5.dat, written at 14:56 on the last day, 16 KB each where every other map is 116 KB, 82 % empty, with no level7.dat, no intro7.dat and no l7_em.dat — and five of them where every other level has three. The binary names them.

And the disc is otherwise immaculate. No all-zero files at 19.9 % capacity, no abandoned localisation, no unreferenced assets — twenty filename templates in the binaries expand to exactly the 56 .dat and .rtf files on the volume, nothing left over in either direction — no MPEG, and no personal data beyond twenty first names.

Tools

Platform-general, carried between CD-i pipelines:

tool what it does
cdilib.py sector, subheader and directory primitives (CD-DA attribute bit corrected)
cdifs.py list / tree / map / extract the Green Book file system
os9mod.py OS-9/68000 module headers, parity and CRC
cdistrings.py filtered strings out of a module
cdiaudio.py Green Book ADPCM, mono and stereo decode
cdistb.py parse a Microware .stb symbol module — stats, list, groups

Written for this disc:

tool what it does
cdiready.py start hereprobe, extract, tracks: recover the CD-i data track from a CD-i Ready image
cdihead.py the two filler regions — map, check, hash, stats, wav
cdidat.py the .dat container — list, check, census, extract
cdirtf.py real-time census — census, records, sizes, wav
cdimusic.py music_tabletable, and check it against the CD-DA tracks
cdinvr.py the default high-score table — show, cross

cdiready.py and cdistb.py should work unmodified on any CD-i Ready disc and any Microware .stb; cdidat.py, cdimusic.py and cdinvr.py carry this title's offsets and are worth reading rather than running elsewhere.

Point them at your own dump:

chdman extractcd -i "Apprentice The (Europe).chd" -o _work/disc.cue -ob _work/disc.bin
python tools/cdiready.py probe
python tools/cdiready.py extract          # -> _work/data.bin
python tools/cdifs.py list
python tools/cdifs.py extract _work/files
python tools/cdistb.py list
python tools/cdidat.py check
python tools/cdimusic.py check
python tools/cdinvr.py cross
python tools/cdihead.py hash

CDI_RAW overrides the path to the raw image and CDI_BIN the descrambled data track; they default to _work/disc.bin and _work/data.bin.

Related

About

Reverse-engineering notes on The Apprentice (Philips CD-i, The Vision Factory, 1994): a CD-i Ready disc hiding the whole game in track 1's pregap, with the linker's symbol table, a shipped 68000 disassembler, and a soundtrack pressed twice.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages