1 page
^^
vv
List results:
Search options:
Use \ before commas in usernames
This information is going to end up being published in parts as and when I have the time to write them. Here's a fair chunk to get started.

Hacking Gamecube Memory Cards For Fun (but no Profit)

'sclaimer: all information presented here is for educational purposes only. Any damage to anything whatsoever caused by implementing any of the techniques in this writeup is at the reader's own risk.

A while ago, I was chatting with Nate on IRC and the idea came up of reverse engineering the Metroid Prime save game format. It was an idea I had after Mills accidentally loaded an NTSC save using that dodgy Korean copy of Prime he has, and all hell broke loose. In particular we wondered if it might be possible to examine the save game data and deduce what the current save time was, so theoretically runners would be able to know the precise metrics of their run by just pulling the relevant data out of a saved game file. Radix was also keen on backing up his Animal Crossing game, something which the Gamecube's default file copier refused to do.

I spent some time wandering around the internet looking for information. The most useful document was this one, as usual penned by an open source team, the Gamecube Linux team in this instance. The section on memory cards revealed that the card basically uses a simple variant of the Serial Programming Interface (SPI), the hardware protocol that is used for applications such as reading and writing from digital camera memory cards such as SD or MMC. The Gamecube cards contained a single Serial Flash ROM chip manufactured by Macronix. Information about this chip was not forthcoming - although the Macronix site provided data sheets for similar integrated circuits, there were none for the precise part number of the chip used by Nintendo. It looked like the chip was a custom one, although the level to which this applied didn't become apparent until later on.

Having read the relevant part of the Gamecube document and the Macronix data sheets, I was feeling confident that I could build something reasonably trivial that would at least have a chance of talking to a memory card extracted from a Gamecube. Because the memory cards use the SPI bus, it would probably be possible to hook them up to a PC MMC card reader or similar, but there was a problem with that approach - the total lack of debugging capacity. I could already see that I would plug my badly-connected card into my buggy piece of homebrew software and would simply be stuck when the two did not work together, with no means of investigating the problem. There are tools such as logic analysers which one can use to tap a bus such as that between the card and its reader and debug what is going on, but - in short - I don't have one. The fact of the matter was that I was going to have to build the interface electronics myself and make it possible to inspect whether the circuit was working by incorporating a set of diagnostic LEDs. I knew from the Macronix information that their serial flash ROM chips had no minimum clock speed, so in theory there was nothing stopping me from clocking the interface at about 0.5 Hertz and seeing what the circuit was doing just by writing down which lights were on.

From research done by the GC Linux team I knew that there would be six connections I would need to make to the memory card. These are: Power (+Volts), Ground (zero Volts), Chip Select (aka CS), Clock (aka CLK), Data In (DI) and Data Out (DO). This meant that whatever interface I built was going to need to have three controllable output lines and one input line. The SPI interface is discussed later on, so don't worry about this yet.

Unfortunately the ports on the PC these days are not well suited to interfacing with homebrew electronics. To build a USB device or PCI card would be far more work than I was prepared to put in (although I did consider it). The PC's RS232 (serial, COM) ports were an option, but since I would be using something that didn't remotely resemble a normal serial interface, it would probably be necessary to resort to some low-level hacking to convince it to speak the language I needed (this practice of abusing a port's handshaking lines to make it behave like something it isn't used to be known as "bit-banging"). The parallel port (Centronics, printer), was also an option, but that also comes with some problems - ignoring modern innovations like EPP, it's a one-way interface that doesn't have much support for reading data. Both of these ports I could probably have persuaded to work, but equally both of them would probably have required some cajoling. I think there were other reasons why I couldn't or didn't use either port, but I've forgotten my justifications now.

What I really needed was a port on a computer that was actually designed for doing what I was doing - an easily controllable general purpose input/output port with multiple input and output lines. It is possible to buy PCI cards for PCs that provide this ability, but I wasn't going to go down that route. It would be nice if we still lived in the days of hobbyist computers that were designed for interfacing to projects, I thought, where the designers didn't actually know yet what the average Joe would want a computer for, and provided for all eventualities.

