Exploring the Pippin ROM(s), part 3

Last week, I found the ‘rvpr’ 0 resource in the Pippin 1.0 ROM and the role it appears to play in the Pippin’s startup process. I noted that there are no ‘rvpr’ resources in the 1.2 or 1.3 ROMs, but after digging a little deeper I discovered that is only half true: the contents of ‘rvpr’ 0 are in fact present in both the 1.2 and 1.3 ROMs. But since there’s no entry for it in either ROM’s resource map, a call to _GetResource won’t find it. If 1.2 executes ‘rvpr’ 0 to perform the auth check, then the loading code I found last week must therefore be different in that version.

‘rvpr’ 0 itself appears to be a bit obfuscated. There are many subroutines contained within– if the Link / Unlk / Rts pattern is used as a heuristic, I counted over 250 of them. However, I suspect that much of this code is unused and/or intentional red herrings.

I quickly skimmed the code for an overall first impression before stepping through it in an editor. Some of the aforementioned subroutines are duplicates for some reason:

108A   4E56 0000    Link      A6, #$0
108E   2F2E 0008    Move.L    $8(A6), -(A7)
1092   206E 0008    MoveA.L   $8(A6), A0
1096   2068 0004    MoveA.L   $4(A0), A0
109A   2050         MoveA.L   (A0), A0
109C   4E90         Jsr       (A0)
109E   4E5E         Unlk      A6
10A0   4E75         Rts

10F0   4E56 0000    Link      A6, #$0
10F4   2F2E 0008    Move.L    $8(A6), -(A7)
10F8   206E 0008    MoveA.L   $8(A6), A0
10FC   2068 0004    MoveA.L   $4(A0), A0
1100   2050         MoveA.L   (A0), A0
1102   4E90         Jsr       (A0)
1104   4E5E         Unlk      A6
1106   4E75         Rts

1172   4E56 0000    Link      A6, #$0
1176   2F2E 0008    Move.L    $8(A6), -(A7)
117A   206E 0008    MoveA.L   $8(A6), A0
117E   2068 0004    MoveA.L   $4(A0), A0
1182   2050         MoveA.L   (A0), A0
1184   4E90         Jsr       (A0)
1186   4E5E         Unlk      A6
1188   4E75         Rts
27E8   4E56 0000    Link      A6, #$0
27EC   206E 0008    MoveA.L   $8(A6), A0
27F0   226E 000C    MoveA.L   $C(A6), A1
27F4   2290         Move.L    (A0), (A1)
27F6   7000         MoveQ.L   #$0, D0
27F8   4E5E         Unlk      A6
27FA   4E75         Rts

2AB0   4E56 0000    Link      A6, #$0
2AB4   206E 0008    MoveA.L   $8(A6), A0
2AB8   226E 000C    MoveA.L   $C(A6), A1
2ABC   2290         Move.L    (A0), (A1)
2ABE   7000         MoveQ.L   #$0, D0
2AC0   4E5E         Unlk      A6
2AC2   4E75         Rts

… while others are mostly duplicates with one or two extra instructions:

1C64   4E56 0000         Link      A6, #$0
1C68   206E 0008         MoveA.L   $8(A6), A0
1C6C   20AE 000C         Move.L    $C(A6), (A0)
1C70   216E 0010 0004    Move.L    $10(A6), $4(A0)
1C76   216E 0014 0008    Move.L    $14(A6), $8(A0)
1C7C   216E 0018 000C    Move.L    $18(A6), $C(A0)
1C82   216E 001C 0010    Move.L    $1C(A6), $10(A0)
1C88   216E 0020 0014    Move.L    $20(A6), $14(A0)
1C8E   216E 0024 0018    Move.L    $24(A6), $18(A0)
1C94   4E5E              Unlk      A6
1C96   4E75              Rts

27FC   4E56 0000         Link      A6, #$0
2800   206E 0008         MoveA.L   $8(A6), A0
2804   20AE 000C         Move.L    $C(A6), (A0)
2808   216E 0010 0004    Move.L    $10(A6), $4(A0)
280E   216E 0014 0008    Move.L    $14(A6), $8(A0)
2814   216E 0018 000C    Move.L    $18(A6), $C(A0)
281A   216E 001C 0010    Move.L    $1C(A6), $10(A0)
2820   4E5E              Unlk      A6
2822   4E75              Rts

