Skip to content

HowTo Patch A Trackload Game

Keir Fraser edited this page Dec 23, 2021 · 2 revisions

This is a brief tutorial on how to patch a trackloaded Amiga game to support FlashFloppy AutoSwap. This will apply to most arcade titles, whereas adventures and RPGs (among others) usually access the disk via AmigaDOS: the patching method for AmigaDOS will be covered in a separate tutorial.

Mortal Kombat

The game we will deal with in this tutorial is Mortal Kombat, published by Acclaim in 1993. This is a typical trackloaded title (by which we mean it accesses the drive hardware directly rather than using the OS) using RNC PDOS disk format and RNC CopyLock for copy protection.

The first decision is whether to work from original disk images and 'crack' the game as part of patching it, or to work from ready-cracked disk images. Where the disk format is not AmigaDOS sectors (as in this case, which uses non-standard PDOS) cracking will involve converting the disk format to AmigaDOS, so that it can be stored in ordinary ADF disk images. Since someone respected (NOMAD/Fairlight) has done the conversion from PDOS to AmigaDOS, it makes sense to use the existing crack as a starting point: we can be pretty confident that the crack will be 100% and will not introduce any more bugs or problems than the original game (and will most likely have fewer!).

flashfloppy-autoswap

The first step is to set up your build environment. Download the AutoSwap source repository and build the Mortal Kombat patch by following the Wiki instructions. Note that you need only build the MortalKombat subfolder; you don't need to build all supported titles.

The minimal AutoSwap patch

The full patch for Mortal Kombat is quite large, as it fixes some game bugs and makes various speed improvements to the track-load routine. Since I want to focus on AutoSwap specifically, I have developed a minimal patch which is what we will discuss here.

After following the build steps above to build the full patch, you can now inspect and build the minimal patch:

 # cd flashfloppy-autoswap/MortalKombat/minimal_example
 # make

You should be able to run the resulting disk images from a FlashFloppy DF0 and observe that there are no requests for manual disk swapping.

Where to start?? Work out what to patch

So we're all set, but where do you even begin? My general approach is to first work out what code I need to patch (the game loader(s), copy protection(s), disk-swap requester(s), etc), and then work out how to apply the patches.

A good method for determining what to patch is to run the game under emulation (which will generally mean some flavour of UAE). I suggest emulating a standard A500, Kickstart 1.3, with 0.5M chip and 0.5M slow (aka trapdoor RAM). Run the original cracked disk images (in folder MortalKombat/FL_Crack/) until a disk request is displayed. Now pause emulation and run the debug monitor (on E-UAE this is CTRL-C at the console, on WinUAE this is Shift-F12).

You will now find yourself running somewhere in expansion memory:

D0: 000000f2 D1: 00000000 D2: 00000000 D3: 00000000 
D4: 0000ffff D5: 0000f000 D6: 00001000 D7: 0000ffff 
A0: 00c082b6 A1: 00000b9e A2: 00c00300 A3: 00c07e7c 
A4: 00bfd000 A5: 00bfe001 A6: 00dff000 A7: 0000034e 
USP=00c014aa ISP=0000034e MSP=00000000 VBR=00000000
T=00 S=1 M=0 X=0 N=0 Z=1 V=0 C=0 IMASK=3
00c010c2 3f00                     MOVE.W D0,-(A7)
next PC: 00c010c4

We can assume the game code has been loaded into expansion RAM, and we can save off that 512kB memory state for closer inspection:

 # S game.bin c00000 80000

I always then convert the entire state file into a 68k disassembly. You can use any raw-binary disassembler for this, however I use my own disassembler contained within the Disk-Utilities repository -- so you may want to go grab that and:

 # cd Disk-Utilities/m68k
 # make disassemble
 # <now put 'disassemble' on your PATH>

You can then disassemble the game code to a text file:

 # disassemble game.bin 0 - 0 >game.txt

