Do you like my hacking? If so, please consider leaving something in the
Fediverse (Mastodon etc): @Sprite_tm@social. spritesmods.com
Unlike something like Doom, which is written in C and is easy to port to anything that has a working C compiler, Pinball Fantasies was entirely written in hand-coded x86 assembly. As I intended use an ESP32S3 as the mind of the tiny pinball table, I couldn't use that directly; the Xtensa core in that chip doesn't know what to make of x86 instructions. So my initial idea was to slowly convert the code to C: assemble the x86 assembly files, link them to a framework of C files, then slowly convert routines from assembly to C until the entire thing was converted and all I had left was clean C code.
Unfortunately, it turns out this is not that easy. First of all, while impressive, the code is a product of who wrote it and the standards at that time: it's a mess of global variables, macros, comments in Swedish, English and some German, labels that are in-jokes rather than descriptive... Don't get me wrong, this is not a dig at the good people from TSP; most of it was pretty standard at that time and as a demo group I can imagine them wanting to have some fun with it. Nevertheless, all that makes it a pretty complicated job to translate part-wise to C.
However, there was an issue that was a bit more complicated: not only did the sources not want to assemble cleanly, there also was stuff missing. Some of it was external to the main pinball logic, with a somewhat clean API: for instance the sound drivers (which handles the timing as well). Code like that would be somewhat easy to reverse engineer from the API alone. However, some more deeply embedded code was also missing, for instance that to render the fonts. Aside from that, all the graphics were also missing, and as they were linked into the same executable as the code, replacing that would be a lot more complex than copying some external graphics files from a binary distribution over.
The source code was, however, informative in that it told me what the architecture of the system is. Essentially, Pinball Fantasies consists of an intro menu program that selects between tables, the tables themselves, and the audio drivers. All these are separate executables that (partially) stay in memory so they can work together. When a table is running, a bit of the intro program stays resident in order to handle the global settings. The audio driver actually is a self-contained music and sound player; the table code can give it a command like 'play jingle 1' or 'go to this position in the music' or 'play sound effect 3'. It also handles some of the display timing. Finally, the table programs have some interaction with DOS (mostly to open/read/write/... the highscores file, load the audio drivers, and to print error messages). There is no driver for the VGA hardware, it handles that directly, but it does so in a smart way: it uses hardware scrolling to move the table across the screen and palette changes to do all the blinkenlichten and other effects. All in all, the only actual graphics it needs to push pixels for are the ball, flippers and the dot matrix display.
This means that the actual core of the simulator, the 'business logic' if you will, actually is pretty light, as the bulk of the heavy lifting is done outside the table executable. The graphics are mostly handled by the hardware in the VGA card and the audio decoding, sequencing and mixing is done in the sound driver. So why not interpret the table logic as if it was written for a very peculiar VM, and emulate that virtual machine?
So, that's what I did. The Pinball Fantasies game supposedly has a 286 at 12MHz as the minimum required specs, but there's no 286-specific assembly instructions used, so I could get away by grabbing an 8086 emulator. I grabbed the one from Fake86+ as it was written in C and didn't take too much effort to get to work outside of the emulator it was supposed to live in. I also needed to add the bare minimum implementation for interfaces that the game expected: a simulation of the VGA mode it would run in, the few DOS calls it used, one or two BIOS calls, the mouse and keyboard hardware and an implementation of the sound driver. I also needed a retail version of Pinball Fantasies; these are easy enough to obtain as Good Old Games had a pack with it and other games of the era in it for only ten bucks.
Emulating the VGA mode was the easiest to do: as it's an output-only device, as long as the image looks good, you emulated enough of it. And in this case, an emulated VGA output can have some extra tricks: while Pinball Fantasies is intended to run in a 320x200 or 320x350 resolution with only a part of the table visible at all times, the actual table is kept entirely up-to-date in video memory and is about 320x620 pixels in size. There's nothing stopping me from ignoring the scrolling altogether and simply displaying the entire table if I wish.
Emulating the audio subsystem was a bit more complex, but as it's based on the standard Amiga .mod tracker format, it's not that hard to build upon existing code. Specifically, I already had a somewhat modified version of ibxm that could run on an ESP32 and play files directly from flash, using very little RAM. It was easily modified to have an extra channel for playing sound effects and to have hooks to report the end of a pattern and to be able to jump to any pattern.
Most of my time here actually went towards fixing a nasty design bug in my emulator. It's pretty hard to try to debug somewhat unknown code, even if you have the source, connecting to badly-documented interfaces on a virtual machine that has a bug somewhere! In the end, I got it working, and with some SDL hooks, I now had Pinball Fantasies running in a window on my desktop.