The British Broadcasting Corporation announced in the very early 1980s in Britain that they would be beginning a Computer Literacy Project, which would consist of television and radio programmes describing the use of a standardised microcomputer that they would endorse. The history of the BBC Micro is long and varied - the winning design was by Acorn, a company who up until then had been manufacturing smallish runs of microcomputers for absolutely hardcore hobbyist geeks. Legend has it that the BBC Micro's original operating system (stored on 16K of ROM, kids) was written in one up-all-night code orgy, the evening before the company were due to show the machine to the BBC. (Just by the way, if you own an iPod or a Nintendo DS, you own microprocessors that have Acorn pedigree - ARM was originally a division of Acorn, and stood for "Acorn Risc Machine".) Ours was one of the first families in the UK to get one of the micros, a "Model A" with 16K of RAM and no disc drive. It is a shame that the original machine we owned broke down and was disposed of, because it was an extremely rare early model with all microchips socketed and a slightly different coloured case, which would no doubt be of interest to a collector today - heck, it would be of interest to me today. The project was far more successful than the BBC expected, originally anticipating uptake of the machines to be in the few thousands range (Acorn's machine, or the "Beeb" as it was known, would eventually shift one million units in the UK). I have extremely fond memories of these distinctive little computers, perfection that they were, and could talk about them literally for hours, but I need to call a halt to this paragraph now.

The BBC Micro had a "user port", which was really a 6522 TTL integrated circuit connected to a socket on the back of the machine. The user port had eight bi-directional general purpose logic lines that could be controlled or read very easily from user software written in either BASIC or assembly language. It also sported an RS423 serial interface, compatible with the PC's RS232 serial interface. I could write an assembly language program on a BBC Micro to talk to the gamecube memory card using the user port, and ferry the resulting data to and from a PC using a completely standard serial interface. It would be slow, as the BBC wouldn't communicate reliably at speeds greater than 9,600 baud, but it would be fast enough, and it would work.

While I was waiting for the computer to arrive (given the number of schools that had Beebs in them, finding them on eBay is like finding leaves on trees), I turned my attention to the interface electronics. One problem is that because the BBC was old, the user port was a five Volt device rather than a three (or 3.3) Volt one. Therefore my electronics needed to do a few simple things - convert the voltage levels in both directions (to 3V for the card and to 5V for the BBC), provide a 3V power supply for the card, and allow me to inspect all the data lines by lighting LEDs.

This was my original circuit, although the design changed later (more on that in a minute). The BBC micro is assumed to be on the left, and the memory card on the right.



IC1 is a voltage regulator, and VR1 (a trimmer) is adjusted until the 3V line is at three Volts. CLK, CS and Serial In are all lines carrying data left to right; Serial Out carries data right to left (as indicated by the arrows). IC2 is a 3V/5V logic buffer that steps the three Volt output of the memory card up to five Volts to drive the BBC's user port input. All the 5V lines are connected to NPN transistors (I used 2N2222A) enabling them to drive the diagnostic LEDs. To step the user port voltage down from 5V to 3V, I opted for the most basic solution of using voltage dividers (such as the R3/R4 pair). This did not quite work as I expected. I was making the assumption that when the BBC's output was at five Volts (logic high) it would be connected directly to the BBC's internal +5V supply (as it would in CMOS). Because the BBC used old TTL (transistor-transistor logic), a logic high was actually connected to +5V through a 2K resistor, which messed up my divider calculations, but the problem was easily solved simply by replacing R3, R5 and R7 with pieces of wire, so I was using the BBC's 2K resistors instead of my own as the top half of the divider.

I designed the circuit to be built on stripboard. Again, modifications occurred after this was drawn, so it's not quite what was eventually used.



(X is a track break)

The intention was to connect the memory card to a socket at the top-right of the layout. I had already experimented a little with ways to make connections to a memory card, preferring if possible to build something that could be used with any card. My first attempt looked like this:



and consisted of an old piece of plastic with a layer of aluminium foil glued to it, and cut with a knife to match the positions of the tracks on a memory card. It had pieces of rubber glued to the back to attempt to keep the contacts firm. After hours of prodding with a resistance meter I declared it a total failure and from that point on I decided to just crack open the cards and add my own external connector soldered via a cable direct to the memory card's PCB. Large numbers of my memory cards now, as a result, have appendages. Nintendo's cards use some horrible proprietary screw head, too, so many are also bound closed with sticky tape.

The computer arrived eventually, although I nearly broke it an hour after opening the box. It turned out to be a relic from some sort of cheap TV studio, as it had been modified to allow video genlocking from an external source, and had an evil ROM fitted. Removing the modification broke the computer, so I had a rather unpleasant hour trying to figure out why (a track had been cut on the motherboard to allow installation of the genlock mod). The BBC was connected to the serial port of my PC via a special cable I had already made (not easy - you try finding somewhere that sells 5-pin "domino" DIN plugs these days). Here's a pretty picture of the device.