Load the text file into your favourite text editor. Also copy/paste the register state (shown above) from UAE into the top of the text file: that's your current execution context and we will work up the stack from there.

Take a look at the code at the current PC (c010c2 in my example). Note that the hex numbers at the start of the line are offsets into the memory dump: add c00000 to get in-memory addresses.

000010c2  3f00            move.w  d0,-(sp)
000010c4  3039 00df f01e  move.w  intreqr,d0
000010ca  0240 0010       andi.w  #10,d0
000010ce  6600 01a6       bne.w   1276
          ...

Start of a routine, messes with intreqr, it's probably an interrupt handler! Not very interesting so we will walk up the stack. Let's look at the stack in UAE:

 # m 34e
0000034e 2004 00c0 032a 0000 0000 00c0 8322 00c0   ....*......."..
0000035e 6b2a 0000 0001 0000 03a8 0000 0000 0000  k*..............

Yes, 2004 is saved SR, and c0032a is the return PC. So let's take a look at the routine containing the address c0032a:

00000310  48e7 8080       movem.l d0/a0,-(sp)
00000314  41f9 00c0 829e  lea.l   c0829e,a0
0000031a  600a            bra.b   0326
0000031c  48e7 8080       movem.l d0/a0,-(sp)
00000320  41f9 00c0 82b6  lea.l   c082b6,a0
00000326  1010            move.b  (a0),d0
00000328  b010            cmp.b   (a0),d0
0000032a  67fc            beq.b   0328        <-- PC here
0000032c  4cdf 0101       movem.l (sp)+,d0/a0
00000330  4e75            rts

See how in a full text file disassembly it is quite easy to see surrounding context and work out where the subroutine boundaries must lie. You should feel free to add newlines or comments into the text file as you go, to delimit subroutines and to record your findings so far.

So we're in a wait loop of some kind, it doesn't look very exciting. Let's walk up the stack again: skip the saved D0/A0 registers and the next return PC is c006b2a:

