Monday, June 22, 2015

Reverse engineering Zany Golf (Apple IIgs version) just enough to be able to actually see the whole game :)

So yesterday was Father's Day, and I decided that I wanted to show my kids my old Apple IIgs games that I loved.  We pulled up Zany Golf but had a problem: my kids kept losing all their strokes (turns) immediately so they weren't having any fun!  I searched online for a cheat so that I could show them the whole game but found nothing.

Challenge accepted!

Tools used: KEGS debugger (setting breakpoints, etc), gdb, and IDA Pro

Why using an emulator is so much nicer: Using an emulator to reverse engineer these old pieces of software is so much more convenient than using a real Apple IIgs.  For one thing, it's a lot faster to start the game.  And for another things, its built-in debugger is much nicer for stepping through code than trying to hack the boot process of the game enough to do it with the built-in monitor (yuck!).

I decided to search for the message "Player 1, this is your last stroke." because I knew that somewhere near this message would be some logic to check to see what the current stroke count was.  And if I knew where the current stroke count was stored, I could set a breakpoint and catch when it gets changed (decremented).

First thing I did was searched for this string on disk.  I found it inside the file named "CODE" inside a Zany Golf subdirectory.  At this point, I realized that this wasn't very helpful because I did not know how CODE got loaded into memory.  I decided that I needed to run Zany Golf in KEGS and search the memory for this string to find where it got loaded.

Unfortunately, I discovered that KEGS (apparently!) does not have a memory search function.  After a few false starts down other paths, I decided to whip up a quick n' dirty memory search function that searches the entire emulated memory space for the string I was looking for.  On a modern PC, this brute force approach still completes very quickly so it's no big deal.

I tried to understand how KEGS actually managed the emulated memory and I got confused quickly.  As grateful as I am to have an emulator for the Apple IIgs, KEGS unfortunately is somewhat convoluted for me (although maybe others don't have as much trouble) with heavy use of global variables that look like local variables, and lots of macros.  So I ditched the idea of understanding how KEGS handles its own memory and instead just wrote a new search method that calls the get_memory_c method that KEGS provides and which the debugger uses to do memory dumps.  My custom method was tailored specifically to the search string and once I found the string, I wouldn't need it again.

Using this approach, I found the string at $01/736D.  On disk, it is at offset $726B inside the CODE file which is kinda weird.  This suggests that CODE is loaded at $01/0102.  I used IDA Pro to disassemble CODE at this starting address to give me some more visibility.  This succeeded with partial success although a lot of the 8/16 bit addressing modes were fouled up.

So the next task was to find when this memory location got read.  Since KEGS did not provide a memory breakpoint feature, I decided to use gdb instead.  First I had to find out where the native memory location for this emulated memory location was.  So I set a breakpoint on get_memory_c and inside KEGS debugger, I type "01/736d" so that only that memory location would grabbed.

I stepped through gdb at this point and after the GET_MEMORY8 macro was called, I typed "print ptr" to see the native memory location.

It came back as 0x7ffff647746d (ie a random number) and this value isn't that important because it may change each time the emulator is run (at least that is my impression).  What is important is that I set a memory breakpoint on it and then get the string about my last stroke to appear by playing through the game.

So I typed "rwatch 0x7ffff647746d" and then "continue" to return control to KEGS.  I then typed "g" to exit KEGs debugger and resume the game.  I played the game until I had one stroke left and sure enough, my memory breakpoint got hit! Woohoo!

'kpc' is the variable name inside KEGS that holds the Program Counter (PC) so I typed "print kpc".

kpc was set to $13e22 (which is $01/3e22).  Finally, a starting point to start stepping through the code!

I set a breakpoint on $01/3e20 and restarted the game.  But the breakpoint was getting hit too often so I concluded that it was part of the "Draw text" routines.  So I disabled the breakpoint, started a new game, and played until right before the message was to appear.  Then I entered KEGS debugger again and set the breakpoint.

I putted the ball and sure enough the breakpoint got hit again.  I set a new breakpoint on the RTS instruction which came shortly thereafter (because KEGS does not have a way to 'step out' of a function, so this is a poor man's equivalent).

This took me to $01/440C which made the JSR, but this is the beginning of a subroutine (according to IDA Pro) so I concluded that nothing interesting was here.  I set breakpoint on 4428 (right before RTS) to step out of this method.

This took me to $01/4437, which is still a small subroutine and did not appear to do any logic to check to see if it's the last stroke.  I set breakpoint on $01/4440 to step out of the function.

This took me to $01/6da2 which modifies $28 and $29 so this may be the subroutine that sets the message about it being the last stroke, as I noticed some references to these constants earlier.

I set a breakpoint at $01/6deb, right before the next RTS to step out of this method.

This took me to $01/73cb.

Ah ha!  I did a disassembly a little before this point and saw that $01/73b4 does "LDA $424E,X" and "CMP #1" and "BNE $73CB".  This is exactly what I've been looking for: something that checks to see if a value is equal to 1.  Therefore, $01/424E,X must hold the strokes for the current player!

Looking a little further back, at $01/73b1, I saw LDX $4218 right before the LDA $424E, X ; so $4218 must hold the current player (I assume it would be 0-3).

Returning from this function put me at $01/7473.  I saw that the previous call was JSR $7390.

I did not see anything that decrements the memory location jumping out at me so I went back to gdb's data breakpoint on this memory location ($01/424E) using the same technique that I described earlier.

Without even needing to start the game over again, I putted the ball again (which concluded with my game being over) and the memory breakpoint was hit... ($01/705E)


Here's the code that decrements the stroke count! :)

ROM:7058                 LDX     word_4218
ROM:705B                 LDA     $424E,X
ROM:705E                 DEC
ROM:705F                 STA     $424E,X

Looks like a simple NOP at $705E should do the trick to give unlimited strokes.

Using a sector editor, such as Copy II Plus, search for AE 18 42  BD 4E 42  3A  9D 4E 42 (I found it on block $40)

Change the 3A (DEC) to EA (NOP)

You can now show your kids this game without making them get frustrated!! :) :) :)

