SDL2, the “Fake” ROM, and resource maps

Move to SDL2

Until now, the testbed was running inside a Cocoa application, but the need for a platform-agnostic implementation was already putting pressure on adding an abstraction layer between the host and the emulated environment. At this point, we took this as a possibility to switch to SDL2, changing the test environment at the same to the SDL2 library but also at the same time adding the abstraction layer on top of it.

This was accomplished by adding “Platform” layer, which abstracts the creation of sound devices, graphics context and input handling into a set of calls which the Toolbox emulator uses. In future, this layer can be extended to support other platforms, so someday we can add again Cocoa layer to handle platform-specific stuff like, like seamless desktop integration.

Emulated monochrome 512×342 video buffer at FA700/F2700 (1MB Mac memory map) being switched to alternate page using VIA emulation

At this point, we added page switching support to the VIA memory page handler, which is the “Classic Video” hardware abstraction layer to choose active video buffer. The above video demonstrates results of the page-switching test.

The “Fake” ROM

An interesting feature of the original Macintosh system is how the onboard ROM was able to contain majority of features of the operating system, reducing the unpatched code loaded from disk to a minimum. As we attempt to replicate system startup as closely as possible with our architecture, an interesting point came up: On real Mac, the boot process is kicked off by ROM doing a POST hardware check, initializing a bunch of stuff in the memory, and loading a stage 1 boot loader from the disk using the disk and file system drivers in ROM. As our goal is to actually use a virtual file system, we need to have a way to kick-start this filesystem. What we decided to do, is to actually simulate ROM resources, and add our native filesystem driver as a DRVR resource which will be loaded by the device manager, and which file manager will use to actually mount the virtualized disk.

Another benefit of the “Fake” ROM is, that it provides us with neat place to store the UPP records for our trap handlers. As trap dispatch tables contain only 32-bit pointers to the handlers, which for native calls only point to the UPP records, we could now install these dynamically into the “ROM” address space, with additional benefit that if any curious applications would snoop for the address of trap handlers, they would to be above ROMBase, like on a real Mac.

The “Fake” ROM format follows closely the one used by Apple, with the exception that we only use the ROM resources, and preallocate a certain size of image for the UPP records. Thanks to this, we were able to start implementation of first parts of Resource Manager support, starting with creation of ROZ (ROM resource zone), which is created inside system zone to hold the master pointers and resource map which are “mapped” to the ROM space using a very weird and clever hack like on real Mac

A fun fact: A Mac can have multiple types (24 vs 32 bits) of zones active at once – The ROM has resources formatted either to 24 or 32 bits, but as the ROM cannot be changed when the 32-bit mode is toggled on real Mac using the Memory control panel, the ROZ will always be in the format for which ROM resources were created for.

Debug dump of the “mapped” ROM resource map in Xcode, with Hex dump in 0xED shown below

For now, we just use the “fake” ROM to hold fonts and cursors, adding more stuff there as needed.

Trap Dispatcher, native interface, and Memory Manager

Although having graphical output in the framebuffer is neat, and for a graphical system such as Mac a required element, the most critical components of the entire Toolbox emulation are definitely the Memory Manager and Trap Dispatcher:

Trap dispatcher

In a rather clever way, Macintosh system programmers used the A-line (line 1010) handler in the 68K CPU to implement all the Toolbox API calls. How this works is that any 68K instruction, which is of the hex form Axxx (from A000 to AFFF), triggers an A-line exception in the CPU. In this situation, the CPU looks up the exception handler routine address from vector table at index #10 (32-bit address at low memory location 0x28), sets up the exception frame, and calls the exception handler.

On the Mac, the trap dispatcher is responsible for handling these A-line exceptions. The actual A-line opcodes, which triggered the trap dispatcher, are divided to OS traps and Toolbox traps based on bit 11, which are handled differently based on their type. More detailed information about how trap dispatcher works is available in Part 3A of the Mac Almanac II at http://www.mac.linux-m68k.org/devel/macalmanac.php

Native interface

As the Toolbox will be implemented in C, another challenge is interfacing the emulated 68K code with C, especially due to the requirement that a 68K application might want to add their own trap handlers, and also call existing native handlers from the 68K code! To solve this, luckily Apple already did a lot of groundwork by introducing the Mixed Mode Manager in the early 1990s, to help interfacing 68K code with the PowerPC code when Macs switched from 68K to PowerPC CPUs. In this emulator, we will attempt to implement the native calls using Mixed Mode-type UPPs, adding the C calls as a completely new “emulator” ISA type (in addition to Apple’s predefined 68K and PowerPC ISAs).

