A Wild Register Appears: Hunting the 30-Year-Old World of Xeen MT-32 Crash | finalpatch
finalpatch
A Wild Register Appears: Hunting the 30-Year-Old World of Xeen MT-32 Crash
Might & Magic IV & V: World of Xeen freezes and corrupts itself when you play it<br>with a Roland MT-32 — on emulators and on real hardware. Here’s the months-long<br>hunt for the bug, and the ten bytes that fix it.
The game that raised me
Might & Magic IV: Clouds of Xeen and V: Darkside of Xeen — together “World of<br>Xeen” — are core childhood memories for me. I spent countless afternoons mapping<br>dungeons on graph paper and grinding my party through Castleview.
Back then, almost nobody I knew had anything fancier than a Sound Blaster. The<br>music was fine, but it wasn’t what the composers were hearing in the studio. The<br>real Xeen soundtrack was written for the Roland MT-32 / LAPC-1 (the CM-32L<br>sound module), and it sounds gorgeous — lush, warm, completely different from the<br>OPL FM synthesis of a Sound Blaster.
So years later, re-experiencing the game, I did the thing every audio nerd does:<br>I set it to Roland MT-32 and sat back to enjoy the “intended” soundtrack.
And the game started falling apart.
The symptoms
Within a few minutes — usually while wandering a town and talking to vendors —<br>something would go wrong:
The music would slowly start playing the wrong instruments .
Then the game would freeze , or crash to DOS , or die on an illegal<br>instruction .
Once, it wandered into the save routine and corrupted my save file .
Switch the music back to General MIDI / Sound Canvas, or to Sound Blaster, and<br>the game is rock solid forever. Only the MT-32 setting breaks it. And the longer<br>you play, and the busier the area (towns, shops, lots of music and sound-effect<br>changes), the faster it dies.
What’s strange is how few reports of this exist online. My theory: almost<br>everybody plays Xeen with the default Sound Blaster setting, so almost nobody<br>ever pokes the MT-32 path hard enough to hit it.
“It’s just the emulator”… right?
My first assumption was the obvious one: it’s an emulation bug. I could reproduce<br>it in DOSBox-pure (on Android/RetroArch), DOSBox Staging, and DOSBox-X, so surely<br>one of them was mis-emulating the MPU-401 or the timing.
Then I went looking, and found scattered accounts from people running the game on<br>genuine MT-32 + CM-64L hardware on real 386/486 machines describing the exact<br>same thing: instruments drift to the wrong patches, then it freezes, worst in<br>towns, never with a Sound Blaster.
That reframed everything. This was not an emulator bug. It’s a real bug in<br>the game’s own code, one that just happens to also fire under emulation. DOSBox<br>wasn’t doing anything wrong — it was faithfully reproducing a 30-year-old crash.
The graveyard of theories
This is the part nobody puts in the writeup, so I will: I was wrong, repeatedly,<br>for a long time.
MPU-401 timing / busy-wait latency. The driver talks to the MPU by polling<br>status bits in tight loops. I was sure the slow MT-32 was stalling those loops<br>and opening a reentrancy window. I built a “spin detector” that logged whenever<br>the driver actually busy-waited. Result: it only ever spun during the 14 ms<br>power-on reset. During gameplay, the emulated I/O is effectively instant. Dead<br>end.
Interrupt reentrancy. The music driver runs as the timer interrupt handler.<br>Maybe the timer was re-entering the driver while it was mid-update? I added a<br>guard that blocked the timer from firing while executing in the driver’s code<br>segment. It barely ever triggered, and the crash still happened. Dead end.
Stack smashing. A classic cause of “the program jumps to garbage” is an<br>overwritten return address. I built a shadow return-address stack that pushed on<br>every call/interrupt and checked on every return. Across ~29 million returns<br>it found exactly zero smashes. Trustworthy negative. Dead end.
Reading the whole driver. I disassembled the entire MT-32 driver and<br>convinced myself every memory write in it was bounded — masked to a channel<br>number, or a fixed address. By that logic the driver couldn’t be the thing<br>scribbling memory. (Hold that thought.)
Every heuristic “this looks abnormal” detector I built drowned in false positives,<br>because legitimate code reuses the same segments, values, and stack slots<br>constantly. The lesson I kept re-learning: stop guessing at what “looks wrong.”<br>Anchor on a confirmed-corrupt value and watch it.
The breakthrough
The shift was to stop theorizing and look at the actual wreckage.
1. Dump the driver after it dies. When the game froze, I dumped the driver’s<br>memory segment and compared it byte-for-byte against a clean copy. It was<br>riddled with changes — but the telling ones were in the driver’s own<br>machine code :
The jump table at the top of the driver had wrong targets.
A configuration word holding the MIDI port address (0x0330) had been<br>overwritten with garbage (0x00DF). That explained a baffling earlier<br>symptom — when this happens, the driver...