Once I'd built and debugged the circuit (can you debug a circuit ?) and had tested it with some simple BASIC programs, it was time to think about writing some full-blown software to communicate with the card at speed. Because the BBC's CPU was 8-bit and only ran at a few MHz (it was in fact a very similar CPU to that used in the NES), and because C implementations on such old home computers were rare, expensive and crap, I would have to use 6502 assembler.

Code:
*FX3,6

PAGE=&E00
*TAPE
NEW

REM reserve 8k for machine code routines

HIMEM=&5C00

10MODE 7
15*FX2,2
16ZP=&70

20FOR PASS=0 TO 3 STEP 3
30P%=&5C01
40[OPT PASS

43.start

44CLD \ ensure decimal reg is cleared
45LDA #2:LDX #2:JSR &FFF4 \ enable serial port
46LDA #15:LDX #0:JSR &FFF4 \ flush buffers
47LDA #7:STA &FE62 \ user port DDR
48JSR resetuserport

49JSR &FFE7:LDX #(ssready AND &FF):LDA #(ssready DIV &100):JSR print:JSR &FFE7 \ print "ready" line

50.idleloop \ wait for something to happen on the serial port
60LDX#1:LDA#&91:JSR&FFF4 \ OSBYTE call
70BCS idleloop \ carry flag set, continue looping

100LDX #(ssgotbyte AND &FF):LDA #(ssgotbyte DIV &100):JSR print \ print "got byte" line
130TYA:PHA \ preserve byte read from serial port
135JSR bytetoasc:TXA:JSR &FFEE:TYA:JSR &FFEE:JSR &FFE7 \ print it
136PLA \ get byte back

140CMP #&89 \ clear status
150BNE bytecheck1
160JSR clearstatus
170JMP finish
180.bytecheck1
190CMP #&83 \ read status
200BNE bytecheck2
210JSR readstatus
220JMP finish
230.bytecheck2
240CMP #&52 \ read block
250BNE bytecheck3
260JSR readblock
270JMP finish
280.bytecheck3
290CMP #&F2 \ write block
300BNE bytecheck4
310JSR writeblock
320JMP finish
330.bytecheck4
340CMP #&85 \ get id
345BNE failed
350JSR readid
360JMP finish
400.failed
410LDX #(ssunrecognisedbyte AND &FF):LDA #(ssunrecognisedbyte DIV &100):JSR print:JSR&FFE7 \ print error

490.finish
495JMP start
500RTS \ return to BASIC

1030.print \ expect high byte in A, low byte in X
1050STA temp \ save A
1060TYA:PHA \ preserve Y-reg
1070LDA temp \ retrieve A
1080STA ZP+1:STX ZP \ address of string now stored in zero page
1090LDY #0 \ counter
1100.printloop
1110LDA (ZP),Y \ get character (zero page indirect indexed)
1115BEQ printend \ null terminator ? quit
1120JSR &FFEE \ OSWRCH call
1130INY \ Y++
1140JMP printloop
1150.printend \ clean up, RTS
1160PLA:TAY:RTS

1300.bytetoasc \ convert byte to two-byte ASCII string, take byte in A and return results in X then Y
1305PHA:PHA
1310AND#&0F:CMP#10:BMI asclodone:ADC#6
1320.asclodone
1330ADC#&30:TAY
1340PLA:LSR A:LSR A:LSR A:LSR A:CMP#10:BMI aschidone:ADC#6
1350.aschidone
1360ADC#&30:TAX:PLA:RTS

2000.clearstatus
2010LDX #(ssclearstatus AND &FF):LDA #(ssclearstatus DIV &100):JSR print:JSR &FFE7 \ print message
2020LDA #&89:JSR sendcmd \ send command to uport
2040RTS

3000.readstatus
3005LDX #(ssreadstatus AND &FF):LDA #(ssreadstatus DIV &100):JSR print:JSR &FFE7 \ print message
3010LDA #&83:JSR sendcmd \ send command to uport
3020LDA #0:JSR sendcmd   \ send dummy byte to uport
3030JSR getsout \ read byte of serial data
3035PHA
3040LDX #(sscardbyte AND &FF):LDA #(sscardbyte DIV &100):JSR print \ print message
3045PLA:PHA
3050JSR bytetoasc:TXA:JSR &FFEE:TYA:JSR &FFEE:JSR &FFE7 \ print it
3060PLA:JSR putserial \ send to RS423
3200RTS

4000.readblock
4010LDX #(ssreadblock AND &FF):LDA #(ssreadblock DIV &100):JSR print \ print message
4015LDA#&52:JSR sendcmd \ send the 0x52 command byte
4016LDY #4 \ counter
4020.readblockbyteloop \ expect four more bytes on serial port
4030TYA:PHA \ preserve Y
4040.readblockwaitloop \ wait for something to happen on the serial port
4050LDX#1:LDA#&91:JSR&FFF4 \ OSBYTE call
4060BCS readblockwaitloop \ carry flag set, continue looping
4062STY temp \ write the byte we read to temporary storage
4063TYA:JSR bytetoasc:TXA:JSR &FFEE:TYA:JSR &FFEE \ print byte
4064LDA temp:JSR sendcmd \ send offset byte to memory card
4066PLA:TAY \ restore counter
4080DEY
4090BNE readblockbyteloop
4095JSR &FFE7 \ print newline
4100LDA #0:JSR sendcmd:JSR sendcmd:JSR sendcmd:JSR sendcmd \ send four null bytes
4110LDX#2
4120.readblockouterloop
4130LDY#0
4135.readblockinnerloop
4136TYA:PHA:TXA:PHA 
4140JSR getsout \ read a byte from card
4150JSR putserial \ send to RS423
4155PLA:TAX:PLA:TAY
4160DEY
4170BNE readblockinnerloop
4180DEX
4190BNE readblockouterloop
4500RTS

4600.readid
4610LDX #(ssreadid AND &FF):LDA #(ssreadid DIV &100):JSR print \ print message
4620LDA#&85:JSR sendcmd:LDA#0:JSR sendcmd \ send command bytes
4630LDY#2 \ counter
4640.readidloop
4645TYA:PHA
4650JSR getsout
4660PHA \ preserve result
4670JSR bytetoasc:TXA:JSR &FFEE:TYA:JSR &FFEE \ print it
4680PLA:JSR putserial \ restore and send to RS423
4690PLA:TAY \ get counter value back
4700DEY \ decrement
4710BNE readidloop \ loop
4715JSR&FFE7 \ newline
4720RTS \ quit

5000.writeblock
5005LDX #(sswriteblock AND &FF):LDA #(sswriteblock DIV &100):JSR print:JSR &FFE7 \ print message
5010RTS

6000.sendcmd \ send command to uport, expect cbyte in A
6010STA temp \ save A
6020TYA:PHA:TXA:PHA \ preserve regs
6030LDA &FE60:AND#5:STA &FE60 \ deassert CS (if asserted, doesn't matter if not)
6040LDY temp \ retrieve byte (in Y)
6041LDX#8 \ counter
6042.sendcmdloop
6045JSR delay
6050TYA:ASL A:TAY \ move top bit into carry
6060BCC sendcmdcarrylo:LDA #4:STA &FE60:JMP sendcmdcont \ load a 1 onto the serial in
6065.sendcmdcarrylo
6070LDA #0:STA &FE60 \ or load a 0 onto the serial in
6080.sendcmdcont
6081JSR delay
6082LDA &FE60:ORA#1:STA &FE60 \ assert clock
6083JSR delay:JSR delay
6084LDA &FE60:AND #&FE:STA &FE60 \ deassert clock
6187DEX
6190BNE sendcmdloop
6200PLA:TAX:PLA:TAY:LDA temp:RTS \ clean up and return

6300.getsout \ read byte of card data (returned in A)
6310TXA:PHA \ preserve regs
6320LDX#8 \ counter
6321LDA#0:STA temp \ target
6330.getsoutloop
6335LDA temp:ASL A:STA temp \ shift result left one
6337LDA &FE60:ORA#1:STA &FE60 \ assert clock
6338JSR delay:JSR delay
6340LDA &FE60:AND #&FE:STA &FE60 \ deassert clock and read data at the same time
6341AND #8:LSR A:LSR A:LSR A:ORA temp:STA temp \ OR the read bit with result and place back in result
6345JSR delay:JSR delay
6360DEX
6370BNE getsoutloop
6380PLA:TAX \ fix X reg
6390LDA temp \ get result
6400RTS

7000.delay \ what it says on the tin
7010PHA:TXA:PHA:TYA:PHA
7020LDX #&0F
7030.delayloop2
7040DEX:NOP
7050BNE delayloop2
7060PLA:TAY:PLA:TAX:PLA:RTS

8000.putserial \ put byte in A into RS423 buffer, if buffer is full then wait
8010STA temp:TXA:PHA \ store A, preserve X
8014JMP putserialwaitloopshortcut
8015.putserialwaitloop \ wait for space
8016LDX #(ssbufferfull AND &FF):LDA #(ssbufferfull DIV &100):JSR print:JSR &FFE7 \ print message
8017.putserialwaitloopshortcut
8020LDA#&80:LDX#253:JSR&FFF4
8030CPX#0
8040BEQ putserialwaitloop
8050 \ have some free buffer space now
8060LDA#3:LDX#7:JSR&FFF4 \ select RS423
8070LDA temp:JSR&FFEE    \ OSWRCH
8080LDA#3:LDX#4:JSR&FFF4 \ back to screen output
8090PLA:TAX:LDA temp:RTS \ clean up and RTS

8400.resetuserport
8405PHA:LDA #6:STA &FE60:PLA:RTS  \ user port CS=1,CLK=0,SI=1

10000.temp \ general purpose temp word storage
10010EQUW &0000
10020.ssgotbyte
10030EQUS "Got command 0x":EQUB 0
10035.ssunrecognisedbyte
10040EQUS "Unrecognised command.":EQUB 0
10050.ssclearstatus
10060EQUS "Clear status.":EQUB 0
10070.ssreadstatus
10080EQUS "Read status.":EQUB 0
10090.ssreadblock
10100EQUS "Read block 0x":EQUB 0
10110.sswriteblock
10120EQUS "Write block 0x":EQUB 0
10130.sscardbyte
10140EQUS "Got byte from card 0x":EQUB 0
10150.ssreadid
10160EQUS "Read ID 0x":EQUB 0
10200.ssready
10210EQUS "(*) Ready.":EQUB 0
10220.ssbufferfull
10230EQUS "(W) RS232 buffer full.":EQUB 0

20000]NEXT PASS

