The first computer I spent a lot of time with was an Amiga 500, and later an Amiga 500+. I did a lot of my early programming on that system, but I also have fond memories of playing various games of the era and occasionally get nostalgic for it and want to just dip in and replay some of my favories.

Amiga emulation is readily available and featureful, but it tends to involve a bunch more fiddling and tinkering than emulating simpler systems like game consoles: you need to figure out what system specs best suit the game you're trying to run, obtain a Kickstart ROM to use to boot it up, set up little M3U playlist files to easily swap virtual floppy disks during gameplay, etc.

Now I want to be clear up front that I'm not here to criticize or demean those who do enjoy the full, authentically-complicated Amiga emulation experience. Different folks enjoy retrocomputing in very different ways and I have definitely had some fun in the past configuring emulation of a souped-up Amiga system with all of the fun expansions I couldn't afford back in the 80s/90s. I know there are lots of people who use use either emulated or real Amiga systems as daily-driver productivity systems, or at least try their best to do so. I'm personally not particularly interested in that, but I can understand why others enjoy it.

But I personally prefer to just focus on playing a game with as little ceremony as possible, and so each time I've revisited the system for a while I've found myself thinking of ways to make it more similar to the experience of emulating the gaming consoles of that same era, like the Sega Megadrive (aka Genesis) or Super Nintendo, where in most cases you just select a ROM image and it immediately boots the game.

A little context

For readers who either grew up with a different platform of the era, or the probably-more-plentiful readers who weren't even born yet, I should probably start by explaining how Amiga systems and Amiga games tended to work.

An Amiga system has a ROM chip in it which contains the foundations for a preemptive multi-tasking operating system, window-oriented GUI system, and a disk operating system. This ROM is called "Kickstart", which I always thought was underselling what it provides by quite a lot! Folks using so-called "productivity software" like word processors, desktop publishing programs, etc would typically use them within a full operating system environment using all of the facilities of the ROM kernel combined with various other OS software loaded into memory from disk.

However, to make the most use of the impressive but still limited Amiga hardware most early games just used Kickstart as a glorified bootloader: it would load a custom block of executable code from the beginning of a floppy disk and immediately start running it, and then that code would directly access the system hardware to do everything else. The degree of this varies between titles -- some literally do directly control the floppy disk drive using chipset registers, while others reuse some of the lower-level code from the kickstart ROM -- but the multitasking and GUI systems are essentially ignored, and more-or-less the only code running on the system is what was loaded by the game from the floppy disks.

In later generations of the Amiga hardware it became more common for folks to have a hard drive and more expansion memory, but the newer systems also had assorted small differences in chipset and OS behavior that broke some of those early games. To make it more convenient to play these classic games on later hardware, various folks experimented with patching these games to be able to launch them from within the full OS environment instead of booting directly from floppy disks, which is typically called "installing" the game. Although there were initially various competing approaches, ultimately WHDLoad emerged as the de-facto standard and the community rallied around it.

WHDLoad works by giving each game a new launcher program that replaces its original startup code. Instead of interacting with the OS or hardware registers directly, WHDLoad provides a simpler API called the "resident loader" which allows loading data from disk in an abstracted way and then applying patches to it. The usual approach is to overwrite the parts of the game code that access disk or that otherwise need adaptation with machine instructions that jump into other parts of the launcher program, which then perform equivalent operations to what the game code would've done but using the resident loader API instead.

On other systems like game consoles and the IBM-style PCs we'd probably call the ResidentLoader API a "BIOS", but in the case of WHDLoad it's actually implemented as a normal application which launches from the full operating system and then implements its API in terms of that operating system, making it possible to exit out of a game and return to the OS environment you launched it from even though most of these games were never intended to be used in this way.

In my early adventures with "console-like" Amiga emulation I wrote a little wrapper program called WHDAutoload, which automates the process of finding and activating an icon that would normally be launched interactively from the Amiga GUI so that it can be done directly inside the system boot process instead. This still requires correctly configuring the hardware and Kickstart ROM, but simplifies the boot media considerably. Alongside that, various forks of the Amiga emulator codebase "UAE" implemented similar solutions themselves which dynamically generate a virtual boot disk which immediately launches WHDLoad with a specified game.

But I couldn't help but think that this whole process of using a full multitasking operating system purely to launch a single program that immediately disables that operating system was a little silly. Although real hardware needs a ROM to give the CPU something to start running as its first instruction, an emulator can "cheat" and preconfigure the system to immediately start running the game code, without needing a ROM image at all!

rlUAE: Skipping the boot process altogether

Skipping ahead a little, I've been working for the last few months on libretro-rlUAE, which is a fork of libretro-uae with the startup process completely replaced and with a built-in implementation of the most important parts of the WHDLoad ResidentLoader API, without relying on either a Kickstart ROM or WHDLoad itself (with some caveats I'll discuss later). This is implemented as a "core" intended for use in libretro frontends like RetroArch.

In place of a cartridge ROM, rlUAE wants you to provide a .lha archive containing the results of "installing" a program for WHDLoad. On startup the core reads the contents of the LHA archive into memory and searches it for an icon that would cause WHDLoad to start, in much the same way that WHDAutoload does. But instead of then launching WHDLoad, it loads the loader program directly into host RAM and maps it into the emulated Amiga's virtual memory space, and arranges for the CPU to begin execution right at the entrypoint of that program with no intermediate ROM at all.