2824   4E56 0000         Link      A6, #$0
2828   206E 0008         MoveA.L   $8(A6), A0
282C   20AE 000C         Move.L    $C(A6), (A0)
2830   216E 0010 0004    Move.L    $10(A6), $4(A0)
2836   216E 0014 0008    Move.L    $14(A6), $8(A0)
283C   216E 0018 000C    Move.L    $18(A6), $C(A0)
2842   216E 001C 0010    Move.L    $1C(A6), $10(A0)
2848   216E 0020 0014    Move.L    $20(A6), $14(A0)
284E   4E5E              Unlk      A6
2850   4E75              Rts

Some subroutines elicit from me a “WTF?!” reaction, like this implementation of memcmp:

A3C    8854 5F6D    DC.L      $88545F6D       ; ' T_m'
A40    656D 636D    DC.L      $656D636D       ; 'emcm'
A44    7000 0000    DC.L      $70000000       ; 'p   '
A48    4E56 0000    Link      A6, #$0
A4C    41EC 8226    Lea.L     -$7DDA(A4), A0
A50    2948 8390    Move.L    A0, -$7C70(A4)
A54    41EC 825E    Lea.L     -$7DA2(A4), A0
A58    2948 8394    Move.L    A0, -$7C6C(A4)
A5C    41EC 81F2    Lea.L     -$7E0E(A4), A0
A60    2948 8398    Move.L    A0, -$7C68(A4)
A64    42AC 839C    Clr.L     -$7C64(A4)
A68    4E5E         Unlk      A6
A6A    4E75         Rts

Once I sat down and walked through the code from the very top, though, things started to become a little clearer, although I’m not finished analyzing this code yet by any stretch. Recall from last week that the Pippin loads ‘rvpr’ 0 from ROM and then copies it into a block of memory allocated on the system heap. ‘rvpr’ 0 starts by getting its own address in the heap and applying an offset to it:

104    41FA FEFA         Lea.L     @start, A0
108    D1FC 0001 06A2    AddA.L    #$106A2, A0
10E    2008              Move.L    A0, D0
110    A055              _StripAddress
112    C18C              Exg.L     D0, A4

Next, it gets its address again, but without the offset:

14     41FA FFEA    Lea.L     @start, A0
18     2008         Move.L    A0, D0
1A     A055         _StripAddress

Then, it calls a subroutine that reads and writes to some data located at the offset -$7C60 from the address calculated in the first step, effectively placing it at $8A42 from the start of its memory block. It subtracts the longword found here (initially zero) from the unmodified start address, and if the result is zero, returns without doing anything else. But if it’s not zero, as would be the case when first running this code, things get interesting. It checks to see if _HWPriv is implemented and if so, sets a Boolean to true at offset $8A46. Then it passes its address + $8A47 to yet another subroutine. Finally, it sets $8A42 to the unmodified start address (effectively short-circuiting future calls), checks the Boolean at $8A46 and if it’s true, flushes the instruction cache by calling _HWPriv with selector 1 in register D0.

Hmmm. Why would it need to explicitly flush the instruction cache? The answer to that question lies in the subroutine that gets passed @start + $8A47. I haven’t fully wrapped my head around it yet, but from reading the code there it looks like offset $8A47 of ‘rvpr’ 0 looks to be a compressed list of offset locations, used to patch ‘rvpr’ 0 in place. A-ha! Now it’s clear why ‘rvpr’ 0 is copied to the system heap, and abundantly clear why the instruction cache needs to be flushed after this subroutine returns: it’s self-modifying code.

(P.S. Josh Juran graciously pointed out that the Metrowerks runtime used by e.g. CodeWarrior performs this same in-place relocation of code resources at runtime. Side question: What’s the possibility the Pippin’s auth check was written with CodeWarrior?)

Exploring the Pippin ROM(s), part 2

I’ve spent the last couple of evenings taking a closer look at the Pippin 1.0 ROM– specifically the boot process– trying to determine precisely how it verifies that a provided boot disc is in fact signed properly before passing it off to get loaded.