00006a80  6100 0102       bsr.w   6b84
00006a84  6b00 00fc       bmi.w   6b82
00006a88  4e56 ffc0       link.w  a6,#-40
00006a8c  48e7 4002       movem.l d1/a6,-(sp)
00006a90  4def 0008       lea.l   8(sp),a6
00006a94  41f9 00c0 832a  lea.l   c0832a,a0
00006a9a  4cd8 00ff       movem.l (a0)+,d0-d7
00006a9e  48d6 00ff       movem.l d0-d7,(a6)
00006aa2  4cd8 00ff       movem.l (a0)+,d0-d7
00006aa6  48ee 00ff 0020  movem.l d0-d7,20(a6)
00006aac  70ff            moveq   #-1,d0
00006aae  7400            moveq   #0,d2
00006ab0  6100 ee84       bsr.w   5936
00006ab4  2079 00c0 82e0  movea.l c082e0,a0
00006aba  6100 9b62       bsr.w   061e
00006abe  41fa 0106       lea.l   6bc6(pc),a0
00006ac2  4cd7 4002       movem.l (sp),d1/a6
00006ac6  0641 0030       addi.w  #30,d1
00006aca  1141 000f       move.b  d1,f(a0)
00006ace  4279 00c0 a95e  clr.w   c0a95e
00006ad4  4279 00c0 a95c  clr.w   c0a95c
00006ada  6100 f7dc       bsr.w   62b8
00006ade  41fa 00f7       lea.l   6bd7(pc),a0
00006ae2  6100 f7d4       bsr.w   62b8
00006ae6  41fa 0102       lea.l   6bea(pc),a0
00006aea  6100 f7cc       bsr.w   62b8
00006aee  2079 00c0 82e4  movea.l c082e4,a0
00006af4  2279 00c0 82cc  movea.l c082cc,a1
00006afa  6100 00b2       bsr.w   6bae
00006afe  1039 00c0 82b8  move.b  c082b8,d0
00006b04  0200 0020       andi.b  #20,d0
00006b08  13c0 00c0 6c1e  move.b  d0,c06c1e
00006b0e  08b9 0005 00c0  bclr.b  #5,c082b8
00006b14  82b8 
00006b16  41fa 00e6       lea.l   6bfe(pc),a0
00006b1a  203c ffff 0000  move.l  #ffff0000,d0
00006b20  7402            moveq   #2,d2
00006b22  6100 ee20       bsr.w   5944
00006b26  6100 97f4       bsr.w   031c
00006b2a  6100 9736       bsr.w   0262          <-- PC here
00006b2e  67f6            beq.b   6b26
00006b30  4cd7 4002       movem.l (sp),d1/a6
00006b34  614e            bsr.b   6b84
00006b36  6aee            bpl.b   6b26
00006b38  6100 97e2       bsr.w   031c
00006b3c  6100 9724       bsr.w   0262
00006b40  66f6            bne.b   6b38
00006b42  103a 00da       move.b  6c1e(pc),d0
00006b46  8139 00c0 82b8  or.b    d0,c082b8
00006b4c  70ff            moveq   #-1,d0
00006b4e  7400            moveq   #0,d2
00006b50  6100 ede4       bsr.w   5936
00006b54  2079 00c0 82cc  movea.l c082cc,a0
00006b5a  2279 00c0 82e4  movea.l c082e4,a1
00006b60  6100 004c       bsr.w   6bae
00006b64  6100 9afe       bsr.w   0664
00006b68  4cd7 4002       movem.l (sp),d1/a6
00006b6c  41ee ffc0       lea.l   -40(a6),a0
00006b70  43e8 0020       lea.l   20(a0),a1
00006b74  70ff            moveq   #-1,d0
00006b76  7402            moveq   #2,d2
00006b78  6100 edca       bsr.w   5944
00006b7c  4cdf 4002       movem.l (sp)+,d1/a6
00006b80  4e5e            unlk    a6
00006b82  4e75            rts

A relatively massive subroutine! It's not so easy to work out where it starts, but note the immediately preceding instruction at offset 6a7c is a BRA.w. You can also search for 6a80 in the text file and find several callers (both as BSR 6a80 and JSR c06a80: don't get confused by this, the BSRs are PC-relative as if the expansion memory was based at 0x0).

So this looks like the disk-swap routine, and look at that first BSR: depending on how it sets the condition codes, this whole subroutine gets skipped. Could this be the "check disk id" subroutine?

If you look at the code at 6b84 and follow the BSR chain down a few steps, you should find yourself at 6de8, 6ebe, 6ed8, 6f16, 6f26. Find the subroutine boundaries and add short comments as you go.

The routine at 6f26 is interesting. It saves all registers, it loads up custom register addresses into registers, and sets up a stack frame. This looks like a dropped-in blob of code. And indeed if you search a little further into the text file, you will find references to disk registers: dsklen, dsksync, etc. We have reached the sector loader!

Okay, so: we have a suspected disk-swap routine at 6a80. Its first invocation is down a path which reads from disk (if you paid attention it looks like registers d1-d2 may get set to read one sector at offset 0x18, which sounds like a disk ID and/or file table). Let's look at the callers to 6a80: you should find them at 699e, 6efc, ab8a, and 16004. And note how all of them set up d1 to a small integer: a disk number?

A sensible path then will be to replace the bulk of this routine with a call to the FlashFloppy AutoSwap code. We could BSR to AutoSwap at 6a88, and then BRA back to the subroutine start (6a80). We will have the disk number in d1 on entry to AutoSwap.

Next question: Where do we put our patch code? In some games this can be a real problem, it needs to be placed in spare room on disk and in memory. A good place can be bypassed copy protection routines.

This Tutorial is Work In Progress: I will update it with the remaining text in the next week or two!