The helper program then wants to call back into the Resident Loader, which rlUAE handles by placing a bunch of intentionally-invalid instructions in the part of memory where a kickstart ROM would normally be mapped, and telling the program to use that as the Resident Loader jump table. When the CPU encounters one of those invalid instructions, instead of jumping into an emulated exception handler it instead runs some native code from inside the emulator's own code -- reusing a mechanism that other UAE variants use for higher-level functionality like BSD-style sockets -- where I've reimplemented a subset of the WHDLoad API as closely as possible so that the loader sees the same results it would get running under WHDLoad, and the game hopefully boots successfully.

WHDLoad effectively creates a limited environment with the following features: - Some amount of "base memory" that is guaranteed to be accessible by the Amiga chipset. - Some amount of "expansion memory" that may not be accessible by the Amiga chipset but can be used for code and data. - The Amiga chipset itself, directly accessible for dealing with graphics, audio, and input. - The Resident Loader API as a lower-level replacement for the kickstart's disk access APIs.

rlUAE aims to provide a compatible environment through emulation: the base and expansion memory are just blocks of host memory mapped into the emulated memory space, the chipset is UAE's normal chipset emulation mapped to the usual memory addresses, and the Resident Loader API is a bunch of trap instructions that run host code providing a similar affect to WHDLoad's equivalent functions.

For loader programs that only need the subset of the API I've implemented, the game just starts right up with little or no time spent waiting for data to load, with no disk swapping, and (in most cases) no need to worry about exactly what model of Amiga you're trying to emulate, how much RAM you need to configure, and which kickstart ROM image to select.

Some Amiga games do rely on the kickstart ROM to a greater extent, though. For those, WHDLoad provides a mechanism to load a copy of a kickstart ROM Into RAM and apply relocations to its code so it'll work correctly at that new location. This means that the kickstart version being used doesn't need to match the one whose chip is in the current system, which is important for some older games that naively jumped directly to addresses in the ROM and so fail if a newer ROM is selected. rlUAE reimplements this mechanism too, and so for those games you will still need to have one of the expected ROM images available but the system automatically selects a suitable one to use based on information in the program's header, and then the loader program will patch it to boot directly into the game.

What's working and what's not working

So far I've been testing with various games I previously had access to in the 80s/90s, because for those I have some memory of how they are supposed to behave so I was able to tell when they were gliched out in one way or another.

Single-player games that expect the typical configuration of one joystick and one mouse seem to mostly work. I've stubbed out configuration options for choosing other predefined controller combinations -- two joysticks, or two CD32-style gamepads -- but currently that option is just completely ignored and so two-player games and CD32-based games are not generally accessible right now.

I was able to replay the Amiga port of Toki from start to finish, which was satisfying because that was one of the very first Amiga games I ever played and I was much less good at it when I was younger! I've not played anything else to completion, but I've tested various other games for which it's at least possible to get through the title screens and menus and reach the beginning of main gameplay.

The system defaults to emulating a 68020 CPU, AGA chipset, and PAL chipset timing. There is a core option to switch to 68000/ECS and one for NTSC, though the NTSC switch is glitchy because I didn't correctly handle how it interacts with the libretro API. Restarting a game from within the loaded core doesn't work properly because of the slightly-awkward way I've shoehorned the loader into the libretro core, and so unfortunately any core options that require restarting the system will need you to save your configuration to disk, exit your frontend, and reload it from scratch.

Where to from here?

To be honest, I've run out of time and energy for this project for the moment. Writing software is my dayjob so I try not to spend all of my spare time doing the same thing! However, I'd love it if others found this an interesting enough starting point to continue iterating on this -- particularly those who have more Amiga knowledge loaded readily available in their brains, since my memories of the Amiga chipset etc are very stale after being away from it for decades!

The most obvious next step would be to figure out what's preventing certain games from working completely or at all. I have some rough notes on what problems I've seen so far, but I've also barely scratched the surface of the classic Amiga games library and I'm sure there are various other problems I'm not even aware of yet.

One way I imagined this codebase could evolve is to remove various functionality that's not needed to provide a WHDLoad-compatible environment, such as the BSD sockets emulation, the low-level floppy disk and hard disk emulation, etc, thereby making this codebase far simpler. However, that would likely cause it to be difficult to incorporate fixes from other UAE forks later, and so perhaps it would be better to instead find a way for these two usage modes to exist together in a single codebase. I'm an interloper into the UAE development world so I'm not trying to impose anything on anyone here: I trust that other UAE maintainers know much better than I do what would make sense here, and they might reasonably want nothing at all to do with this either.

Although I've focused purely on software-based emulation here, I suspect a variation of this strategy could also be incorporated into a MISTer-like FPGA-based Amiga system by again replacing the Kickstart ROM with various stubs that somehow cause control to temporarily transfer to the ARM CPU, implement the required Resident Loader API behavior over there, and then resume the emulated CPU. This could then allow the FPGA core to skip implementing floppy disk and hard disk emulation, though I don't know if those features actually take up enough logic space for it to be worthwhile to do that.

In principle it could be possible to make an actual ROM containing a simple bootloader and an implementation of the Resident Loader API in terms of the AROS implementations of hard disk access and thus avoid needing a kickstart ROM even in a real hardware Amiga system, but I think by that point we're definitely in the realm of diminishing returns: if you've gone to the trouble of aquiring, restoring, and maintaining genuine retro Amiga hardware then I expect you're probably quite invested in it behaving like it would've done at the time, rather than just treating it like a quirky game console.

Regardless, I'm sharing this just in case it's interesting to someone who might want to take it further. I'm not expecting to spend any more time on it for the foreseeable future, and I've intentionally disabled Pull Requests in the GitHub repository to encourage folks who might want to extend it to create their own forks since I'm unlikely to be a responsive maintainer and I don't want folks to get frustrated at me for not reviewing their changes.

In the meantime, I'm going to keep using this to occasionally replay my old favorites!