The earliest parts of the Pippin ROM are not much different from the late Quadra “universal” ROMs, which kind of makes sense given how close the Pippin is to the first couple generations of Power Macs. It deviates in a few places by writing to some areas of high memory for reasons I haven’t yet deduced, but is otherwise pretty straightforward compared to a real Mac– in accordance with being derived from a “universal” ROM, it retains the checks for various 68K processors and their capabilities, even despite only having the 68LC040 emulator underneath.

Where things start to get interesting is after the ROM initializes the SCSI Manager. It then looks for an ‘iNiT’ 1 resource (note the capitalization) and executes it, followed by an ‘iNiT’ resource named “Install XFS.” I haven’t yet dug into these segments to see what is happening here, but somehow I don’t think the latter block is installing drivers for a popular filesystem… 😉

Elliot Nunn pointed out to me that the boot process is part of the Start Manager and hadn’t changed much in the years leading up to the Pippin’s release. He also kindly suggested that I specifically search for FindStartupDevice.

So I did that.

I found a few interesting things.

FindStartupDevice pretty much follows the same steps as a real Mac… until we find valid boot blocks. Then it runs this little snippet of code:

1592   303C FFDC        Move      #-36, D0
1596   322A 0008        Move      dqRefNum(A2), D1
159A   B240             Cmp       D0, D1
159C   66C0             BNE.B     @TryAgain

-36 is the refNum for the internal CD-ROM drive. What this code does is check to see if our current drive queue entry is using the .AppleCD driver with the internal drive. If it’s not, it loops back to search for other potential boot volumes. Looks a bit like a hotfix and/or conditionally compiled to me (Why didn’t they just refactor the code so that it only searches the CD-ROM drive? Hey, I wasn’t there…), but essentially this means definitively that a 1.0 Pippin will not fully boot from any device other than its internal optical drive.

After this code is where things start to heat up. Take a look:

159E   2F3C FFFF FFFF   Move.L    #-1, -(A7)
15A4   4EBA 0B0A        Jsr       @mysterySub1          ; hmmm...
15A8   588F             AddQ.L    #4, A7
15AA   4EBA 1C84        Jsr       @mysterySub2          ; HMMM...
15AE   4A40             Tst       D0
15B0   6738             BEQ.B     @GotIt                ; success! boot!
15B2   303C FFDC        Move      #-36, D0
15B6   B06A 0008        Cmp       dqRefNum(A2), D0
15BA   66A2             BNE.B     @TryAgain
15BC   4FEF FFCE        Lea.L     -ioQElSize(A7), A7
15C0   204F             MoveA.L   A7, A0
15C2   317C FFDC 0018   Move      #-36, ioRefNum(A0)    ; .AppleCD
15C8   4268 0016        Clr       ioVRefNum(A0)
15CC   42A8 0012        Clr.L     ioNamePtr(A0)
15D0   317C 0007 001A   Move      #7, csCode(A0)        ; eject the disc
15D6   A004             _Control
15D8   3028 0010        Move      ioResult(A0), D0
15DC   4FEF 0032        Lea.L     ioQElSize(A7), A7
15E0   3F3C 0002        Move      #2, -(A7)             ; ShutDwnStart
15E4   A895             _ShutDown                       ; restart the Pippin
15E6   6000 FF76        Bra       @TryAgain
15EA   4A78 08D0        Tst       (CrsrState)
15EE   6B02             BMI.B     @ShowHappyMac
15F0   A852             _HideCursor

Immediately before making the decision to advance to the “Happy Mac” state (such as it is on the Pippin), this block of code passes -1 on the stack to a mystery subroutine. Then, it calls a second mystery subroutine, the result of which, if zero, indicates the Pippin is free and clear to boot from that volume (provided it’s the CD-ROM drive– again with that check!). If the check fails, the disc is ejected and the Pippin restarts.

So, let’s start with mysterySub1. mysterySub1 calls down to $20B0, where this happens:

20B0   60FF 000C AB0E    Bra.L     @mysterySub3

