Reverse-engineering notes on Merlin's Apprentice (Philips Interactive Media of America, 1995) — a CD-i puzzle game designed and directed by Cliff Johnson, the author of The Fool's Errand, and the only CD-i title he made.
This repository documents the European pressing: one CDI/2352 track of
131,610 sectors, 29:14, 39.5 % of a CD, holding eighteen files in a flat
root — and, in among them, the linker's symbol file for the shipping build.
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.
| Doc | Contents |
|---|---|
| 00-overview.md | The release, the disc, and where 309 MB goes |
| 01-disc-image.md | The track, and 5.2 MB of speech in front of the file system that a 1995 disc shares byte for byte with two 1993 ones |
| 02-filesystem.md | Green Book descriptor, eighteen entries, SCCS keywords, and a 39-person credit roll |
| 03-executable.md | cdi_merlin: one CRC-clean module whose first instruction proves the symbol file |
| 04-symbol-table.md | cdi_merlin.stb — 887 named symbols, shipped by accident |
| 05-bolt-library.md | BOLT: 129 groups, 7,703 members, mapped to the byte, and a compressor that barely compresses |
| 06-realtime-files.md | Eight streams, 89 animations, and the 472-byte table that indexes them |
| 07-audio.md | Level B ADPCM, an eight-channel jukebox the code calls a jukebox, and the one stereo animation |
| 08-game-architecture.md | Eight engines, 27 named puzzles, three potions, the save format, and a cheat flag |
| 09-leftovers.md | The archaeology: a symbol table, a Traffic Cop, a Kludge, and fourteen diagnostics that shipped |
| 10-open-questions.md | What is measured, what is inferred, and what did not resolve |
| notes/file-inventory.md | All eighteen entries, the sector budget, and all 89 animations |
| notes/symbol-inventory.md | All 887 symbols in address order, by section |
| notes/bolt-inventory.md | The BOLT header, all 129 groups, all 49 member ids |
| notes/string-inventory.md | Every readable string in the executable, grouped |
| 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 |
The Microware linker's symbol file is on the retail disc.
cdi_merlin.stb, 19,752 bytes in the root, is an OS-9 Data module holding the
complete global symbol table of the shipping build: 887 symbols with their
source-level names, covering the game, BOLT, the Microware C library and the
OS-9 system-call library. Nothing on the disc references it and the program
never opens it. The timestamps say it was not an oversight from an earlier
build — it is one second older than the executable and two seconds older than
the volume descriptor:
1995-01-31 13:57:18 cdi_merlin.stb
1995-01-31 13:57:19 cdi_merlin
1995-01-31 13:57:20 the volume descriptor
The address model is addr = (hi << 16) | value, and three independent checks
settle it: btext → 0x00000000, etext → 0x0002196a which is M$Size to
the byte, and _cstart → 0x00000054 which is M$Exec to the byte. Of the
576 code symbols, 452 land on a C-compiler prologue.
The whole game falls out of it. Intersecting symbol addresses with string offsets recovers eight puzzle engines and every puzzle in each:
| engine | puzzles |
|---|---|
SoundChallEngine |
Pond, Flasks, Stalac(tite) |
FragChallEngine |
Mirror, Plaque, Octagon, Tile |
ArcadeChallEngine |
Seeds, Leaves, Bubbles, Snowflak(e), Gems, Demons |
CodeChallEngine |
Grave, Parch(ment), Tablet |
MorphChallEngine |
Window, Star |
AlignChallEngine |
Planet, Door, Sphere |
JumbleChallEngine |
Raven, Leaf, Snake, Skeltn, Spider, Quartz |
PotionChallEngine |
Forest, Labrat, Cavern |
27 puzzles and 3 potions — exactly what the disc's own abstract.txt
promises. There is also a BOSS_GetCheatMode / BOSS_SetCheatMode pair, built
with the same verbs as BOSS_GetDiffLevel / BOSS_SetDiffLevel.
The engine names itself three times over. BOLT in ASCII at byte 0 of
boltlib0.blt; InitBOLT, OpenBOLTLib, GetBOLTGroup, BOLTAllocType,
Decompress in the symbol table; and in bibliography.txt, BOLT Engineering
Support: David Todd, Ken Jordon. The container is mapped here — 129 groups,
7,703 members — and the map closes:
groups 129, members 7703, chain breaks 0
last group end 8007558 (header says 8007558)
Its compressor recovers 1.172 : 1 across 4.7 MB, which is 8 % of the library, on a disc using 39.5 % of a CD. The codec itself is not identified; what constrains it is written down in 10-open-questions.md.
"TC" is Traffic Cop. Forty symbols carry the prefix and nothing expands it
— until you read the error strings, which say Traffic Cop Could Not Open Animation:%d. It is the streaming scheduler, and it reads a 472-byte table
called tcanim.toc whose every byte is accounted for:
python tools/cditoc.py check
89 of 89 entries land on a channel-31 EOR sector
help.rtf is a jukebox, and the code says so. Eight audio channels whose
first sectors are 24, 23, 22, 21, 20, 19, 18, 17 — a perfect round-robin, so
switching between them costs no seek — carrying 25.2 minutes of narration in 43
records, of which the game plays one channel. The API is
TC_Open_Jukebox, TC_Play_Jukebox_Selection, TC_Is_Jukebox_Playing. Link's
disc does the same thing with the same channel count; there it had to be
inferred from sector arithmetic, and here it has a name.
A 1993 artefact on a 1995 disc. Descramble the 2,250 sectors in front of
the file system and the 5,229,000 bytes hash to
a0ed87f2e98b43f91281d16390fb178b — byte-identical to the same region on
Origami (Netherlands, 1993) and Link: The Faces of Evil (USA, 1993). Three
discs, two continents, three unrelated development houses, and now two years
apart. It is an artefact of the CD-i authoring chain, and nobody can hear it.
A libgcc-to-Microware thunk table. Five BRA.W instructions, four bytes
apart, at 0x1d452:
__mulsi3 -> _T$UMul / _T$LMul __umodsi3 -> _T$UMod
__udivsi3 -> _T$UDiv __modsi3 -> _T$LMod
__divsi3 -> _T$LDiv
Every branch target is exactly the symbol its name calls for. Something in this build was compiled by a GCC-family compiler and shimmed onto the Microware runtime's arithmetic helpers rather than linked against libgcc.
Fourteen developer diagnostics shipped, including one that was column-aligned for a terminal that is not there:
BOSS_Init: NumBitsPerVar = %d. Should be 1,2,4, or 8. -- using 8.
and a global named after the fact that the code around it is a kludge:
KludgeTwoControllers.
One animation out of 89 is in stereo. Exactly 149 sectors on the disc carry
coding 0x01; they are every fourth sector of one 7.9-second sequence in
anims.rtf, which contains no mono audio at all. Everything else on the disc
is Level B mono.
And the disc is unusually clean. No dangling path references, no all-zero files, no unused sectors between the path table and the tail, no MPEG, no abandoned localisation, and no personal data. What was cut from Merlin's Apprentice was cut before the final link.
Platform-general, carried between CD-i pipelines and unchanged here:
| tool | what it does |
|---|---|
cdilib.py |
sector, subheader and directory primitives |
cdifs.py |
list / map / extract the Green Book file system |
cdihead.py |
the pre-file-system region: map, check, wav |
os9mod.py |
OS-9/68000 module headers, parity and CRC |
cdistrings.py |
filtered strings out of an executable |
cdiaudio.py |
Green Book ADPCM decode |
Written for this disc:
| tool | what it does |
|---|---|
cdistb.py |
parse a Microware .stb symbol module — stats, list, groups |
cdibolt.py |
parse a BOLT library — header, groups, members, ids, check |
cditoc.py |
parse tcanim.toc — list, and check it against the disc |
cdirtf.py |
real-time census — census, records, sizes |
cdistb.py should work unmodified on any Microware .stb; the other three
carry this title's tables and are worth reading rather than running elsewhere.
Point them at your own dump:
export CDI_BIN="/path/to/Merlin's Apprentice (Europe).bin"
python tools/cdifs.py list
python tools/cdifs.py extract _work/files
python tools/cdistb.py list
python tools/cditoc.py check
python tools/cdibolt.py check
python tools/cdirtf.py census
cdilib.py defaults to Merlin's Apprentice (Europe).bin in the repository
root; CDI_BIN overrides it.
- cdi-platformnotes-doc — the shared CD-i checklist
- cdi-ultracdisoccer-doc
- cdi-origami-doc
- cdi-linkthefacesofevil-doc