*FX3,4
*FX2,0


This is a BBC BASIC program which, when run, will assemble a machine code routine into memory at address &5C01. Because I had no disc drive for the BBC, the easiest way to get this program into its memory was to just do a "*FX2,1", which told it to accept all input from the serial port instead of the keyboard. I could then just pipe my program to /dev/ttyS0 on my Linux PC. This also made editing the code much easier than if I'd had to do it on the micro. Control is returned to the keyboard by the *FX2,0 right at the end of the script above. The BASIC program would be RUN and then the resulting machine code routine invoked using CALL &5C01.

The software essentially listens to the serial port (lines 50-70) and waits for a byte to arrive over the RS232 from the PC (the command byte). Once a byte is received, the code goes through a switch/case style construction (lines 140-410) to decode the command byte and decide what to do. The command bytes correspond to the instruction bytes for talking to the memory card via its SPI interface: clear status (0x89); read status (0x83); read block (0x52); write block (0xF2) and get ID (0x85). Depending on the byte received, a subroutine is called. Some of them, such as readblock (line 4000) will expect extra bytes to arrive over the serial port (in this case, the PC must tell the software the block that it wishes to be read). The subroutines make heavy use of other routines such as sendcmd (line 6000) which sends a byte to the memory card; getsout (line 6300) which reads a byte from the memory card and putserial (line 8000) which sends bytes received from the memory card over the serial interface where they can be collected by the PC. The subroutine delay at 7000 is used to introduce varying levels of artifical delay so that the diagnostic LEDs may be inspected.