Hmmm. OK. So where does that take us? We end up in a short subroutine that loads a ‘nint’ 43 resource, then through a series of calls to _CodeFragmentDispatch we jump into InitAnimation. A-ha! ‘nint’ 43 starts with the string “Joy!peffpwpc” indicating that it’s PPC code, and a list of symbols at its end suggests it draws the “tray loading” animation using a loop of _DrawPicture and associated Color QuickDraw calls. Neat.

But mysterySub2 is where things get really juicy. Check it out:

3230   48E7 3030         MoveM.L   D2-D3/A2-A3, -(A7)
3234   2078 0DDC         MoveA.L   (BootGlobPtr), A0
3238   41E8 FF7E         Lea.L     -$82(A0), A0
323C   20B8 020C         Move.L    (Time), (A0)
3240   594F              SubQ      #4, A7
3242   2F3C 7276 7072    Move.L    #'rvpr', -(A7)       ; 'rvpr' resource
3248   4267              Clr       -(A7)                ; ID 0
324A   A9A0              _GetResource
324C   221F              Move.L    (A7)+, D1
324E   6700 0050         BEQ       @fail
3252   2041              MoveA.L   D1, A0
3254   2648              MoveA.L   A0, A3               ; A3 = handle to loaded 'rvpr' resource
3256   594F              SubQ      #4, A7
3258   2F08              Move.L    A0, -(A7)
325A   A9A5              _SizeRsrc
325C   201F              Move.L    (A7)+, D0
325E   2600              Move.L    D0, D3               ; D3 = size of loaded 'rvpr' resource
3260   A71E              _NewPtrSysClear
3262   6600 003C         BNE       @fail
3266   2F08              Move.L    A0, -(A7)
3268   2003              Move.L    D3, D0
326A   2248              MoveA.L   A0, A1
326C   204B              MoveA.L   A3, A0
326E   2050              MoveA.L   (A0), A0
3270   A02E              _BlockMove                     ; copy 'rvpr' resource into new ptr
3272   2F0B              Move.L    A3, -(A7)
3274   A9A3              _ReleaseResource
3276   2657              MoveA.L   (A7), A3             ; A3 -> our 'rvpr' resource data
3278   554F              SubQ      #2, A7
327A   3F2A 0008         Move      dqRefNum(A2), -(A7)
327E   3F2A 0006         Move      dqDrive(A2), -(A7)
3282   41FA FF5C         Lea.L     someData, A0
3286   2F08              Move.L    A0, -(A7)
3288   41FA FF46         Lea.L     someOtherData, A0
328C   2F10              Move.L    (A0), -(A7)
328E   4E93              Jsr       (A3)
3290   301F              Move      (A7)+, D0
3292   205F              MoveA.L   (A7)+, A0
3294   3F00              Move      D0, -(A7)
3296   A01F              _DisposePtr
3298   301F              Move      (A7)+, D0
329A   4CDF 0C0C         MoveM.L   (A7)+, D2-D3/A2-A3
329E   4E75              Rts
32A0   303C FFFF         Move      #-1, D0
32A4   60F4              Bra.B     @exit

That’s a bit to take in, but here’s a summary. We load ‘rvpr’ 0, then copy it into a new block of memory within the system heap. Then we pass the current DCE refNum, drive, a pointer to four longs (the first of which having the value $4B, or 75), and then a pointer to a much larger data block immediately preceding this subroutine to our local copy of ‘rvpr’ 0. It returns a 16-bit result code on the stack, which we save before disposing of our local copy of ‘rvpr’ 0, then we return. From examining what FindStartupDevice does earlier, the result of ‘rvpr’ 0 must be zero in order for the Pippin to complete the startup process.

So what’s in ‘rvpr’ 0?

'rvpr' 0 in 0xED
Anything stand out to you?

and… InitRSAAlgorithmChooser

That smells like paydirt to me, at least in the 1.0 ROM. The best part is that it’s written in 68K, my reading comprehension skills of which are much better than that of PowerPC assembly. Curiously, there are no ‘rvpr’ resources in ROMs 1.2 or 1.3, even though 1.2 also does the auth check. I’m interested in discovering what replaces it in 1.2, but for now I will continue to investigate 1.0’s implementation. Stay tuned. 🙂