I've tested through level 2 and have no idea if something REALLY BAD will happen if the stroke count gets too high later, so take that as a caveat.  Possible changes to this mod are to hard-code the stroke count to something like 5 instead of NOP'ing out the decrement.


UPDATE 23 Oct 2015:

I wanted to try this trick again on a different disk but had forgotten how I hacked KEGS to do a memory search.  Here is the function I wrote for reference:

void find_zany()
        unsigned char array[] = { 0x50, 0x6C }; // "Pl"
//      unsigned char array[] = { 0x50, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x20 };   // "Player "
        unsigned char cmp[sizeof(array)];
        int iBank = 0;
        int iAddr = 0;
        int i = 0;

        // search all reasonable banks where it could be
        for (iBank = 0; iBank < 9; iBank++)
                printf("Checking bank %02x\n", iBank);
                for (iAddr = 0; iAddr < (0xFFFF - sizeof(array)); iAddr++)
                        for (i = 0; i < sizeof(array); i++)
                                cmp[i] = get_memory_c((iBank << 16) | (iAddr + i), 0);

                        // if we get a match
                        if (memcmp(array, cmp, sizeof(array)) == 0)
                                printf("Found a match at bank %02x, addr %04x\n", iBank, iAddr);

Inside dis.c, inside the function called do_debug_intfc(), I added this to the switch statement:

case 'k':       // zany golf


  1. Matt, this brings back so many cool memories - the time I spent (and wasted) on creating trainers for my favorite games - It has led to me still being a terrible player !

  2. Thanks Matt! I applied your hex change to the HD image found on the whatisthe2gs site and was finally able to finish the game. The final level took me 22 strokes but now I have a score to beat!