To understand what it does to the signal lines, it is necessary to have a look at how the SPI bus works.

... to be continued !
Thread title: 
Wow, that's some hardcore technical stuff. Bookmarked for later reading, looks pretty interesting.

EDIT: read it. Wow, that's alot of work for a hobby project ;). luckily, I had a course in basic computer architecture last period, certainly helped understanding you article :P.
Looking forward to the next part.
Yay!  Geek porn!  This first installment was a blast to read.  You are a true hacker.

You say that you are converting the 5V to 3V (or 3.3V) for the card.  The card interface on the GC can do both 5V and 3.3V (so it is said).  (Of course, for you it is probaby pretty obvious which of these the cards use under nominal conditions or can at least handle.)

So ... will you be retiring your GC to be a linux machine (if that time ever comes)? :)
Quote from SkippyJr:
Yay!  Geek porn!  This first installment was a blast to read.  You are a true hacker.

You say that you are converting the 5V to 3V (or 3.3V) for the card.  The card interface on the GC can do both 5V and 3.3V (so it is said).  (Of course, for you it is probaby pretty obvious which of these the cards use under nominal conditions or can at least handle.)

So ... will you be retiring your GC to be a linux machine (if that time ever comes)? :)


Yeah, I didn't really want to start bunging five volts into the memory cards. I figured that since I wanted a debugging interface between the two devices anyway, I might as well do it properly and not play russian roulette with my '59. In actual fact I think the 3V -> 5V buffer on the data line that comes back in to the micro is unnecessary as 3V is probably enough to take TTL high, but meh.