Exploring the Pippin ROM(s)

The Pippin never really had a fair chance at life. Produced by Bandai under license to Apple, it was too expensive for a gaming console yet overshadowed in the computer market by full desktop machines. The system suffered a bit of an identity crisis as well: Was it for gaming? The Internet? Neither? Both? Pippin was one of those late-90s, pre-Jobs Apple experiments that didn’t take the world by storm and as such is largely ignored by retro gaming and computing enthusiasts.

Which is, of course, why I have one. 😀

Welcome to Pippin. ..that's OK, I didn't see it either.
Neither did most of the gaming world back then.

Essentially a Power Mac 6100/66 crammed into a set-top box, in my opinion the Pippin packs a bit more power than a lot of folks realize. The Pippin had the most success in Japan, and most titles developed for it were localized for that region. Unfortunately many of those titles were authored in Macromedia Director and did little to show off the capabilities of the console. There were a number of third parties lined up to develop English software for the system in 1996 and early 1997, but this was also the era of Nintendo 64 and PlayStation, so Pippin was cancelled before many of these titles would ever ship– in some cases even before projects got off the ground. One of those titles was Presto Studios’ The Journeyman Project: Pegasus Prime, which I’ve had and continue to enjoy the tremendous honor of maintaining a modern rerelease for Windows, macOS, and Linux. Pegasus Prime almost didn’t ship at all– that Pippin was based on the Macintosh platform allowed Bandai to publish it for Power Macs instead, where certainly the returns were more promising. A number of Pippin discs could run on Macs– this was in fact a hallmark of the system. But this could also go both ways– if Pippin software is also Mac software, then shouldn’t the reverse be true? Why isn’t there a homebrew scene for the Pippin?

Pegasus Prime on the Pippin
The rerelease is way better. Trust me. 😉

The only built-in software shipped with the Pippin came in the form of a circuit board with four megabytes of ROM that fit into a slot on the logic board inside the system’s case. The Pippin itself has no built-in operating system– each title shipped on one or more bootable CD-ROM discs containing a modified version of System 7.5.2. In a time before ubiquitous broadband Internet access like we have today, this allowed Bandai/Apple to provide upgrades and new features in software without having to download patches or release new hardware. But only titles that were signed or “Pippinized” by Apple could boot the system. This was done less to combat piracy (as the CDs themselves were and continue to be easily duplicated), but more to control the library, limiting the selection of titles to those produced by officially licensed developers. This is not that different from how Sony, Microsoft, and Nintendo prepare software for their respective consoles today. While the check for Pippinized discs could be skipped on test or retail kits using a special dongle provided by Apple (extremely hard to find today), development units came with a special ROM that removes the check entirely, allowing programmers to more conveniently test and debug multiple revisions before finally reaching “Gold Master” status and sending their final CDs off to be signed and pressed.

There were a couple different ROM board PCB styles– very early developers typically would get pre-production hardware with a ROM board populated with erasable Flash chips. These developers would periodically be asked to send their boards back to Apple to be reflashed with the latest ROM revision. Once the ROM was finalized and the Pippin went into production, boards shipped populated with mask ROM chips that could not be rewritten. I know of two developer ROMs, plus three ROM revisions that were released with retail consoles: 1.0 (white atMark Pippins in Japan), 1.2 (black @WORLD Pippins in the US and late atMarks), and 1.3 (very late Pippins). I have images of these three retail ROMs plus an image of the “GM Flash” developer ROM; I have never seen a retail ROM 1.1. As ROM 1.3 only shipped with a small number of Pippin units late in the system’s lifecycle, it is very rare but represents the last revision of the Pippin ROM, culminating a number of bug fixes and other changes.

ROM 1.3 also… I am told… removes the authentication check at boot, much like the developer ROMs do. 😉

I want to find out how. 😀

Pippin ROM board

