I closed out 2020 by piecing together a minor update to Pippin Kickstart (then spent most of January writing this blog post ). Version 1.1 patches the SCSI Manager on Pippins with ROM 1.0 (most models with a white case) so that they too may boot from SCSI devices other than the internal CD-ROM drive, such as an external hard drive. If you have a 1.2 or 1.3 Pippin and are happy with Pippin Kickstart 1.0.x, then 1.1 adds no new functionality other than some cute graphics (see below).
Download it here: pippin-kickstart-1.1.zip. Extract and burn pippin-kickstart-1.1.iso to a CD-R using the software of your choice.
Source code is available here, licensed under the GPLv2: https://bitbucket.org/blitter/pippin-kickstart
The Pippin was Bandai and Appleās ill-fated collaborative attempt to break into the video game console market by marrying two things I love: Macs and video games. Bandai launched the Pippin in 1996 amid fierce competition from other fifth-generation consoles like the Panasonic 3DO, Sega Saturn, Sony PlayStation, and Nintendo 64. Based on Macintosh technology, the Pippin is capable of running Mac software and vice versa, but Apple built some software-based security into the Pippinās boot process making it difficult to use the Pippin just like any another Mac. In part because of its high price and lack of developer supportāboth internally and externallyāthe Pippin was considered a commercial failure and Apple subsequently canceled the project in early 1997, with Bandai following shortly after in 1998.
I grew up playing games on Macs through the 90s and early 2000s, so Iāve always had a soft spot for the classic Mac OS. I learned how to program on a Mac and nurtured those coding skills over several years, which I later parlayed into a modest career in video game development. All the while I noticed that while other vintage consoles were getting renewed attention due to burgeoning homebrew developer scenes of their own, the poor Pippin was being left out in the cold. By the late 2010s I figured that since nobody had paid much attention to Appleās foray into video games, then I may as well, especially given my nostalgia for classic Mac gaming. So I cracked its signing key in May 2019 and shortly thereafter released a boot disc called Pippin Kickstart that made it easier for folks to test their own Pippin CD-ROMs.
When I released Pippin Kickstart 1.0.1 at the beginning of July 2019, I thought it was a done deal. Owners of 1.2 and 1.3 Pippins could boot from any SCSI device they wanted, and 1.0 Pippin owners could boot from any CD-ROM they wanted, owing to its lack of support for other SCSI devices. Unsigned booting was finally a reality on the Pippin, and I could rest easy not having to worry about this little project anymore.
That is, until September 2020, when @LuigiThirty on Twitter picked up a 1.0 Pippin and wrote about this obstacle she encountered while working on some homebrew:
Not only was her Pippin refusing to boot from a known-good external SCSI hard drive, but the drive wouldnāt work with her Pippin at all. Hard drives are the earliest and most basic SCSI storage devices available for Macs, but even when unformatted or without drivers installed, formatting utilities should still recognize that a SCSI device is attached and available for use. Perhaps the 1.0 Pippinās ignorance of external SCSI devices had less to do with driver support in ROM and more to do with artificial limitations. As a hacker, artificial limitations offend my sensibilities, so I revisited Pippin Kickstart with an aim to do something about this.
The Problem
The consumer model of the Bandai Pippin is designed to boot exclusively from its internal CD-ROM drive. In late 1996, a revised ROMā1.2āwas offered as an upgrade allowing the use of external SCSI devices, but particularly the Deltis 230 MO Docking Turbo which provides a magneto-optical drive ādockedā to the underside of the Pippin. A developer dongle could be attached to Pippins equipped with ROM 1.2 to enable booting from external SCSI devices. But the earlier ROM 1.0 completely ignores all SCSI devices (and dongles) other than the internal CD-ROM, precluding altogether the useānot to mention bootingāof external SCSI volumes. Signs point to the ROMās low-level SCSI Manager as the culprit, since while mandatory initial booting from CD-ROM is standard across all retail Pippins, only ROM 1.0 refuses to see additional SCSI devices after the Pippin has fully booted.
However, the āGM Flashā ROM supplied with developer and test Pippin kits is nearly identical to the final 1.0 ROM, save for a few minor changes to enable debugging. Indeed, the GM Flash ROM has an Open Firmware timestamp of 1996-01-28, while the 1.0 ROM has a corresponding timestamp of 1996-01-29ājust one day apart, suggesting that the two ROM versions were built from the same codebase. Among the differences, the GM Flash ROM can enumerate all SCSI devices at all times and may attempt to boot from any of them.
The Solution
All known Pippin ROMs load their SCSI Manager code from a ānittā resource located in ROM. Upon closer examination it appears that the ānittā resources with ID 43 (hereafter referred to as āānittā 43ā; SCSI Manager 4.3 was codenamed āCousin Itt.ā) in the GM Flash and 1.0 ROMsāwhere the SCSI Manager code is storedāare the exact same size and, aside from timestamps, differ by only nine bytes. These nine bytes make up code that check a SCSI deviceās ID to determine whether or not it should be considered. In the GM Flash version, this code verifies that the ID is between 0 and 7 inclusive (all legal SCSI IDs), whereas in ROM 1.0, this code only passes devices with an ID of 3, the internal CD-ROM drive. Given that the GM Flash and 1.0 ROMs are so closely related, itās reasonable to hypothesize that the 1.0 ROM can use a SCSI Manager from the GM Flash ROM. Replacing ROM 1.0ās ānittā 43 with the GM Flash version should therefore be the most straightforward fix. Barring that, patching those nine bytes to match those of the GM Flash ROM should be sufficient to make ROM 1.0ās SCSI Manager functionally equivalent to that of the GM Flash ROM.
Finding The Problem
Pippin Kickstart was first conceptualized as a custom SCSI CD-ROM driver. My thinking was, since Macs automatically load device drivers from the first few partitions of an Apple-formatted disk, why would the Pippin behave differently? The longer story is written in my blog post about that, but the short answer is that the Pippin simply ignores patch and driver partitions. The Pippin has its own .AppleCD driver in ROM, which has to be loaded and active before it can boot, well, anything. Perhaps the thinking at Apple was, āsince .AppleCD has to be working to boot the Pippin into an OS, thereās no sense in patching it that early. Let the OS do that if need be.ā Since the signing process is only applied to the boot volume and no other partitions, maybe it was a conscious effort to block patches and custom drivers from executing their own unsigned code early enough to work around the Pippinās security. Whatever the reason, I discovered quickly that Pippin Kickstartās payload couldnāt sneak in through a back door.
Ultimately, I reverse-engineered the signing keys and implemented Pippin Kickstart as a simple bootloader, signed using Appleās private key so that it launches on any retail Pippin without resorting to any sneaky tricks. My own code implements its own boot candidate search loop, mimicking the loop in the Pippin ROMās Start Manager (in fact calling some support functions in ROM as necessary) but omitting an authentication and driver check. I implemented my own loop because the ROMās loop, being read-only, canāt be patched in-place. But since the ROMās search loop is part of the early startup code, frozen in ROM rather than loaded as a resource, the locations of code in ROM needed by Pippin Kickstart are always at known, fixed addresses. They never change, so I can hardcode them directly into Pippin Kickstartās logic.
Itās a different story with the SCSI Manager. The SCSI Manager is a low-level library used to enumerate and wrangle access to any and all SCSI devices attached to a Mac or Mac-based system like the Pippin. If you want to get into the party where the SCSI devices are, the SCSI Manager is both the bouncer and the emcee. In order to get to the point where any user-provided codeāincluding Pippin Kickstartācan be loaded at all, it has to be read from a drive, a process which the ROM starts by first querying the SCSI Manager for where and how it can ask a drive for anything. The SCSI Manager therefore has to be loaded and active before the Start Manager even checks to see if it can boot from anything. Furthermore, as I point out above, the 1.0 ROMās SCSI Manager appears to reject external devices even after weāre done booting, so the SCSI Manager has to persist as long as it may be in use.
The Pippinās earliest boot code in ROMāfrom the time it powers onāexecutes natively on the PowerPC, configuring some low-level hardware and initializing a 68K emulator. But soon after that it enters the emulator to launch the boot code located in the Toolbox, which is predominantly written in 68K assembly. In fact most of the Pippinās ROM targets the 68K instruction set architecture (or āISAā), but some portions target the Pippinās native PowerPC ISA. To understand why this startup code (and, by extension, Pippin Kickstart) runs in emulation rather than natively, we need to look back in time to when the Pippinās software was being designed.
In 1994, Apple released their first PowerPC-based Mac. The Power Macintosh 6100/60 sports a PowerPC 601 processor running at 60 MHz and shipped with System 7.1.2. The development of the 6100 is a very interesting tale, with some parallels to how the most recent Apple Silicon-based Macs came to be, particularly when it comes to emulation. The Mac had a relatively rich library of both first-party and third-party software, a relatively mature OS that was going to be ten years old by the Power Macsā release, and a developer community that was used to working with the Mac and how to flex its muscles. Throwing that all away and starting up again from scratchāespecially with Windows 95ās release on the horizonāwould not have made the best business sense given the short period of time in which Apple had to make the transition. Thus the decision was made to use emulation to run the Macās existing software libraryāincluding most of its operating systemāon the new PowerPC-based machines, with the idea that modules of the OS could be replaced piece by piece over time rather than all at once. In turn, existing software and development knowhow would continue to retain its value, and developers were under less pressure to produce PowerPC-native versions of their software right away. This strategy paid off in a big way; the new Power Macs were a hit, and soon became the basis for the Pippin platform.
Due mostly to time pressure, only the most-often used portions of the Macās System Software were rewritten as native PowerPC code for the initial lineup of Power Macs. Namely, QuickDraw was given the native treatment, while components that still had many 68K dependenciesāsuch as the SCSI Manager and disk drivers that had heretofore been written to work with it (targeting the 68K, mind you)āwere left emulated, for the time being anyway. It didnāt take long for Apple to let the SCSI Manager into the native club. Just 15 months after the first Power Macs hit the market, the Power Macintosh 9500 arrived on the scene in June 1995, utilizing the PCI standard as the first of the āsecond-generationā Power Macs and featuring a native SCSI Manager 4.3 built into its ROM. The new PCI-based Power Macsāparticularly the Power Mac 7500ālent many of their features and specifications to what eventually became the final hardware and software design of the Pippin.
The opportunity to transition to a new processor architecture in turn gave Apple the opportunity to learn from the effects of previous design decisions and implement more modern corresponding changes. The original Macintosh operating system was designed in 1983 to run one application at a time on a computer with no built-in storage, 128 kilobytes of RAM, no virtual memory, and a 68000 processor which was limited to relative branches up to a range of 32 kilobytes in either direction. User-provided 68K code must be loaded into RAM before it can be executed; Macintosh engineers invented the Segment Loader as a sort of virtual memory to allow larger applications to be broken into code resources swapped in and out 32K at a time. Enterprising hackers later figured out how to work around this to get much larger segment sizes, but segments still have to be loaded into RAM regardless. Generally, all the code associated with a 68K application has to live in that application. āDynamicā or āsharedā code libraries were not explicitly supported by the operating system, so the best one could hope for were system extensions/patches offering either new official system APIs, or third-party de facto standard interfaces informally agreed upon by multiple applications. This was not the most stable environment in which to develop scalable software.
By contrast, the upcoming Power Macs would feature cooperative multitasking with System 7, paged memory support, an internal hard drive, multiple megabytes of RAM, and a completely different ISA which could support much larger branch sizes. Apple had almost ten years of Macintosh development experience by the time they began designing software for the upcoming Power Macs, and had taken lessons to heart in terms of how to improve their operating system to better scale to modern software development practices. The new PowerPC code that would run natively on the improved operating system would not use the old-style Segment Loader. Instead, code blocks on the hard drive could be mapped and executed directly as pages in memory without having to copy them to physical RAM. Self-contained blocks of PowerPC code, known as āfragments,ā are defined in terms of multiple āsectionsāāboth code and dataāand could be paged/loaded into memory once and then optionally shared with multiple applications, either as application plugins or standalone libraries in their own right. Each fragment could have its own block of globals and constants loaded into RAM for it to use, with their initial values specified in the fragmentās definition. The standard interface in the Mac OS to fetch these fragments and their entry point(s) at runtime became known as the Code Fragment Manager, or āCFM.ā
There are three ways to load a fragment, and the CFM provides three respective functions to accomplish each one:
68K application code lives by and large in that applicationās resource fork, leaving the data fork typically empty; one or more āCODEā resources are swapped in and out at runtime by the Segment Loader, using a jump table in āCODEā 0 as an initial reference point. To support āfatā binaries that can run on 68K Macs but also natively on Power Macs, a PowerPC-aware application stores its code in the otherwise unused data fork of an application, using a ācfrgā resource as an initial reference point. When booting from ROM, there is no concept of āresource forksā or ādata forksā until Mac filesystem code is loaded, but the Resource Manager doesnāt require resources to live in a file to be located and used. There exists a provision to load resources from a resource map located in ROM instead, and this is how the native SCSI Manager and other modular components are loaded despite the rest of ROM running in emulation. GetMemFragment
is therefore the function used by the ROM; the SCSI Manager isnāt a shared library (at least not by the CFMās definition), and we obviously canāt call GetDiskFragment
until we have the ability to read from disk. During startup, the ROM asks the Resource Manager for a handle to the ānittā 43 resource in ROM, and then that resource is passed to GetMemFragment
to prepare the fragment contained in that resource.
Comparing the ānittā 43 resources of the GM Flash and 1.0 ROMs reveals that, aside from timestamps in the fragmentsā respective headers, the two resources differ by only nine bytes in four places. Curiously, three of those nine bytes are replaced by the value 3 in the 1.0 ROM, where they originally have the value 7 in the GM Flash version. The SCSI ID 7 is the upper bound of what IDs may be assigned to devices (7 is always reserved for the host in Appleās implementation), whereas ID 3 is that of the internal CD-ROM drive. I suspected this might have something to do with why only the CD-ROM drive is recognized by the 1.0 ROM, but without knowing what the other changed bytes correspond to, I couldnāt know for sure. Iād have to run the two versions through a disassembler.
Finding a sufficient PowerPC disassembler was somewhat of an adventure in itself, but I wound up circling right back to something that I had long since installed on my G3 Power Mac: Appleās own Macintosh Programmerās Workshop, or āMPW.ā I didnāt know this, but MPW comes with a tool called DumpPEF specifically designed to tear apart fragment containers for analysis. The best part: it includes a very good PowerPC disassembler. All I had to do in MPW Shell was pass along the contents of ānittā 43 as a data-fork-only file and redirect DumpPEFās output to a text file, like so:
DumpPEF -do All -ldr All -pi u -dialect PPC601 -fmt on -v "Arthur:Development:Projects:Pippin Kickstart:1.1:nitt43" > "Arthur:Development:Projects:Pippin Kickstart:1.1:nitt43dump.txt"
I know 68K assembly much better than I do PowerPC, but teaching myself just enough PowerPC to understand what goes on in the SCSI Manager was not as bad as I thought it might be. Matching up the offsets where bytes differ to their corresponding locations in the disassembly, I quickly confirmed my suspicions. If Appleās SCSI Manager 4.3 Reference guide (and the short amount of time it took Apple to build a native version) is any indication, the SCSI Manager itself was originally written in C. According to Appleās own documentation, then, one of the common structures used by the SCSI Manager is called a āDeviceIdent
,ā containing among other things the ātargetID
ā of a particular SCSI device.
If we look at the last two differences as one code āsite,ā making the total amount of differences map to changes at three code sites, then in the GM Flash ROM, the SCSI Manager appears to be doing the equivalent to this C code at all three sites:
if (devIdent.targetID > 7)
where target ID 7 is the upper bound of what IDs are legally allowed as mentioned earlier. Translated into English, when a request for a SCSI action comes in, the SCSI Manager looks to see if the intended deviceās ID is out of range, and if so it refuses the request.
Contrast that with this C code, equivalent to what the 1.0 ROM does at each code site:
if (devIdent.targetID != 3)
Notice the difference? āIf a deviceās ID is not 3, refuse the request.ā This is clearly why no other SCSI devices are recognized by the 1.0 ROM; the low-level code responsible for routing SCSI requests flat out refuses to do so unless itās to or from a device with ID 3. I had figured out where the problem is, and fortunately, Apple already showed me how to fix it by way of how the GM Flash ROM behaves. The next logical step then was to develop a patch.
Implementing The Solution
The most obvious way to patch the SCSI Manager would be to burn a patched 1.0 ROM. In theory itād be easyāthe ānittā 43 resource is the same size in both the GM Flash ROM and the 1.0 ROM. From a content perspective itād literally just be a copy/paste job, but Iām primarily a software guy, and Iād rather lose just my time debugging software than lose my time and money feebly trying to make and support a reliable physical tool all while out of my wheelhouse. Acquiring, programming, and installing a custom Pippin ROM board can not only be intimidating to a casual collector/homebrewer (including yours truly), but also significantly more expensive (and legally questionable) than burning Pippin Kickstart to a CD and running it on stock hardware. Besides, if I was to burn a new ROM anyway, why would I stick with 1.0 when I could use the much more fully-featured version 1.3 instead? Pippin Kickstart is a free, open, and purely software-only utility, so I think itās worth trying to patch in software. The fix should only cost the price of a blank CD-R.
When GetMemFragment
is called to prepare the native SCSI Manager fragment in ROM, no code is copied or moved around in memory. The ānittā 43 resource stays right where it is and the SCSI Manager is executed directly from its home in ROM. How then does one patch this read-only code in software? Is it even possible?
Writing into read-only memory is out of the question for reasons that should be obvious. What about replacing the SCSI Manager with my own implementation? In order to cleanly install my own replacement, I would have to shut down and clean up the existing ROM-based SCSI Manager so as to make sure no remnants remain. Is this possible? I donāt know. The SCSI Manager is designed to remain permanently resident, so while I know a SCSI Manager system extension exists for older 68K Macs, I donāt know how or when it installs itself and furthermore, I couldnāt find a mechanism by which the PowerPC-based Pippin could accomplish the same task at boot time. So thatās out.
Hang on. If, hypothetically, the CFM loads a fragment from ROM that depends on a fragment thatās loaded from RAM or from disk, how does the ROM fragment know howāand whereāto call into those dependencies? What about non-ROM fragments that in turn depend on ROM fragments and other non-ROM fragments alike? It would make sense for the CFM to keep track of these inter- (and, as weāll see, intra-) fragment locations in a unified way.
Each loaded fragment has at least one associated ādataā section allocated in RAM. This section may contain globals or other statically-initialized data referenced by the fragment, but the data section also contains a special area called (by IBM) the āTable of Contentsā or āTOC,ā though thatās a bit of a misnomer. Apple says that the TOC is more like an address book, acting as a lookup table for functions and data living both within a fragment and outside that fragment. Each fragment has its own TOC, so before a routine in another fragment is called, PowerPC register GPR2āotherwise known as the āRTOCāāis saved, then preloaded with the bottom of the destination fragmentās TOC so that the fragment knows how to find its own globals and data. The calling fragmentās RTOC is restored upon return of the called routine.
A fragmentās code must be position-independent; that is, it should be able to be loaded into and run from any address. Therefore, a fragmentās code section does not usually reference hardcoded memory locations. Instead, it fetches an address it needs from its TOC at runtime by looking up that address in its TOC and reading it from RAM. It does this by using the RTOC register plus a known offset as an index into its TOC. The CFM is responsible for preparing and maintaining these addresses in the TOC at fragment load time, and at any time the loaded fragment or any of its dependencies have to be relocated in memory.
When pointing to data, a TOC entry is just a pointer to that raw data. But when pointing to a routine, because that routine could be exported from a fragment (someone can call us) or imported from another fragment (weāre calling someone else), it needs to know at minimum where its TOC lives in RAM, so a simple raw pointer to the routine is not enough. Enter the ātransition vector.ā A transition vector is very simple: it contains at least two pointers, the first being the address of the routine within the fragment, and the second being the address of a fragmentās context. In most cases, a fragmentās TOC provides enough context, so the second pointer is used to prepare RTOC immediately prior to entering the routine. A transition vector may optionally contain other fields at the discretion of the compiler and environment used to build the fragment; the only expectation is that it contains at least the first two.
A strange environment
Transition vectors must contain at least two pointersāone to the routine itself and one to its contextābut the SCSI Managerās transition vectors each contain three pointers. The third pointer is unused, but is designated as an āenvironmentā pointer. Early PowerPC Mac development was done on IBM RS/6000 workstations, so this vestigial āenvironmentā pointer may have come from that early toolchain.
If youāve been paying attention so far, you might be able to figure out where this is going. The SCSI Managerās transition vectors all point to code in ROM, because ānittā 43 itself is not loaded into RAM and thereās no problem executing its code directly from its home in ROM. But the transition vectors themselves live in RAM, which means they can be changed. Patching the necessary transition vectors in RAM is tantamount to patching the routines that the SCSI Manager itself exports to be called by the OS. So naturally, the next question is, how do we find those transition vectors?
Calling GetSharedLibrary
, GetDiskFragment
, or GetMemFragment
prepares a fragment (if found) and returns a āconnection IDā to that fragment. Each time an interface is established to a particular loaded fragment, itās called a āconnectionā to that fragment. Connections are reference counted and when all connections to a particular fragment have been closed, that fragment is unloaded. All three of the āGetFragmentā APIs create a new connection and each takes a parameter called findFlags
that can equal one of three values:
kLoadLib
: load a fragment if itās found and not yet loaded. If it is loaded, create a new connection to the already-loaded fragment.kFindLib
: find a loaded fragment. If it is loaded, create a new connection to the already-loaded fragment. If not, returnfragLibNotFound
.kLoadNewCopy
: load a fragment if itās found and not yet loaded. If it is loaded, create a new connection to the already-loaded fragment but also create a new data section specifically for this connection.
The CFM provides the FindSymbol
API for locating a symbol in a fragment by name, given a connection ID. After preparing the SCSI Managerās native fragment, the ROM calls FindSymbol
to find the transition vector for the SCSI Managerās āInitItt
ā entry point, then calls it to begin executing the native SCSI Managerās code.
Hmm. In the SCSI Managerās case, a connection is created at startup, but it is never closed at any time thereafter, ensuring that the SCSI Manager is never unloaded. Could a new connection be established to the SCSI Manager / ānittā 43 fragment-as-resource, then a known symbolāperhaps its entry pointābe used as a reference to poke around in the rest of the fragmentās data section, including its transition vectors?
This seemed like the most āpoliteā way of getting at the SCSI Managerās data section, so I tried this first.
move.w #0xFFFF, (RomMapInsert)
subq.l #6, %a7 /* make room for GetResource's return handle (4 bytes) */
/* and GetMemFragment's return value (2 bytes) */
move.l #nittRsrcType, -(%a7)
move.w #43, -(%a7)
_GetResource
movea.l (%a7)+, %a4
move.l (%a4), -(%a7) /* Ptr memAddr */
subq.l #4, %a7 /* make room for SizeRsrc's value in length */
/* (4 bytes) */
move.l %a4, -(%a7)
_SizeRsrc
clr.l -(%a7) /* Str63 fragName */
pea 1 /* kLoadLib /*
pea 0x1A(%a7) /* ConnectionID* connID */
clr.l -(%a7) /* Ptr* mainAddr */
clr.l -(%a7) /* Str255 errName */
move.w #3, -(%a7) /* GetMemFragment */
_CodeFragmentDispatch /* we'll assume it succeeds... */
fragLibConnErr
Would that it were so simple. The use of the findFlags
parameter is documented for GetSharedLibrary
and GetDiskFragment
, but the documentation for GetMemFragment
just refers to the documentation for GetDiskFragment
for how findFlags
is used. Despite Appleās redirection, passing kLoadLib
to GetMemFragment
will not create a new connection to an already-loaded fragment at an address previously provided to the CFM. There would be no going in through the front door. Damn. Iād have to sneak in through another way.
The classic Mac OS memory map is split into several areas. There are low-level system globals near the bottom of the address space, a system heap for use by the OS above that, and the remaining RAM is comprised of one or more fixed-size application heaps (and stacks) belonging to running applications. Originally, the system heap was fixed in size, with the remainder of usable RAM reserved for the application heap and stack. Double-clicking an application from the Finder would close down the Finder and the newly-launched application would take its place in the application heap. The reverse would occur when the application closed down, relaunching the Finder in its stead. As this original design was built around running one application at a time, later some technical gymnastics were achieved to add multitasking to the system while maintaining backward and some level of future compatibility with Mac apps. The original Memory Manager APIs and low-memory globals were therefore left mostly unchanged, including a well-known global containing the base address of the system heap.
As is mentioned earlier, the SCSI Manager is designed to remain permanently resident, so it makes sense that its fragmentās data section lives in the system heap. Opening and closing applications has no effect on the existence of the SCSI Manager. Indeed, launching applications often involves the SCSI Manager to fetch those very applications from disk; the SCSI Manager is a core component of loading code on the Pippin. Furthermore, since the Pippin needs to know at all times how to load additional code and data from disk, those exported transition vectors need to be at fixed locations in memory in a nonrelocatable block of RAM. Of the many low-level Memory Manager structures documented early on by Apple, the system heap is one of them, so we can find the SCSI Managerās data section in the system heap by doing a brute-force linear search.
Reading the SysZone
system global gives us the address of the beginning of the system heap. The system heap āzoneā begins with a zone header block for various bookkeeping tasks like keeping track of its size, flags, which blocks are free, and other internal uses. Immediately following the zone header are the contents of the heap itself. Likewise, each block in a classic Mac OS heap starts with a block header, describing among other things its size in memory.
Immediately following the block header are the blockās contents. By adding each blockās size to its respective headerās address, we can step through each block of the system heap, inspecting each blockās contents along the way.
movea.l SysHeap, %a0
lea heapData(%a0), %a1 /* A1 -> allocated block in system heap */
NextSysBlock:
cmp.l bkLim(%a0), %a1 /* bkLim(A0) -> system heap trailer block */
beq.w SkipPatching /* if this block is the trailer, we've searched */
/* the entire system heap and couldn't find the */
/* SCSI Manager's pidata section */
movea.l %a1, %a4
move.l blkSize(%a1), %d0 /* D0 == physical size of this block */
add.l %d0, %a1 /* A1 -> the next block in case we skip */
As well as being a handy PowerPC code fragment disassembler, another nifty facility DumpPEF provides is the ability to examine how a fragmentās data section is initialized. The data section is of a fixed size and, as discussed previously, the SCSI Managerās data section starts with a Table of Contents, followed by a list of transition vectors. These transition vectors are the bytes weāre looking to patch. But in the SCSI Managerās case, after the transition vectors comes a series of text string constants. These strings are always in the same location relative to the beginning of the data section and as read-only constants, they always have the same predictable values. Therefore I reasoned that in addition to verifying that a block within the system heap is of the same expected size as the SCSI Managerās data section, checksumming these strings of text within that block should provide a suitable heuristic for identifying a particular block as belonging to the SCSI Manager.
/* Verify this really is the SCSI Manager's block in the system heap. */
/* We do that by checksumming the area in the middle of this block where */
/* we know the SCSI Manager looks for some read-only strings. Use an */
/* algorithm similar to that used to checksum the Toolbox, only we'll */
/* walk our pointer backwards so we can use A3 as-is if/when it comes */
/* time to check our TVectors. */
ChecksumLoop:
add.l -(%a2), %d0
cmpa.l %a2, %a3
ble.s ChecksumLoop
cmp.l #scsiStrsCksum, %d0
bne.s NextSysBlock
Once weāve found our block, we know where the transition vectors live within it, so itās time to patch them, right? Well, first we need to create the patch itself. At the time Pippin Kickstart runs, there is no application heap yet. Application heaps arenāt set up until the Process Manager starts, which doesnāt happen until after the familiar āWelcome to MacintoshPippinā extension parade has completed and our first application is ready to launch. Therefore, the system heap is our active and only heap. Whatās more, as of System 7, so long as there is RAM available the system heap can grow dynamically to accommodate allocation requests, with the Process Manager shifting its base accordingly.
Our patch needs to stick around as long as the SCSI Manager exists, so naturally we need to give it a home somewhere where the OS wonāt stomp over it later. Since the SCSI Managerās data section lives in the system heap, and since Pippin Kickstart itself works from the system heap, it follows that we should be able to safely create a small block of nonrelocatable space in the system heap for our patch to live. Our patch really only needs to replace nine bytes in four locations, but we canāt just create a nine-byte block, stick our bytes there, and call it a day. The nine patched bytes belong to different functions in the SCSI Managerāfunctions that can and are referenced internally. Specifically, these functions are invoked internal to the SCSI Manager not by referencing transition vectors, but by good old-fashioned relative branching. It makes sense; the SCSI Manager targets the PowerPC and its functions run natively on the PowerPC, so why bother with transition vectors when you know youāre calling other PowerPC functions that are part of the same code fragment? Itās certainly convenient for the Pippin, but makes things slightly more annoying when creating this patch.
We have to ensure that no code that either leads to or leads from our patched locations can lead back to the unpatched versions in ROM. Therefore we have to account for relative branching by including all of that extra code in our patch, even though we donāt change any of it! I wrote a small C++ program to calculate exactly how much code Iād have to copy by essentially āemulatingā PowerPC branch instructions, keeping track of the lowest and highest reachable addresses and using the āblr
ā instruction as a heuristic for the ends of subroutines. Passing my program a line-by-line disassembly of the SCSI Managerās code section cut from DumpPEFās output, I started the āemulationā at each of the three code sites and noted which one could be reached by the largest range. It turns out that all three sites lie within a mere 35K of code that only calls into itself; the rest of the SCSI Managerās code section appears to be helper functions or routines unrelated to the SCSI Managerās ācore.ā
The earliest address of our 35K block is offset 0xB854 into the SCSI Managerās code section. The transition vector in the SCSI Managerās data section with the earliest offset that should call into our patch is the vector that points to offset 0xBAD4. We know this transition vectorās index into the data sectionās list, so by reading its target address and subtracting an offset (0xBAD4 ā 0xB854), we can get the starting address in RAM of the code block to copy into our patch area. We also know exactly how much code to copyā35536 bytesāso the procedure becomes rather simple: copy our 35K of code into a nonrelocatable block on the system heap, then patch that. We can also easily determine which transition vectors point within that original 35K of code, so we know exactly which transition vectors to patch. It turns out that the vectors, starting with the one pointing to offset 0xBAD4 through the end of the data sectionās list, all need to point into our patched code. By calculating the difference between offset 0xB854 into the SCSI Managerās code section, and where our 35K block is in RAM, we get an offset value that makes it trivial to patch the transition vectors. We simply add that offset to each of those transition vectors so that they then point into our patched code instead of into ROM.
/* now let's patch up the TVectors to point to our patched code */
move.b #tVectorsSize-1, %d0 /* # of TVectors to patch minus one */
TVectorLoop:
movea.l (%a4), %a2
suba.l %a0, %a2
move.l %a2, (%a4)+
addq.l #8, %a4
dbra %d0, TVectorLoop
After all of that, weāre still not quite done. All PowerPC processors have some form of a ādata cache.ā When you make changes to RAM on a PowerPC architecture, those changes arenāt necessarily written to RAM right away. Instead, the address decoder checks first to see whether where youāre reading/writing has been ācached,ā or saved in a smaller but faster block of memory within armās length of the processor. Cache is to RAM what RAM itself is to System 7ās virtual memory; it is prioritized as a faster alternative to its counterpart, and when thereās no space left its contents are āflushedā to make room. Consequently, writing to a particular address will often write only to the cache instead, anticipating that its contents will be referenced again shortly thereafter.
To further complicate matters, the cache on the PowerPC 603 chip used in the Pippin is split between instructions (code) and data (not code). Weāre patching code in RAM, but the Pippin doesnāt know that; itās all just bytes of data as far as itās concerned. We want to make sure that our patched area is flushed to RAM so that when the SCSI Manager comes around to execute it next, those patched bytes are waiting in RAM ready for the instruction decoder to pick them up. Itās reasonable to assume that at the time in the startup process when Pippin Kickstart runs, our block of code in the system heap does not have corresponding entries in the instruction cache, but itās not necessarily safe to assume that our patched code will be automatically flushed to RAM before Pippin Kickstart exits. We certainly wouldnāt want the SCSI Manager to invoke the old unpatched transition vectors, or worse, execute whatever happened to be in RAM before we put our patched block of code there.
There exists an API called MakeDataExecutable
that does exactly what we want here. But in order for this API to work for us, weād have to make a connection to InterfaceLib, call FindSymbol
, call MakeDataExecutable
with the proper parameters, then close the connection to InterfaceLib. Thatās a lot of work for just one call. Fortunately, since Pippin Kickstart runs in the 68K emulator, thereās a faster and easier way, albeit undocumented.
movea.l %a1, %a0
move.l %d6, %d0
dc.w 0xFE0C /* undocumented F-line instruction that evicts our */
/* patched area from the PPC data cache into main */
/* memory so it's visible to the instruction decoder */
Appleās 68K emulator supports the features of a 68LC040 processor with a 68020 exception stack frame. The 68LC040 is like the more powerful 68040 processor powering the Quadra line, but minus the floating-point operations built into the latter. Floating-point operations on the ā040 are implemented by way of āF-line instructions;ā that is, instruction opcodes that begin with the hex digit F. But just because the 68K emulator doesnāt support floating-point operations doesnāt mean that the emulator doesnāt support F-line instructions. Elliot Nunn helpfully pointed out that one of the F-line instructions used internally by the 68K emulator has the opcode 0xFE0C and it does just what we want: it flushes the PowerPCās data cache to RAM. This instruction takes two parameters: a pointer in 68K register A0 to an area in memory, and a size in bytes in register D0. Easy peasy, if a little skeezy.
With that, weāre finally done patching the SCSI Manager so that it behaves identically to the version in the Pippin GM Flash ROM.
Bad F-line instructions
System error type 11 often manifests itself as a ābad F-line instructionā bomb dialog in System 7 and later. In System 6 this dialog instead displays the message ācoprocessor not installed,ā owing to the fact that F-line instructions can map to floating-point operations on an internal or external FPU. Despite suggesting that these errors stem from a missing FPU, very little software for classic Mac OS makes use ofālet alone requiresāfloating-point hardware. For the broadest compatibility, programs requiring floating-point operations either use their own integer math library or call into Appleās SANE math library instead, requiring no F-line instructions.
Usually these system errors are the result of a buggy program erroneously jumping into an area of data and interpreting it as code, setting off the bomb when carelessly stumbling upon a pair of bytes starting with the hex digit F.
Adding Some Fun
Pippin Kickstart runs from RAM after being loaded from the boot blocks, which are the first two 512-byte sectors of an HFS-formatted volume. I was able to squeeze versions 1.0 and 1.0.1 each into the first 512 bytes of this area. Keeping Pippin Kickstartās footprint limited to the boot blocks makes authoring the Pippin Kickstart disc relatively easy; I merely have to replace the boot blocks with my own, and since the Pippin loads them for me, I donāt have to make any other calls to load any additional code from the CD. Calls to the disk driverās _Read
āwhich Pippin Kickstart makes to check the first block of boot candidatesācan only return 512-byte chunks, so 1.0 and 1.0.1 use the latter half of the boot blocks as scratch space during their respective boot candidate search loops. With the aforementioned SCSI Manager patch going into Pippin Kickstart 1.1, I need more code than will fit in those first 512 bytes, but I still want to limit myself to the boot blocks for convenience. Keeping the code tight is a fun engineering challenge, too.
If I move the boot candidate search loop and its dependencies from the first 512-byte block into the second block, I leave behind enough room in the first block to patch the SCSI Manager. By the time Iām done patching the SCSI Manager, I donāt need anything from the first block anymore, so I can jump into the search loop in the second block and that first block can be used instead as scratch space. Other than the address of my scratch space, I donāt have to change any of my tested and working search loop logic from 1.0 and 1.0.1. Hooray!
But the SCSI Manager patch doesnāt take up a lot of space, certainly not a whole extra 512 bytes. All that extra unused space felt like a waste to me, but thereās nothing more that Pippin Kickstart needs to do to allow a stock 1.0 Pippin to boot from any capable SCSI devices. My code golf skills had gotten the better of me. How could I make meaningful use of those remaining bytes?
Pippin Kickstart has a very spartan interface, though perhaps itās a little too spartanāfolks have mistaken it for BSD and have asked me what ākernelā it boots into. I take that as a compliment and a testament to how much utility Iāve packed into such a small space, but I do admit that it could look prettier. My good friend Tommy Yune is an accomplished graphic artist who graciously drew up a Pippin Kickstart ālogoā (seen above) around the time I was working on the first versions. His graphics appear in the readme files I include on the disc. But other than the text Pippin Kickstart prints to the screen logging its behavior, the most anybody sees is the Pippin logo leftover from when the Pippin gets a fresh start.
Perhaps those extra bytes could translate into some extra polish.
Normally when the Pippin boots, it first draws the Pippin logo and looks for a bootable CD-ROM. If after a few seconds it canāt find one, it starts looping an animation suggesting that a CD be inserted into the built-in CD-ROM drive.
If the inserted CD is an audio CD, then the Pippin launches into its built-in audio CD player application. If the inserted CD is a data CD, then the screen goes black and the Pippin tries to boot from that disc. Many Pippin titles at this point put up a āStartupScreenā made up of the Bandai Digital Entertainment (the Pippinās first-party publisher) logo; some unofficial titles like āTusconā have a custom StartupScreen file. But if the Pippin cannot boot from a given data CD, then it is ejected and the Pippin reboots, drawing the Pippin logo again and repeating the cycle. Therefore if Pippin Kickstart is inserted during the tray-loading animation, you get the least interesting visual result: the screen goes black and nothing else is shown on the screen other than Pippin Kickstartās text console.
Weāll fix that in 1.1 by drawing Tommyās ālockedā Pippin logo, then drawing it āunlockedā after weāve successfully circumvented the Pippinās security.
Except for the Pippin logo itself, which we get from ROM, weāll do all of this using QuickDraw primitives. Drawing the Pippin Kickstart logo programmatically rather than storing it as a bitmap takes up a fraction of the space, which is important given that weāre drawing two versions of it and have less than 512 bytes available to pull it all off. To begin, we create a new āclip regionā that excludes the area where the Pippin logo is in the center of the screen. Since this is created in RAM at runtime, we have to clean it up before Pippin Kickstart exits, but itās needed to later tell QuickDraw that we want to allow drawing anywhere but where the logo is.
lea logoRect, %a3
subq.l #8, %a7
_NewRgn /* create clipRgn */
move.l (%a7), %d5 /* save clipRgn */
move.l %a3, -(%a7)
_RectRgn /* clipRgn == logoRect */
_NewRgn /* create tempRgn */
move.l (%a7), %d6 /* put tempRgn in D6 because D7 is our ROM index */
move.l %d6, (tempRgn - logoRect)(%a3) /* save tempRgn in RAM since D6 */
/* is used by the search loop */
_GetClip /* tempRgn == original clip region */
movem.l %d5-%d6/%a3, -(%a7)
move.l %d5, -(%a7)
_DiffRgn /* clipRgn == original clip region - logoRect */
We then set the background color to black (at this point itās notāblack is the foreground color and thatās what QuickDraw uses to initially paint the screen at startup) and erase the area inside the clip region we just created. This has the positive effect of erasing around the Pippin logo if it has already been drawn. We later draw the logo ourselves just in case, but either way this approach avoids some flicker when Pippin Kickstart launches.
pea blackColor
_BackColor
move.l %d5, -(%a7)
_EraseRgn /* erase around the logo */
Next we set the foreground color to white and draw a 7-pixel border around the logo area. Since we have to set the foreground color to white for the text anyway, we set it here so we donāt have to worry about it again.
pea whiteColor
_ForeColor /* draw white-on-black */
move.l #((outlineThickness << 16) + outlineThickness), -(%a7)
_PenSize
_FrameRect /* draw logo outline */
The Pippin logo is stored in ROM as 'PICT' resource -20137. It's trivial to find in ROM where the code is to draw the Pippin logoāsearch for a call to _GetPicture
(0xA9BC) and an instruction that passes -20137 (0xB157) to it. The logo-drawing routine is located in the same place in all three known retail ROMs: offset 0xE3C from the beginning of ROM. We call this routine to fill the outline with the Pippin logo if it hasn't yet been drawn. If it has, then drawing over the Pippin logo has no perceptible side effects.
jsr logoRoutineOffset(%a4) /* draw the Pippin logo from ROM */
We then have to draw the locked Pippin logo's "shackle." I created a routine for this purpose called, appropriately, DrawShackle
and placed it in the second boot block so that we can call it again to draw the "unlocked" logo when it's time to exit the search loop. DrawShackle
sets QuickDraw's clip region and then draws a framed rounded rectangle clipped to that region. The net effect is a shackle that appears inside the "locked" Pippin logo.
/* input: A3 -> shackle rect - 8 */
/* trashes: D0-D2, A0-A1 are scratch regs used by QuickDraw */
DrawShackle:
move.l %d5, -(%a7)
_SetClip /* clip shackle to logo */
move.l #((shackleThickness << 16) + shackleThickness), -(%a7)
_PenSize
addq.l #8, %a3 /* a3 -> shackle rect */
move.l %a3, -(%a7)
move.l #((shackleRadius << 16) + shackleRadius), -(%a7)
_FrameRoundRect
rts
Notice how DrawShackle
grabs the clip region handle from register D5. Luckily, none of the external routines called by Pippin Kickstart trash this register, leaving it available for temporary storage. The same is true of register D7, used by Pippin Kickstart as an index corresponding to the Pippin's ROM version so that we can call ROM routines from their proper locations. It is not true however of register D6, which is used by the ROM routines called by the search loop.
Now that the "locked" Pippin logo is up on the screen, we ready a clip region in register D5 that will be used to replace the "locked" shackle with one that dangles off to the side. We use the same clip region both to erase the existing shackle and to clip the "unlocked" shackle during the next and final call to DrawShackle
. The _SectRgn
API lets me calculate this region easily, finding the intersection of the existing clip region (set during the first call to DrawShackle
that allows drawing anywhere except the logo area) and a predefined rectangle enclosing both the intended area of the "unlocked" shackle and the area of the existing "locked" shackle. Even though my predefined rectangle overlaps the forbidden logo area, this isn't a problem because _SectRgn
finds the intersection of both drawable regions; that is, it calculates the region common to both. In the final clip region, only the shackle areas outside the logo will be affected.
addq.l #8, %a3 /* A3 -> clipRect */
move.l %d5, -(%a7)
move.l %a3, -(%a7)
_RectRgn /* clipRgn == clipRect */
movea.l GrafGlobals(%a5), %a0 /* A0 -> qdGlobals */
movea.l thePort(%a0), %a0 /* A0 -> qdGlobals.thePort */
move.l clipRgn(%a0), -(%a7) /* push existing clip region */
move.l %d5, -(%a7) /* find intersection with clipRect */
move.l %d5, -(%a7) /* make it our next clip region */
_SectRgn
The active clip region at this point is still what DrawShackle
uses to draw the "locked" Pippin logo, so everything outside the Pippin logo is still fair game as far as drawing goes. This includes text, so naturally we still get the familiar text console as Pippin Kickstart goes through its expected motions.
When it comes time for Pippin Kickstart to exit and boot from the candidate it finds, the string "Booting..." is printed and the existing shackle is erased, using the clip region that we prepared earlier. Dangling the shackle to the left side of the Pippin logo would overlap our lovely text console, so we call DrawShackle
to dangle an "unlocked" shackle off to the right instead.
/* Erase the "locked" Pippin. */
move.l %d5, -(%a7)
_EraseRgn
/* Draw an "unlocked" Pippin. */
lea unlockedRect-8, %a3 /* because A4 is our link pointer */
bsr.s DrawShackle
This "unlocked" shackle is missing something, or rather it needs to be missing something. In Tommy's logo, the shackle has a "notch" cut out of it, as would a shackle on a real padlock. Cutting a rectangular notch out of our shackle is simple enough; just erase a tiny rectangle where the notch should be.
pea notchRect
_EraseRect
There's no "notch" primitive in QuickDraw (nor is there a corresponding _DrawNotch
API), so drawing the slanted part of the notch requires a little bit of outside-the-box thinking. One option is to set the pen size to the notch width and draw a line between two points. That would work, but there's an even more efficient way, at least in terms of instructions used.
Among its supported primitives, QuickDraw can draw ovals, circles, and rounded rectangles. What do all these shapes have in common? They all involve drawing one or more arcs of a particular width, height, and arc length. Since ovals, circles, and rounded rectangles are themselves at least partially made up of arcs, QuickDraw also exposes the ability to draw just an arc through its _PaintArc
API. If we draw an arc at least half the height of the notch we erase with a length extending to the edge of the notch, we get the slanted part we need. There is a tiny bit of overdraw into the shackle area above the notch, but since both the shackle and the arc are drawn with the same white foreground color, it doesn't matter. In the end, using an arc instead of a line segment gets the job done in about half the required instructions, with even less overdraw than drawing a line segment would produce.
pea arcRect
clr.w -(%a7)
move.w #-45, -(%a7)
_PaintArc
Finally, we clean up after ourselves and exit. The clip regions we create are allocated dynamically in RAM, so to be a good citizen we dispose of those, but not before restoring the original clip region from prior to launching Pippin Kickstart. The next thing QuickDraw puts on the screen could be an alert, a StartupScreen, or something else entirely; it's up to whomever we hand off the boot process. The least we can do before we say goodbye is return the Pippin to a state reasonably close to how we found it.
/* Clean up. */
move.l %d5, -(%a7) /* push clipRgn */
_DisposeRgn
move.l -(%a3), -(%a7) /* push tempRgn */
move.l (%a3), -(%a7) /* push tempRgn */
_SetClip
_DisposeRgn
After adding these graphics, I'm back to having zero bytes left in both boot blocks. Waste not, want not.
Conclusion?
Apple Computer was willing to license their Macintosh technology to third parties by the mid-90s, but not at the expense of their own first-party products. While the initial line-up of Power Macs and clones were a success, Apple was still a computer company; it was right there in their name. Licensed Mac derivatives like the Bandai Pippin could not be allowed to cut into Apple's bottom line, so Apple took measures both technical and tactical to help protect against the Pippin cannibalizing Mac sales. The term "Mac" or "Macintosh" was never to be used publicly to describe the Pippin or its software; the Pippin runs "Pippin OS" and is based on "advanced technology by Apple Computer." Pippins with the first revision of the retail ROM have special code that explicitly blocks the use of storage devices other than those built into the system and those officially available at launch. But on top of that, as extra insurance against Pippins being used as "cheap Macs," Apple added a signing check to the startup process that verifies that a particular boot CD has been authorized for use on the Pippin.
There was nothing particularly novel about this approach in 1996, and in fact it's still in use today by almost all video game console platforms. Ever since the Nintendo Entertainment System's release in 1985, most consoles have some kind of protection against running unlicensed software. Atari's 7800 ProSystem from 1986 was the first video game console (that I know of) to use a boot-time signature check (with similarities to the RSA algorithm the Pippin would use ten years later). Like the Pippin and all major disc-based consoles that came after it, all 7800 titles had to be digitally signed for release. Given the amount of computing power required in those days to crack a digital cryptographic signature, and the amount of computing power typically available to the general public, these strategies were mostly effective for their time to prevent unlicensed software from affecting a platform's brand and public image during its supported lifetime, for better or worse.
Reverse-engineering and circumventing the Pippin's boot security wasn't easy, but with the exception of deducing Apple's private RSA key, Pippin Kickstart could have been developed using tools and documentation available in the late 90s and early 2000s. I am often nostalgic for the Mac games of my youth and feel that in modern times they deserve to be played on a "real" video game system on a big-screen TV. Thus the idea appealed to me of hacking an "Apple" video game system to let me do just that. Given the Pippin's place in history and how little attention it has received compared to homebrew efforts for systems from Atari, Nintendo, Sega, Sony, and Microsoft, I was surprised at the positive reception and interest Pippin Kickstart got when I first released it in June 2019. That folks are beginning to dip their toes into producing homebrew for this relatively obscure platform goes above and beyond my expectations; I'm absolutely delighted to have enabled this and I hope it continues.
It has been 25 years since the introduction of the Bandai Pippin. In that time, the Internet has exploded with a force that hardly anybody could have predicted back then, and Apple itself has gone from a relatively small player in the computer industry to one of the largest consumer electronics companies in the world. (Bandai is still doing OK, too.) The Internet has brought together fans of gaming from all over the world and from all kinds of backgrounds, fostering communities that celebrate video games and their technology from the mainstream to the obscure. The classic systems of yesteryear may have been forgotten by major retail outlets, but that doesn't mean they have been forgotten by fans and enthusiasts, no matter how obscure or commercially unsuccessful. Nostalgia is a powerful drug, and thanks to it I think there will always be an audience for new developments targeting these vintage consoles, by amateurs and professionals alike. These days, "retro" is cool, and I'm happy to contribute to the zeitgeist in my own small way.
If I've done anything to help rekindle some gaming nostalgia among fellow retro gaming fans, then it's all worth the while.
Pippin Kickstart, in any version, in terms of PowerMac CDs that I own, will only accept system software CDsā¦and then freeze/crash. I have 13 MB of total RAM, and yet Kickstart rejects Software Solutions Mega Mac Games CD which only requires 4 MB of RAM. None of my PowerMac LucasArts Archive Vol. 1 CDs are accepted either, which need 8 MB of RAM. Any help would be greatly appreciated.
Pippin Kickstart only circumvents the Pippinās security and locates boot blocks; you will need to provide appropriate media to boot the Pippin the rest of the way as Pippin Kickstart does not provide Appleās copyrighted Pippin OS. However, if you have a Power Mac equipped with Toast 5, you can rustle a copy of Pippin OS out of an existing Pippin title and customize it to your liking before authoring a bootable disc of your own. I recommend tracking down and using the āMacintosh on Pippin (Tuscon)ā disc as a starting point, as it contains one of the latest versions of Pippin OS before the project was shut down.
Thank you for the reply. I am in for a real challenge it seems. I donāt have a Mac, but I just got a Pippin game. Two last thoughts (on workarounds): 1. Using Tuscon, I donāt suppose a Pippin would accept an unmodified CD Mac game running off a CD drive from the Pippin floppy drive port (if a CD drive can even work from that port). 2. Could you not run the contents (rip a game then dump it to a SD card) of an unmodified CD (or floppy) Mac game on a FloppyEmu device via the Pippin floppy port.
The expansion port on the underside of the Pippin provides both PCI and floppy drive signals, though only enough power is provided to use one or the other at a time. When attached to a real Mac floppy drive or a Mac floppy drive emulator like the Floppy Emu, it will only recognize what appears to the Pippin as a floppy disk. The Pippin does not support the HD20 nor an emulated HD20, so configuring a floppy emulator to present a CD-sized disk image to the Pippin will not work.
Note that Pippin discs only need to be *authored* in Toast. You donāt actually need a Mac to burn them; you can āSave as Disc Imageā from the File menu (to a .toast file in Mac OS X; classic Mac OS wonāt add an extension), which most PC-based utilities will happily burn for you after renaming the file to have a .iso extension.
I just wanted to take a moment to thank you for all your hard work in making Pippin a better device. Your contributions are truly appreciated!
I have a couple of questions: Is it possible to remap the AppleJack controller to work with different Mac games? Additionally, what are your thoughts on fixing the save functionality in those Mac games that donāt allow directory changes? Is using a SCSI HDD the only option for that?
Thanks again for everything!
No problem!
The AppleJack controller was offered both for Pippin systems and for ADB-equipped Macs. In the latter case, the controller shipped with a floppy disk containing the AppleJack INIT and āAJ Controlā software. AJ Control is what provides the ability to map AppleJack buttons to keypresses, modifiers, and/or mouse buttons. It stores the mappings in a āpippā resource, and the AppleJack INIT is smart enough to read āpippā resources out of individual applications if present, so yes, it is possible to remap the AppleJack controller on a per-application basis.
Which games do you have in mind? Using the Standard File Package will ājust workā on Pippin (Pippin OS provides its own PippinStandardFile which Apple recommended, but the Standard File Package is also present regardless). Games that assume their applicationās folder is writable would need some hacking, though, to work from read-only media such as CD-ROM. But no Pippin titles that I know of make that assumption, so such hacks are best performed and verified first from Mac OS.