Reverse-engineering notes on Link: The Faces of Evil (Animation Magic / Philips Interactive Media of America, 1993) — the Philips CD-i action game that everyone has heard of and almost nobody has looked inside.
This repository documents the USA release: a single-track disc holding fourteen files in a flat root, two OS-9/68000 executables, 71 hand-painted backgrounds, 5,769 run-length coded animation cels, and 101 minutes of ADPCM. Half the disc is deliberately empty.
Documentation only. No game assets, no extracted art, no audio, 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 release, the disc, where the 600 MB goes, what the timestamps say |
| 01-disc-image.md | The sector map, three kinds of filler, and 29 seconds of scrambled 44.1 kHz audio in front of the file system |
| 02-filesystem.md | Green Book volume descriptor, a flat root, and the same 30 MB pressed three times |
| 03-executable.md | cdi_link and cdi_bumper: modules, parity, CRC, and a shared C runtime |
| 04-symbol-table.md | The 325 C function names the retail build still carries |
| 05-realtime-files.md | Channels, submodes, records, triggers, and why half the disc is empty |
| 06-audio.md | 101 minutes of ADPCM, and an eight-channel music jukebox with no seek |
| 07-playfields.md | 71 CLUT7 backgrounds, their IDAT descriptors, and 24 compiled subroutines each |
| 08-animation.md | RL7 cels in fixed 14,336-byte slots, and the Philips logo in DYUV |
| 09-savegame-and-nvram.md | /nvr/link1.nvr, three slots, string tables, a pronounceable-name generator |
| 10-leftovers.md | The archaeology: ersatz, cdi_test, fake bombs, a kiosk demo, a publisher typo |
| 11-open-questions.md | Everything unresolved, with the measurements behind it |
| notes/file-inventory.md | All fourteen entries with LBA, size, date and attributes |
| notes/rtr-channel-map.md | Every real-time file by channel and submode |
| notes/audio-inventory.md | All 114 audio records with durations |
| notes/playfield-inventory.md | All 71 backgrounds with geometry and stream offsets |
| notes/symbol-inventory.md | Every function name, in link order and bucketed |
| notes/string-inventory.md | Every legible string in both executables |
| cdi-platformnotes-doc | Platform checklist — what to look for on any CD-i disc. Shared by every CD-i pipeline; this repo no longer keeps a copy |
The retail executable still carries the name of almost every function it was
built from. 325 of them, NUL-terminated, laid down in link order between the
functions themselves — the complete architecture of a 1993 game whose source has
never been public. Enemies turn out to be eight numbered types sharing five
movement routines and a "macro" animation system. The display is two MCD212
planes and fourteen functions. Among the names: load_piracy,
retrieve_mkt_demo, add_fake_bombs, check_vulnerablility,
old_get_sprite_block, fix_jump_stick, and my_sprintf.
Half of this 600 MB disc is deliberately empty. 126,815 sectors — 294.7 MB
— carry nothing at all. That is not waste, it is the mechanism: a CD-i real-time
file streams at exactly 1x and every channel it carries must be present at a
constant rate whether or not anyone is listening. lmusic.rtr runs eight
stereo music channels simultaneously, perfectly round-robin from its first
sector, and the game listens to one. The other seven are the price of switching
tunes without moving the laser.
83 minutes of music, in 72 records. Nine blocks of eight tunes each — 40 to 118 seconds apiece, all stereo — plus a three-minute finale at double the sample rate on the last channel of the file. On a 1993 CD-i disc.
A further 11.5 % of the disc is the same 30 MB pressed three times.
ldata.rtr, ldata1.rtr and ldata2.rtr are byte-identical down to their
subheaders, placed at the start of the file area, 45 % along and 92 % along, so
a single-speed drive is never far from the game's working set.
Every playfield ships with two dozen compiled 68000 subroutines. Each of the
71 background descriptors carries a CPL0 block whose 24 entries all begin
48 e7 — the MOVEM.L prologue a C compiler emits. The level scripts are
machine code, streamed off the disc with the picture, which is why 71 levels of
behaviour fit behind a 133 KB executable.
The backgrounds are raw framebuffers, 240 lines tall and one, two or three
screens wide. 24 at 384 × 240, 17 at 768, 30 at 1,152. Across 13.6 MB of pixel
data not one byte has bit 7 set — no compression, no header, no line padding.
Each starts on a sector boundary, and 24×40 + 17×80 + 30×119 = 5,890 sectors
is the video stream to the byte.
The cutscenes are 5,769 run-length coded cels in fixed 14,336-byte slots. Seven Form 1 sectors each, holding 240 lines of RL7 and then zero padding — 60 % full on average, 10.7:1 compression. The slot size is the frame rate: seven sectors out of 75 a second is a hard ceiling of 10.7 fps, and the padding is reserved bandwidth rather than waste.
load_piracy loads a picture, and the picture is a threat. The first still
frame in lanim.rtr is a full-screen notice about federal copyright law,
$50,000 fines and five years in jail. Nothing tests anything; the function just
displays it.
Two 70-byte files called ersatz_1 and ersatz_2 are byte-identical valid
OS-9 data modules, both named cdi_test, both containing nothing. The
executable lists them next to prepare_mod_dir — substitutes padding out a
module directory, and ersatz is the German for substitute.
Every shop item has a fake version. add_bombs_to_buy / add_fake_bombs,
add_ropes_to_buy / add_fake_ropes, add_oil_to_buy / add_fake_oil — six
functions, three adjacent pairs, and no oil anywhere in the item table.
The publisher's name is misspelled on the glass master. Philips Intractive Media of America, Inc. in the volume descriptor — the one string on this disc a
CD-i player reads and can display. The COPYRIGHT file three sectors away, and
the DYUV logo in bumper.rtf, both spell it correctly.
The executable was rebuilt seventeen hours after the volume was closed. The
volume descriptor is stamped 1993-06-23 17:48:35, three seconds after the last
real-time stream was written. cdi_link is stamped 1993-06-24 11:02:55.
29 seconds of a voice sit in front of the file system, and the identical
recording sits on an unrelated disc. Sectors 19–2,268 belong to no file and
descramble to 16-bit stereo PCM. Origami (EagleVision, Netherlands, 1993) —
no shared developer, publisher or continent — carries the same 5,229,000 bytes,
md5 a0ed87f2e98b43f91281d16390fb178b. It is an artefact of the CD-i authoring
chain: a CD-DA stream the mastering system wrote sector headers over, losing
seven stereo frames per sector. Who is speaking is still unknown.
You need the disc image as a raw 2,352-byte-per-sector track, Python 3, and Pillow for the image tools.
mkdir -p _work && cp "Link - The Faces of Evil (USA).bin" _work/link.bin
python tools/cdifs.py list # the fourteen files
python tools/cdihead.py map # the pre-file-system region
python tools/cdihead.py wav _work/head # the 29 seconds
python tools/os9mod.py _work/files/cdi_link
python tools/cdisym.py list # the 325 symbols
python tools/cdirtr.py map # every real-time file, by channel
python tools/cdirtr.py audio lmusic.rtr 3 _work/audio
python tools/cdipf.py png _work/png # all 71 backgrounds
python tools/cdianim.py frames # animation slots
python tools/cdianim.py png 60 _work/anim
| Tool | Scope |
|---|---|
cdilib.py |
sectors, subheaders, directory walk — platform-general |
cdifs.py |
Green Book file system — platform-general |
cdihead.py |
the pre-file-system region and the ECMA-130 scrambler — platform-general |
os9mod.py |
OS-9/68000 modules, parity and CRC — platform-general |
cdiaudio.py |
CD-i ADPCM in AIFF-C containers — platform-general |
cdirtf.py |
real-time file demux, MPEG-oriented — platform-general |
cdistrings.py |
filtered strings — platform-general |
cdirtr.py |
this disc's real-time files, channels and ADPCM |
cdipf.py |
the 71 playfields, IDAT and CPL0 |
cdianim.py |
RL7 cels and the 14,336-byte slot |
cdisym.py |
the symbol table |
The first seven came from cdi-ultracdisoccer-doc and work unmodified on another CD-i disc. The last four carry this title's tables and are worth reading rather than running.
Producer -- Dale DeSharone
Script -- Jonathan Merritt
Programmers -- Linde Dynneson, John O'Brien, John Wheeler
Graphic Design -- Rob Dunlavey
Video Production -- Animation Magic, Inc.
Audio Production -- William Havlicek
Music -- William Havlicek and Tony Trippi