Power Macs produced in 1998 or later boot from Open Firmware and tend to have their ROMs loaded from a file on disk and loaded into RAM– the “New World” model. Earlier Power Macs like the Pippin also boot from Open Firmware but still have a ROM burned into a physical chip or chips attached to the logic board– the “Old World” model. Open Firmware is contained within the last 1MB of these ROMs, and overall its job is to start the 68LC040 emulator and use it to boot the rest of the ROM, which is written predominantly in 68K. Thus, the first 3MB of an Old World ROM is straight 68K code, mostly comprising the Mac Toolbox APIs and some necessary drivers for booting the system and loading the OS from a startup device. These drivers can be found in what are known as “resources”– self-contained chunks of data addressed by a four-byte “type” and a two-byte ID (or occasionally a human-readable name as a Pascal string). Resources aren’t just segments of code– they can contain any type of data such as pictures, icons, sounds, and fonts, just to name a few.

I’m quite familiar with resources– my earliest work on Pegasus Prime involved reverse-engineering some custom resource formats developed to hold metadata about the game’s QuickTime-based animations. I did some work a number of months ago trying to reverse-engineer the MacWorks XL ROM for the Apple Lisa, in an effort to port the HD20-enabled .Sony driver to it so my Floppy Emu could boot it (One of these days I should revisit that and do a writeup about it, but that project is definitely on the backburner). In the course of dissecting that ROM, I learned a lot about the Mac boot process and how the ROM’s address space in general is laid out. For example, the first four bytes of a 68K Mac ROM consist of a checksum of the ROM’s contents. Four bytes at offset 4 provide a reset vector, which the Mac uses to boot. And, at offset $1A, four bytes provide an offset to where the ROM’s resources are stored. An easy first step to understanding a Mac ROM– or in this case, that of a Pippin– would be to extract its resources and examine them in a tool such as ResEdit.

I think one of the reasons why I couldn’t readily find a utility that took a binary ROM image and spit out a .rsrc file is that the way resources are stored in the ROM has changed since the earliest Macs. The Mac Plus ROM, for example, literally stores a resource map as one would find in an actual resource fork or .rsrc file– quite simple. By the time of the Pippin, though, this had changed to a slightly more complicated scheme. I spent a few hours last night successfully reverse-engineering this and then halfway through writing an extraction tool before the evening got too late. This evening, I was greeted by a pleasant surprise:

A few seconds later, I had the resources extracted from all four of my ROM images. I spent a little time tonight skimming through all I’ve found. A few highlights:

– The initials “kc” and the name “Kurt” are used for padding– these refer to Kurt Clark, presumably an engineer at Apple at the time.
– Open Firmware contains strings referencing TFTP in all the ROM revisions I have. Maybe the Pippin can boot from TFTP somehow, bypassing the authentication check?
– Gary Davidian’s 68LC040 emulator and Eric Traut’s dynarec can both be found after Open Firmware in the last 1MB of the ROM. Maybe they can be replaced with the versions found in Connectix’s Speed Doubler, saving the need for loading that INIT into RAM?
– All four ROMs contain the resources for the blinking ? and X floppy icons, as well as the recognizable Happy Mac. These icons are shown during the boot process on a real Mac but unused on the Pippin.
– There are two PPC-based ‘ndrv’ resources: .Display_Video_Apple_Control (to drive the “taos” graphics hardware) and .DAVInput, which might be used with the rear RCA audio jacks. The Pippin doesn’t have any video input capabilities, but the Power Mac 6100AV did… maybe .DAVInput and some of its associated hardware is borrowed from that machine?
– There are five 68K-based ‘DRVR’ resources– .rdrvr (for the internal 128KB of Flash storage), .AppleCD, .Sound, .Sony, .AppleSoundInput, and .EDisk. The latter two are a bit interesting– .AppleSoundInput because it’s either used for the rear audio jacks or the internal connection to the CD-ROM drive’s audio, and .EDisk because it suggests the existence of a ROM disk somewhere.
– The CD-ROM driver is identical between the GM Flash and 1.0 ROMs. But then there are major changes going into 1.2– in addition to code changes, a new drive model appears to be added to a whitelist: the Matshita CR-8006, a.k.a. the Apple CD 600e. But then, in 1.3, the whitelist reverts back to that of 1.0, while incorporating additional code changes. I’ve successfully got a Toshiba DVD-ROM drive to boot my @WORLD, so I’m not entirely sure what this whitelist is used for yet.

More to come as I dig deeper…