Memory Manager

With the previously added 32 kilobyte RAM block, we could start work on the Memory Manager. As stated in the goals, the first phase aims for 24-bit compatibility, as the “Classic Mac” macs are anyway limited to 24-bit addressing due to the 68000 CPU’s 24-bit bus, and a lot of software from that era is also not 32-bit clean. The groundwork for Memory Manager was done in such a way, that adding 32-bit support next to the 24-bit implementation should be relatively easy. At this point, we don’t yet have a working trap dispatcher, or a 68K CPU for that matter, but rather use direct C method calls to the required Toolbox API calls.

Output from the DumpZone call of one of the first 24-bit memory manager C prototypes

Laying out the Foundation

The very first step was to figure out how to handle the most core abstraction of the emulation; how to handle memory interfacing. The Mac has a very strict memory map layout, with 68000 vectors residing in the beginning of address space, and all Toolbox low memory globals following them mixed with trap dispatcher routine addresses.

In a pure 32-bit environment, a cheap option might be just to allocate a contiguous area of memory to use as the emulated RAM, and offset the accesses so that emulated address 0x0 (zero) would be actually first byte of this memory block, and access any memory addresses from native code by just offsetting the native pointers. However, the recent transition to 64-bit causes new kind of headache; emulated pointers would be 32-bit while native pointers would require a double the space of 64 bits, not only requiring the offset, but also conversion from different data storage size.

To solve this, a “virtual” memory mapping scheme was devised, in which the emulated RAM would be divided into a number of “blocks”, each of which would have its own handler routine, which would handle the memory access. An additional benefit of this is, that for example VIA space can be mapped to a different handler, which would immediately trigger changes to emulated VIA registers without need to poll them from the generic system RAM; additionally, emulated “fake” ROM space could be implemented to have read-only access.

With this vision, we got a very simple Cocoa-based test app running, which did not yet emulate a real Mac address space, but rather a fixed 32KB RAM and 38KB VRAM for 640×480 monochrome buffer, with a static predefined test image (actually converted to C header with GraphicConverter!) shown in the video below:

A simple 640×480 monochrome framebuffer with static pre-defined raw image being scrolled

Mapping the goals

For a project with a scope as large as this one has, setting goals and planning how to get there is very important. Here’s a rough outline of what we actually want to achieve:

  • Some level of compatibility with all apps ranging from early apps for 128K Mac in 1984 to the last PowerPC apps in early 2000’s
  • Ability to run apps either:
    • Classic-style seamless integration with any host operating system desktop. This would be for any apps/games which use windows and adapt correctly to various desktop sizes.
    • Full-screen exclusive mode. Mostly for full-screen games/apps, and apps designed for fixed-resolution screens such as most of the early games.
  • Sound emulation either through low-level Classic-type sound hardware (often used in early monochrome games), and high-level Sound Manager emulation (for games/apps from the later times).
  • Provide a one-click solution to play any compatible favourite game as a pre-built Mac OS X .app package.

With these goals in mind, this project is divided to various phases, each of which can be considered as kind of a milestone:

  • Phase 1: Motorola 68000 CPU emulation, with “Classic Mac”-type hardware emulation with monochrome-only QuickDraw on 512×342 screen in non-seamless mode, 24-bit Memory Manager, “Sound Driver”-compatible hardware sound emulation and file system emulation, System 6.0.7/7.1 level Toolbox API compatibility.
  • Phase 2: Later 68020/30/40 emulation, with Slot Manager-type video device emulation, 32-bit Color QuickDraw and Sound Manager emulation, better System 7.1/7.5.x API compatibility. Optionally 32-bit Memory Manager support.
  • Phase 3: PowerPC emulation, Mac OS 8-level Toolbox API’s (Appearance, Game Sprockets, Networking).
  • Phase 4: Mac OS 9-level Toolbox API’s, OpenGL compatibility.

The Phase 1 and 2 seem to be most realistic, but if (when!) we actually reach those milestones, we can revisit the plans and see how feasible the rest of the goals look at that point.