I've never really felt much inclination to run Linux on the Gamecube, although if I didn't already own a PowerPC machine, perhaps I would. If I ever feel like demoting a gaming console to a number cruncher, I'd just use the xbox, where it's much easier. :P
Strategy Guide Writer
I didn't really understand a single word of what you wrote (past the Dodgy Korean copy of MP and the ability to possibly read time-staps on the saves), but that IS some mental work being put in to achieve that goal!  Shocked

I just hope that it all pays off and that we CAN read the exact times on save points for MP/MP2. :D Nice work DJ.  8)
Well, I'll spoil the ending by pointing out that I didn't get any useful results. I also tried one of the commercially available PC <-> GC memory card devices (Datel MaxDrive), but didn't get any results with that either, for entirely different reasons, although I'd lost a lot of interest in the project by that point.

I suck at reverse engineering.
This sounds interesting.
Mister ...
I didn't read it, as it would take forever for me to do so.  I'd probably get lost anyways, as I have no idea whatsoever what half of that stuff is.  *sigh* ohwell. 

I wish you good luck with your hacking and hope that it turns out the way you want it too, as I know it wouldn't with me.
Quote from nn12000:
I didn't read it, as it would take forever for me to do so.  I'd probably get lost anyways, as I have no idea whatsoever what half of that stuff is.  *sigh* ohwell. 

I wish you good luck with your hacking and hope that it turns out the way you want it too, as I know it wouldn't with me.


It took me 5 or 6 minutes to read it.
red chamber dream
Quote from Nintendo65:
It took me 5 or 6 minutes to read it.

No one asked; no one cares, and you are way off-topic. Once again, remember our lesson?:

Arkarian's forum guidelines - please consider these three things before you post.

1. "Is my post stupid?"
3. "Is my post not going to contribute anything to this thread?"

The two listed apply to you at this moment. Think before you post.
Wow you are a genius when it comes to this stuff I only understood about 80% of what you said which is suprising for me good job :)
Quote from DJGrenola:
Well, I'll spoil the ending by pointing out that ...

What?  So the butler did it?  I'm still interested in how the story plays out.

Of course you are attacking a very difficult problem.  It is unfortunately one which appears to be hard and crunchy on the outside, and soft and chewy on the inside.  That is, the initial successes will be the hardest to accomplish.  The first sucess would come from having any sort of conversation with the game card.  To get even that many things have to go right.  In part one we learned how you created hardware to physically interface with the cards (and found the 102nd use for aluminum foil :)), bought a new (ha!) machine capable of managable speeds, wrote a driver for the custom hardware on that machine, and how you had to solve unexpected/unrelated problems along the way.  We haven't even gotten to the interface specification and related stuff yet (unless you count 6502 code ;)).  That right there is one heck of a lot that has to work absolutely perfectly, and even one small mistake would drive you nuts for hours.  That you have taken on this project and have overcome the various technological hurdles of part 1 speaks volumes...

Quote from DJGrenola:
although I'd lost a lot of interest in the project by that point.

I hope you haven't lost interest in writing things up, as we have been left with a suspenseful cliffhanger, so to speak.

Quote from DJGrenola:
I suck at reverse engineering.

You are in the distinguished position of being able to produce as many game save images as you like.  Here, differential cryptanalysis is your friend. :)  Although tedious at first, you would be able to make great strides in RE-ing as the layout begins to make sense.
you're very gracious, but there are some other issues ... meh, you're right, I need to finish the writeup. Unfortunately the micro I used is in storage now and the notes I made at the time may well be too (although I'm not 100% sure about that) - I'll just have to see how much I can remember.