It’s been quite a few years since I did my tutorial for cracking Golden Axe. After recently releasing a javascript based Atari ST emulator I feel some nostalgia for the old 16 bit days so I figured it might be time to brush up on my old Amiga cracking skills and put something together for Flashtro.
I thought I’d start off with a game that we are all probably familiar with. Lemmings (2 disk version CAPS release 0132). I would classify this one as a medium to tricky one to crack. This tutorial is going to attempt to explain the steps in pretty basic terms but it’s going to be a long slog and will require some basic understanding of the use of Action Replay and some understanding of MFM and load routines. This may not be the best tutorial for beginners. There’s a lot of swapping disks and resetting and starting again from the bootblock code involved in following this tutorial. I won’t be reminding you every time which disk needs to be in the drive, you must remember each time to swap disks when you need to load files into memory and swap back to the original or crack disks as needed.
I no longer own a real Amiga so for the purposes of this tutorial I’ll be using WinUAE. For this game I’ll be running using a standard A500 machine but to make things easier I like to take advantage of the following:
· Action Replay III Cartridge – useful for general poking around, reading disk data into RAM and messing with it. Good for assembling very small bits of code to test.
· Devpac – my assembler of choice.
· 2MB Fast RAM highly useful for dealing with large disk image data in memory with Action Replay. Located at $200000 - $400000 on my setup.
Our usual approach to cracking Amiga games would be to consider the following possibilities we may have to deal with:
· Non-standard MFM disk format(s)
· Single track protections (eg. Rob Northen CopyLock)
· Manual / Codewheel type protections
· Checksums
· Dongles (rare)
So our first step is to determine which of these we may have to deal with. Simply load up your lemmings disks and use your preferred copy program to look at the contents. You will see that only track 0 is standard and everything else is not. So now we know we have non-standard MFM disk format to take care of first. (See figure 1)
So we start at the boot sector. Boot up till the insert disk screen appears then enter action replay insert the first disk and use the RT command to load the first track into memory (see figure 2).
Now let’s disassemble the bootblock (see figure 3a and figure 3b).
What we see in the bootblock is some pretty standard stuff. First off is a call to -$1c8 in exec.library – this should be very familiar. It’s loading data from sector 2 onwards for $1200 bytes (which would be the remaining 9 sectors of the first track). This is loaded at address $70000. It then attempts to allocate memory in various configurations and stores the results in d0 and d1. It then jumps to $70000 and starts executing the previously loaded code.
We already have the whole of track 1 loaded into memory at $40000, so we can quickly copy the data to $70000 (using the TRANS command) and see what is happening there. (See figure 4a, 4b and 4c).

Figure 1 - burst nibbler – built into action replay III

Figure 2 – action replay- load track 0 into memory

Figure 3a – boot block disassembly (1)

Figure 3b – boot block disassembly (2)
First of all we don’t see much. Keep going and at $70070 we will start to see something more interesting. Then we see some basic set up code and a couple of calls to $700e0 followed by a jmp to $400.This is interesting as we know we don’t currently have anything interesting loaded to $400 so these calls must either load more data from the disk or copy to $400 from somewhere else in memory.
Let’s take the first call to $700e0 first. It loads $60000 into a0 and $61000 into a1. Since we don’t have anything in memory at this point it’s a good bet it’s going to store something there. We’ll now start the original bootblock code executing, put a breakpoint at $40048 (BS $40048). We then use G 4000C to start executing the bootblock. When the code stops at $40048 we next put a breakpoint at $700c4 and let it run again (use command X) then we can see what’s in memory at $60000 and $61000. (See figure 5a, 5b, 5c, 5d, 5e and 5f).
This is interesting we see something at $60000 that looks like a file table and at $61000 we see something that looks very much like raw mfm data (the many $aaaa words and $4489 near the start are a dead giveaway). The mfm data is not of much interest (other than it suggests that the code we have just called is a load routine of some kind).
Looking at the file table in more detail it clearly starts with a list of files of some kind but then there appears to be a second part at $60c00 that we can’t immediately identify. We should now save off the data between $60000 and $61000 to a blank disk for later. (See figure 5g).
Now if we return to our disassembly from the current address we see another call to $700e0 passing in $70610 and $400. Now that we have an idea that $700e0 is a load routine of some kind we can probably assume that $400 will be the load address. If we get a memory dump of $70610 (See figure 7) we see what looks like it is probably a filename – which seems to make sense as it’s a file listed in the file table we saw at $60000.

Figure 4a – disassembly 2 (1)

Figure 4b – disassembly 2(2)

Figure 4c – disassembly 2 (3)

Figure 5a – run code until after first call to $700e0

Figure 5b – memory dump starting at $60000

Figure 5c – continued memory dump (1)

Figure 5d – continued memory dump (2)

Figure 5e – continued memory dump (3)

Figure 5f – continued memory dump (4)

Figure 5g – save file table
So we can see that the same routine is being called twice and it seems to have different results each time. Looking at the routine starting at $700e0 we can see that very soon after it is called it branches off to different parts of the code based on the value in D1. (Starting at $70106). We can see that from the calls we are seeing so far that 5 in D1 will load up the file table and 0 in D1 will load a file. What the other values in D1 may do at this stage we don’t know. They don’t seem to be used in this part of the code anyhow so at this stage we don’t need to worry about this right now.
Now that we have a very basic understanding of this load routine we can put a breakpoint at $700D4 (BS $700D4) and we will see if our ideas so far are correct. Let the code continue running (use command X). Now we can get a disassembly of the memory at $400 and see what we can see. (See figure 6a). Definitely looks like we have something loaded right here.

Figure 6a – disassembly of main file loaded at address $400
Now that we have found a load routine what we want to try and do is see if we can use this load routine to try and extract all of the disk data (or even better both disks). If we are going to save this data to an AmigaDOS disk in files we will need 3 blank disks in order to fit the data onto.
![]() |
What we need to figure out is how we can fool this load routine to loading the data we want to extract. We want to try and extract the first disk in two chunks. We will be looking to extract tracks 0 – 39 and then 60 – 79 and save the two areas of memory onto two of our blank disks.
Taking a closer look at the load table in figures 5a – 5e we see at the start a list of files but then there is only four other bytes of additional information for each file eg $7008 in the case of the main file. We would normally expect a disk location (eg. Start sector or similar) and a file length. So we don’t quite know how this load routine calculates these two things from this file entry. Since we currently have the main file loaded we can have a quick look and see where this file ends and see if that gives us a clue. If $7008 is the length of the file then if we take a look at $7008+$400 we should see the end of the file. (see Figure 6b). I dump from $73F0 and it definitely looks like we are onto something here.
So if the values listed in the file table are the file lengths we can quickly total them up as a double check to see if we end up with something sensible looking. A quick calculation gives around 747k so that seems reasonable to me. So what we don’t know now is how the loader calculates the correct position on the disk to load the file from. Looking at the load routine in more detail we see that the main code for the file loader starts at $70160 (See figure 7a).

Figure 7a – load routine(1)

Figure 7b – load routine continued(2)

Figure 7c – load routine continued(3)

Figure 7d – load routine continued (4)

Figure 7e – load routine continued (5)
Looking at the routines called we see nothing much of interest at $7054a (just some drive select stuff). Looking at $70292 (See figure 7b) we see something a little more interesting. Some code that looks like its comparing something (a filename?) and then skipping $10 bytes if it doesn’t match. This looks to me like a routine to find our file in the file table given the filename. It returns the file entry in D1 or 0 if it can’t be found. Now we look at the next routine (Figure 7c, 7d, 7e). This is interesting again as the first thing its doing is using our file number, doing an LSL #4 which multiples by $10 and then pulls out the value at that offset plus $C into D2 so that’s pulling out the length and storing it. Next we see that we pull out what appears to be the start of the file table and adds $C00 so this points us to the second part of the file table that we currently don’t understand.
It then searches through that part of the file table looking for the file number that is stored in D1.
Now that we know that the numbers in the second part of the file table correspond to the files in the first part we can theorise that it might be a file bitmap. eg. Each byte identifies which file it belongs to. So by finding the first byte with the matching file number it is locating the start of the file. If we want to extract the disk data we can do it by manipulating this file table to fool the loader into pulling the whole disk into ram. If we look at the file table again and for the main file we see that the file size is $7008 and the file number 1 occupies 29 blocks in the second part of the file table. If we divide $7008 by 29 we get $3DC so we can probably assume that each block in the file table is $400 (1024) bytes. This is confirmed by the reserved file that is $4000 bytes and occupies the first 16 blocks (16 * $400 = $4000). If we calculate the total number of blocks (starting at $60c10 and ending at $60ef0 that gives us a total of $2e0 blocks. We will try to load this data in two halves. $2e0 blocks / 2 is $170 blocks or $5c000 bytes.
If we manipulate the file table so that the main file is $5C000 bytes long and occupies $170 of the blocks we should end up loading $5C000 bytes from the disk. If we load this at address $400 we should have enough room in memory to store this data.
See figure8a and 8bfor the command to fill the memory in the file table and the results. Then we modify the file length in the first part of the file table. (see Figure 8c)

Figure 8a – file table – modified (1)

Figure 8b – file table - modified (2)

Figure 8c – file table – file size modified

Figure 8d – re run loader

Figure 8e – save disk 1 part 1

Figure 8f – file table modified to load second half of disk 1 (1)

Figure 8g– file table modified to load second half of disk 1 (2)
Now we are ready to rerun the load routine with the relevant values. Reset the PC to $700c4 (see Figure 8d) then you can let the code run again to the breakpoint at $700D4. Once the breakpoint hits you can save the memory from $400 to $5C400 (see figure 8e) to our first blank floppy. Now we can update the file table to extract the second half of the data. (See figure 8f, 8g) and re-run the code (don’t forget to re-insert the lemmings disk 1 first)and save out the second half of the data to the blank floppy (see figure 8h).

Figure 8h
Now we want to try and get the second disk in the same way as the first disk. We will need to restart the code from the point that loads in the file table so we can see what’s on disk 2. Insert disk 2 and reset the code to $700AE and put a breakpoint at $700C4 so we can look at the file table. (see figure 9a) then let the code execute to the breakpoint. Now dump the file table memory (see figure 9a to 9j). Save the file table off to a blank disk for use later (see figure 9k)

Figure 9a - Read file table from disk 2

Figure 9b – Memory dump of file table from disk 2(1)

Figure 9c – Memory dump of file table from disk 2(2)

Figure 9d– Memory dump of file table from disk 2(3)

Figure 9e– Memory dump of file table from disk 2(4)

Figure 9f– Memory dump of file table from disk 2(5)

Figure 9g– Memory dump of file table from disk 2(6)

Figure 9h– Memory dump of file table from disk 2(7)

Figure 9i– Memory dump of file table from disk 2(8)

Figure 9j. – Memory dump of file table from disk 2(9)

Figure 9k – Save off file table for disk 2.
There are a lot more files this time and if we look at the amount of blocks used $60c10 to $60fb3 that is a total of $3a3 (931) blocks which equals $ec800 (953344) bytes. We can use the same technique as for disk 1 to extract the contents but we will need to use fast memory this time as we won’t have enough free chip memory (we can only load between $400 - $60000 otherwise we will overwrite the mfm buffer). We must also change the file name of the first file to be ‘main’. This will result in more data than we can fit on one disk. Save the files in two parts onto two separate disks. (See figure 10a, figure 10b and figure 10c).
The two files extracted from disk 2 (length $74400 and $74800) total $e8c00 bytes or 953344. We need to figure out a way we can get this data written back to disk. A couple of possibilities are
1) (Re)pack some of the files and attempt to reduce the data to a size that will fit on the disk
2) Split the data over two disks resulting in a 3 disk crack (this will pose additional problems as we will need to somehow make the game ask for the correct disk as needed).
3) Try to organise the data in such a way that it can still fit on two disks.
In this case repacking the files might be a good idea as creating a 3 disk crack would be the least preferable option. However given the number of files on the disk and the block size being used ($400) it’s entirely possible that if we re-organise the files so that they are entirely back to back on the disk and use a byte based loader that might work. Looking at the file table we see that there are $92 files. If we assume that the wasted space at the end of each block will be on average half the block size then that equals $92*$200 = $12400. The total size $e8c00 - $12400 gives $d6800 or 878592 bytes. This should be enough to fit nicely (in theory). What we need to check now is whether or not this will work in practise.

Figure 10a – set up ready to load disk 2 data part 1

Figure 10b – save data from disk 2 part 1

Figure 10c – extract and save disks from disk 2 part 2
Take a look at figure 11a and see the code I wrote to run through the files and create a new copy of the file data with all of the blank space between removed. This expects the source disk data to be stored at $200000 and it will create a new copy at $300000. If we setup and run this code (See Figure 11b). We see in the register a2 it has written up to address $3d8828 – which means it has written $d8828 bytes (or 886824) which equates to around 866k. This is good news for us. Write this data out to tracks starting at track 2 to a new blank disk. (See figure 11c). The reason we are starting at track 2 is to give us some space at the start of the disk for other stuff eg. The file table and anything else we might want to store.
Clear out the memory between $200000 and $400000 and we can now repeat the same process for disk 1 then we have the data from both disks saved. (See figure 11d, 11e and 11f)

Figure 11a – code to remove the blank space from the disk data.

Figure 11b – setup and run code to remove blank space.

Figure 11c – save disk 2 data to a new blank disk tracks starting at track 2.

Figure 11d – clear memory ready for disk 1.

Figure 11e – convert track data for disk 1

Figure 11f – write out track data for disk 12c
Now we can write out the file table to each of the two disks starting at track 1. Before we save we need to update the first Reserved file entry to point to the start of track 2 ($1600 bytes per track on standard AmigaDOS disk so start is $2c00 for track 2) -see figure 12a and 12b. Next we copy across track 0 (bootblock and the code that gets loaded from the bootblock) onto our cracked disk 1. (See figure 12c).
We should now have all the mfm data extracted from the original disks that we need and we can start thinking about patching the load routines to load from our copied disks. As an mfm cracker you should have a load routine of choice. Ideally this load routine will be as compact as possible and as flexible as possible. It should be entirely pc relative and not rely on processor loops for its timings. In this case it will also need to be a byte passed loader (eg you can pass in the start position as a byte offset). It should also not use the blitter for mfm decoding.
My own load routine is 766 bytes and I keep a precompiled binary handy. This tutorial will not walk you through creating your own loader. You can do this as a separate exercise, use one of the ones posted on Flashtro.com or use the one provided here. My loader expects the following:
load address in a0
mfm buffer address in a1
start disk offset in bytes in d0
length (in bytes) in d1
drive number in d2
it returns:
error code in d0 (0 = ok, non-zero for error).
In order to use this we will need to write a small piece of interface code which will set up the registers as passed into the Lemmings loader. What we know of the Lemmings loader so far is:
Register D1 indicates operation code.
Operation code 5 is a load file table operation which takes
Register d0 - unknown
Register a0 – file table load address
Register a1 – mfm data buffer area
Operation code 0 is a load file operation which takes
Register a0 – filename
Register a1 – load address.
Returns:
D0: error code (0 = ok)
D1: file size (we must emulate this in our loader otherwise it will fail later on)
We will need to store the file table address and mfm addresses as these are not passed into the main load routine and in addition we will need some code which calculates the start address and length based off of the filename. We will search the file table for the correct file and then pull out the length and then total up the lengths of the preceding files to calculate the start offset. See figures 13a through 13d. Notice the code at $50058 we stole from the original loader to search the file table and the code at $50092 that pulls out the file size from the relevant file table entry and then totals up the previous file table entries to determine the load start address. The main load routine itself starts at $500b6. It is not shown here.
Now we can test our loader by loading in the boot track, executing until a sensible point and then load our new loader over the top of the original loader and see how we go. (See Figure 13e and 13f). We now have code loaded at $400 so looks like we are good so far.
If we now patch the code on disk we have the start of a cracked disk 1. (See figure 13g). Repeat the steps to test the loader and get a disassembly of the code at $400 (See figure 13h). Looking at the code the first thing it does is some basic hardware register setup and then copies the code from $44e to $78ad0. It then executes a trap to start executing at $78ad0. Breakpoint at $44c and then at $78ad0. Then get a disassembly from $78ad0 (see figure 13i). Take a quick look at this code then replace the original Lemmings disk in the drive and let it continue on loading. If all works well then we are doing well.
Restart the process all of over again (read track 0 into memory, breakpoint at $40048, run, breakpoint at $700f0, run, breakpoint at $44e, run, breakpoint at $78ad0, swap disks back to original disk 1, run) then as soon as it starts loading again jump into action replay.
Chances are now you will be in the next loader code. Scroll around and have a look at the code and you may start to notice some familiar things from the previous loader we patched. Eventually you should end up around $7f570 which should look like the start of the familiar loader. (See figure 13j). This looks to be another copy of the exact same load routine. We should try and patch this one with our new loader. (See figure 14a and 14b). Ensure the cracked disk 1 is in the drive and let it run. Success (See figure 14c)

Figure 12a – write out disk one file table.

Figure 12b – write out disk two file table

Figure 12c – copy track 0 from the original disk to the cracked disk 1.

Figure 13a – loader interface code (1)

Figure 13b – loader interface code(2)

Figure 13c – loader interface code (3)

Figure 13d – loader interface code(4)

Figure 13e – testing new loader(1)

Figure 13f – testing new loader(2)

Figure 13g – update crack disk with new loader

Figure 13h – main file code at $400

Figure 13i – stepping into code at $400

Figure 13j – second loader start.

Figure 14a – run through loading sequence until code is located at $78ad0

Figure 14b – patch second loader with our load routine

Figure14c – success.
Let the loader run as long as it can and eventually it gets stuck and if you jump into the action replace you will see we are stuck in a loop. (See figure `15a, 15b). If you scroll back through the code you will see we are stuck at the start of our load routine with it having detected an unknown operation code of 2 in D1. We now need to take a better look at the original load routine and see what happens when it is called with a value of 2 in D1. Looking at the load routine in figure 13i we see it jumps to $7f610 in the case of 2 being passed into D1. We can now boot up the original, wait for the intro animation to play and put a breakpoint at $7f610 to see what’s happening.
It’s not immediately obvious what’s going on here as there isn’t much to go on without diving into all of the subroutines. However upon a little examination (figures 15c to 15f) we see that the routine at $7f684 (which is the one called when the request is made to load the file table) we see that it stores the file table load address at $4(a5) and our routine pulls out this address then calls the same piece of code repeated 4 times. To my eye this looks like it possibly reloads the file table to the original address $400 bytes at a time. We can test this theory by putting a breakpoint at $7f640. Having a look at the memory where the file table should be then running the remainder of the code and looking at the memory again (See figure 15g). When we do this we see that the file table has been reloaded. We can now go back and make some additional changes to our loader to include this functionality. Our load routine now looks something like figure 15h.
If we now repeat the process shown in figure 13i and 14a to patch the second loader with our new load routine (see figure 16e) and the result is that we now manage to make it all the way to the insert disk 2 prompt (see figure 16f). Although there does appear to be a little bit of corruption so something isn’t quite 100%

Figure 15a – stuck in an infinite loop

Figure 15b – our loader code. Stuck with a value of 2 in D2.

Figure 15c – original loader call at $7f610

Figure 15d – original loader call continued

Figure 15e – more original loader disassembly

Figure 15f – more loader disassembly.

Figure 15g – file table memory dump.

Figure 16a – updated loader (1)

Figure 16b – Updated loader(2)

Figure 16c – Updated loader(3)

Figure 16d – Updated loader(4)

Figure 16e – repeat the loader process

Figure 16f – waiting for disk 2.
What we need to do now is to update the second loader onto the disk that way we can more easily test how things are working. Read in the disk tracks starting at track 2 (where our main file data starts) and start disassembling from here (see figure 17a). We know that our load routine needs to end up address $7f570 and the start of the relocated data is $78ad0. We can see that the start of the code to be relocated is at $4004e based on the data we have read from the disk. So we calculate $7f570-$78ad0+$4004e and that’s where we should find the load routine. So a quick calculation in action replay and a disassembly shows we are on track (see figure 17b). We can now load our loader routine into memory at the correct point then write back the tracks to disk. (See figure 17c). Rebooting with our crack disk in the drive should now result in getting as far as the insert disk 2 screen.

Figure 17a – disassembly of the main code

Figure 17b – loader calculation and disassembly.

Figure 17c - load our loader and resave the tracks back to disk.
If we try to insert our disk 2 and click the mouse the disk is not recognised. If we jump into action replay we will most likely see we are executing code in the low area of memory around the $1000 - $2000 (see figure 18a). Previously we were executing code around the $78000 - $7ffff area of memory so it’s a good bet more executable code has been loaded. If we reset and reload our cracked disk 1, then wait for the intro to start playing we can then put a breakpoint at our file load routine to see what’s being loaded next. Quit the intro with the mouse and we get a breakpoint hit (see figure 18b).
Looking at what’s in the registers and memory we see that a file called Code is being loaded to $400. Put a breakpoint at the end of the load routine and we can investigate what is at $400 (see figure 18c). Doesn’t look like any valid code and nothing obvious in the data. Might be a packed file so we can step through the code and little to see what the next steps are. (See figure 18d). We see that pretty much straight away we end up at a routine that takes the load address and does something then the code is started executing at $400. A good sign that the routine at $7932c is a depack routine. Try breakpointing at $7c690 and we can then get a better view of what’s going on at $400. (See figure 18e) – This looks more promising. If we re-insert the original disk 1 at this stage and let it run everything then loads correctly and the original disk 2 will also allow you to run the game fully.
There is clearly some stuff we need to patch in the code we have just been inspecting. Probably another load routine at the very least. If we want to patch this file we have two options.
1) Depack and repack the code with the changes then update the file table respectively
2) Patch the code on the fly after it has been depacked prior to the JMP $400 that we see at $7c690.
Looking at the depack code it seems as though it is packed with a modified version of the bytekiller packer (with the header placed at the end of the file). It should be possible to unpack and repack but instead of doing that lets look at on the fly patching.
Firstly let’s try finding the third loader and patching it in memory to see how we get on. Use the FAQ command to find references to $dff020 (disk pointer register). See figure 18f. Disassembly backwards from there and we will find the start of the load routine (see figure 18g).

Figure 18a – example of code being executed while waiting for disk 2.

Figure 18b – file loader call after the intro

Figure 18c – examination of what’s at $400 after load routine is completed.

Figure 18d – probable depack routine.

Figure 18e – depacked code at $400.

Figure 18f – looking for the third loader

Figure 18g – found the now familiar loader code

Figure 18h – load our loader over the third loader code.

Figure 18i – call to load routine

Figure 18j – results of file load

Figure 18k – unknown data set up in D2 before the call to the load routine.

Figure 18l
If we now load our loader routines over the top of the existing code at $7fb2 (See figure 18h) and let it run. There is a problem here – it simply crashes out so somewhere something is wrong. So let’s repeat the process again but this time instead of letting it run put a breakpoint at $7fb2 so we can see where we are going wrong.
First off after setting the breakpoint and letting the code continue we see that there is some drive activity and if you are using WinUAE you will see the drive heads seek to track 0 and stay there for a while then return to their previous position. If you are using a real Amiga you will probably hear the familiar grinding of what sounds like a CopyLock. For now we will ignore this but we will come back to this later. After the track 0 seek we hit the breakpoint and then get a dump of the registers (see figure 18i). At first glance this looks like a standard call to the load filetable routine – however if you breakpoint the same code and run it through on the original disks you will see that the file table load routine fails at this point and returns a nonzero value in D0. (See figure 18j).
If we then look at where the code is called from at this point we see that some value is passed in D2 that we don’t really know the meaning of (see figure 18k). The value in D2 looks like ASCII text codes and if you examine them as ASCII you will see they are ‘Grou’ and if you look at the file table for disk 2 you will see that this is the first 4 characters of the first file (ignoring the reserved file) on disk 2. (See figure 18l). Clearly when a value is passed in D2 the routine expects it to be the first 4 characters of the first file in the file table and it will return an error code in D0 if this is not the case. We now need to update our loader code to comply with this case. See figure 19a for the slightly updated bit of the loader.

Figure 19a – updated loader.

Figure 19b – patch third loader in memory.

Figure 19c – use TRANS command to verify we can copy from loader 2 to loader 3.
If we now repeat the routine going back to breakpoint at $7c690 after the code has been depacked and then load up our load routine to overwrite the loader at $7fb2 you should see that it can now recognise our crack disk 2 and starts loading but then it crashes. What we can do now is wire our updated (again) loader into the loader 2 in the same way we did before and that way we can wire in some code to copy the loader 2 over the loader 3 before executing the jmp $400. (See figures 17a through 17c for the steps to redo loader 2). We can then repeat the same routine going back to breakpoint at $7c690 after the intro and this time we should be able to use a TRANS command to copy loader 2 over loader3 just to prove that this will work. (See figure 19c).
This seems to work – so now if we do some strategic breakpointing and disk juggling at the right times we should be able to allow the track 0 check to read from the original disk and then still load everything else from our cracked copy. Breakpoint at $7c690 again and then use TRANS to copy over our loader to loader 3. Put a breakpoint at $7fb2 so we can re-insert our crack disk at the correct point in time and insert the original disk 1 before allowing the code to continue. Wait for the breakpoint at $7fb2 and the put the cracked disk 1 into the drive. If all is well at this point it will load right through and you will be able to even play through the game.
Now we have successfully extracted all of the data and patched the loader (although we still need to write the code to do the loader 2 to loader 3 memory copy). We can progress onto the CopyLock. Since this will also likely require a patch to the packed code, I am leaving the additional code we need to write until we know how best to bypass the CopyLock. At this stage we don’t know for sure that it is a CopyLock, it could be any similar type check.
In order to verify we can breakpoint at $7c690 again so that all the code is depacked and search for references to address $24. This this is the trace vector and will definitely indicate whether or not this is a CopyLock or some other similar single track check. What we see in figure 20a is the results of the search. Disassembly of these routines is shown in figures 20b, 20c and 20d. So to my eye the first two look like CopyLock routines and the last one is just random – nothing interesting there.
If we breakpoint at $4fd8 and $4fd8 and $76b8 we can see which of these is being triggered just after the intro. Figure 20e shows the results. If you then disassemble from $7638 until you find the first non-encrypted code you will see something familiar. (See figure 2f). This is the end of the CopyLock and the start of the loader3 routine. Just to make sure we can set a breakpoint at $7fb0 and see. (See figure 20g). Quite often when CopyLocks are implemented the first thing you see after the encrypted code is a check of the serial number returned. The coders have not been so silly in this case so we will need to make some effort to find out what the CopyLock is doing. It’s also not obvious from what’s in the registers after the call what the serial number might be and the other common place for values to be returned from the CopyLock is in the very bottom of the chip memory. We take a dump of this in figure 20h. Nothing much jumps out.

Figure 20a – CopyLock?

Figure 20b – CopyLock 1?

Figure 20c - CopyLock 2?

Figure 20d – CopyLock 3?

Figure 20e – CopyLock results

Figure 20f –end of CopyLock code

Figure 20g – after the CopyLock has run.

Figure 20h – dump of low memory.

Figure 21a – copy all chip memory to fast ram and run CopyLock
m 2000
Figure 21b – routine to extract memory changes (1)

Figure 21c – routine to extract memory changes (2)

Figure 21d – running our routine to capture memory changes.

Figure 21e – memory differences table

Figure 21f – code to replay memory changes.

Figure 21g – load up rnc patch and test

Figure 21h – install loader3 and rnc patch code

Figure 21i – install loader3 and rnc patch code saved to disk 1

Figure 21j – found a spare bit of space and put some code in to load our patch installer.

Figure 21k – jump to our patch routine
So far we are drawing a blank. We know that if we don’t run this CopyLock the game stops before the main menu and also there are corrupted graphics at the insert disk 2 code so we need to figure out what it’s up to. There are several tutorials on Flashtro on how to patch a CopyLock. I’m going to use a different method than those I’ve seen demonstrated so far. This method works quite well if you have access to the original disks. It’s not feasible to use this if you only have a warp of the disk data for example.
Using the original disks we will breakpoint at $7638 and $7fb0. (First breakpointing at $7c690 as usual as a stepping stone). Then when we hit $7638 I take a copy of the whole of the chip ram into the top end of my 2mb fast ram. We then disable all interrupts to prevent any interference and run the CopyLock till it ends. I then execute some code wrote to create a table of memory differences in high fast memory. It also saves the contents of all of the registers immediately after the CopyLock call. See figure 21b and 21c for this routine. You can see it compares 0-80000 against $300000-$380000 and creates a difference table at $200000. (figure 21d) Once this is run we then save the file to disk (figure 21e).
We can then replace the whole CopyLock with some code I wrote to replay the memory and register changes. (see figure 21f). So if we now try this on our cracked disk we should be able to bypass the original disks completely. The usual breakpoint at $7c690 where we then use TRANS to copy over our loader2 to loader3 and also load up the CopyLock patch routine. Success!
That seems to work. We have successfully patched the CopyLock without even having to figure out what exactly was going on in there.. Hold on though remember there was another CopyLock. I did some further testing to try and trigger this CopyLock and so far have not managed to trigger it. I took a look at the code and it simply checks the returned value directly against a hard coded value. I tried running the CopyLock against both original disks 1 and disk2 and it did not return the correct key code so I can only assume it is a dummy.
So what we need to figure out now is how to hook into the code at $7c690 and copy over our loader2 to loader3 and patch our CopyLock. Again we could depack the main code file and modify the routines and repack but it’s simpler just to patch on the fly. Using my load routine I probably have enough space to tack a mem copy routine onto the end of it but we definitely don’t have enough spare space to the rnc patch as well. The easiest way is to use the load routine we already have in memory to pull in a few bytes from the disk and run those before jumping to $400. The code we want to execute would be something like what we see in figure 21h. We can put this code immediately after the file table on disk 1 (see figure 21i).
Once we have done this all that remains is to plug some code into the spare space we have left on the end of the loader2 to make a call to load our patch code and execute it. See figure 21j and 21k. Notice that there are two places that do a jmp to $400 that we need to patch. One is when you let the intro run to the end and the other is when you click to exit the intro.
Now we have a crack disk that will run fully and allow you to play the game with no intervention.
As a side note, the CopyLock we have bypassed is a particularly tricky beast in this case. It patches several bits of memory that otherwise will cause problems later on. In addition it stores some data in low memory right at the top of the stack at $3fe. You can see exactly what bytes it changes by looking at the difference data we built but in the end it is of little consequence since we have bypassed it by replicating its output.
Not quite. If you play test the game for any length of time you will notice that eventually it will crash or start acting strangely (usually around level 10 for me if I load it up and start playing from the first fun level).
Seems like either we did something wrong or we missed something. Could be any of the following:
1) Checksums (very possible)
2) Problems with the CopyLock bypass (unlikely given the technique used)
3) Corrupt data extracted from the disk (possibly)
4) Something else we missed
If its checksums it could potentially be in any of the areas of code we have changed. The most likely place would be in the main code though where we bypassed the CopyLock or the load routine. Next step is to try and rule out a checksum so that we can consider the other points where we may have made a mistake or missed something else vital to the running of the game.
If we load up the game from the original disks then breakpoint at our usual place of $76c90 have a look at $7638 where the CopyLock routine lives and $7fb2 where the loader lives we can find a convenient place to try and make some insignificant changes and see if that triggers anything (see figure 22a). Two things spring to mind. Replacing the MOVEQ #0,D0 with CLR.L D0 (at $7644) and replace MOVE.L #DFF000,a6 with LEA #DFF000,a6 at $7fb6. Neither of these changes should affect the running of the code but will generate different opcodes but of the same length of the originals so wont overwrite anything unintentionally and should hopefully trigger something if we are dealing with checksums. When I did this I was able to recreate the crashing problem successfully. Clearly we are dealing with some kind of checksum.

Figure 22a – original CopyLock code and loader code
Here’s where the fun starts. We know there is at least 1 checksum of some kind but we have little clue where to search for it. I made quite a few attempts at finding it before I finally down the culprit. With these kind of things its often a case of lots of work manually locating the code. I tried various techniques including manually scanning the code for EOR instructions which is often an obvious trick for calculating checksums. I tried manipulating data in memory to try and figure out what kind of checksum we are dealing with (eg. Try EORing two adjacent bytes, words, longs in memory with the same value to see if it’s a simple EOR over the whole memory range). Tried the same technique for ADD and SUB (eg Add a value to one byte, word and long and subtract it from the next). Trying to figureout what will and what won’t trigger the routine. None of this got me very far in this case.
I also tried searching for refences to $7638 and $7fb2 to see if that was the start of the checksummed area and I could find it that way. No joy there. I then tried modifying various random areas of memory in an attempt to figure out what the scope of the checksum was. Everything below our load routine seemed to trigger it. I still had no luck in finding the actual code. I also tried FAQ on address $400 to see if we could see anything useful there.
I did only see one piece of code that contained an EOR when I started scanning through the code from address $400. (See figure 22b). It looked slightly suspect but I couldn’t figure out what was going on and it didn’t look like an obvious checksum routine to me.

Figure 22b – slightly suspect code.

Figure 22c – waiting for mouse click

Figure 22d – interrupt routine (1)

Figure 22e – interrupt routine(2)

Figure 22f – interrupt subroutine $1d80 (1)

Figure 22g – interrupt subroutine $1d80 (2)

Figure 22g – interrupt subroutine $1e82(1)

Figure 22h – interrupt subroutine $1e82(2)

Figure 22i – probably checksum routine.

Figure 22k – this time it will crash
By this point I was pretty stumped as to how I was going to find the checksum. Eventually I managed to figure out that the game would eventually crash even if I left the game sat waiting at the insert disk 2 screen. This narrowed down the amount of code I needed to sift through considerably since at this point the code is pretty much sat in a tight look waiting for a mouse click (see figure 22c). That meant that the checksum code had to be in the interrupt routine(s) somewhere.
Using this as a starting point we see that the only interrupts that seem to be active are at $1706 and $18ee. I tried disabling the interrupt handler at $1706 (just making it do an RTE immediately) and it didn’t have any effect on the crashing. There doesn’t seem to be anything alarming in that code. We now need to focus on the other routine at $18ee. Looking at the code in figure 22d and 22e there isn’t much in the main code. We need to now consider what’s at the subroutines at $1d80 and $1486. (See figure 22f and 22g). Nothing much here either, we need to delve a bit deeper so we look at the call to $1e82 (see figure 22g and 22h).
It’s all pretty much looking like boring stuff but there is a slight hint of something going off at $1ef8 through $1f20. It is keeping a count of something in D2 using SUB and then skipping the following code if $84(a5) is not 0. We can see above that it subtracts 1 from $84(a5) and then resets it back to $3f80 once it reaches 0. The SUB at $1f0e looks like it could be comparing the result against a known value. If we breakpoint at $1f0e and wait we should eventually hit this piece of code.
The length of time taken for the code to activate seems roughly along the lines of the time we are seeing for the game to crash so we could be onto a winner here. Looking at the way it adds D5 onto A5 (which is used as a base register for much of the code we have seen so far) it becomes quite clear that if the result in D5 was not zero it could easily cause mayhem by altering A5. When our breakpoint activates we are able to see that the result of the SUB does not end up with D5 being 0. In our case (see figure 22i) we get $DFE. This doesn’t seem to cause an immediate crash if we let it run but that is more luck than anything at this stage. The value in D5 could easily end up being an odd number which would cause an address error on 68000 due to a word memory access on an uneven boundary. If we reset at this point and load the original game to the insert disk 2 screen and then patch the address at $7fb6 with LEA $DFF000,a6 and set a breakpint at $1f0e again – this time after a bit of a wait we see this time we get $AAD in D5 which will definitely cause a crash (see figure 22k). We can repeat this test making different changes and we will see that the number varies but if the changes are kept constant the number will not change.
What we cannot do is check to see if the original unmodified code results in a value of 0 as the effect of simply adding a breakpoint at $1f0e changes the contents of the memory.
If we load up our cracked copy of the game and put a breakpoint at $1f0e and wait we get a value in D5 of $5DA6. If we replace the code at $1f0e with a CLR.W D5 and a couple of NOPs (see figure 23a) we can see what happens when we play the game.
Eventually the game will still crash or start acting strangely as before. It seems as though we might have more than 1 checksum to deal with (or we were mistaken about the code we found). Looking back at the slightly suspect code we found previously (figure 22b). There seems to be some bits in common with the routine we disable at $1f0e. The code at $16bc subtracts 1 from a value and skips over the code at $16c4 which looks suspiciously like the code at $1f0e. Especially when we look at the code at $16ce and then see that the contents of $84c2 contains $3f80 it all starts to look pretty familiar. $3f80 is the number of words it will process in the checksum and once it’s done them all it checks the result and starts all over again.
If we do a search for those bytes ($3f and $80) we find several memory addresses at which to investigate further. (See figure 23b). Starting to investigate these $48c doesn’t seem like another checksum (probably just setting up the first run of the checksum at the start). $1f1c and $84c2 we already know about. Looking at the other addresses returned we don’t see anything much of interest.
Now that we know how this checksum routine roughly works (if you haven’t understood the code around $1da0 sets up the current address to read from starting at $400). The code at $1e86 reads a word from the memory and the code at $1efc does the calculation at the end and resets the offset and number of words to process ready for another run. This routine is called once per frame so that it checksums one word of memory each frame until all the range of memory is complete. That is why it takes a while for the checksum to activate.
Based off of this we can surmise that there could also be checksums that work on a byte or longword basis. So we can search for $3f80 / 2 ($1fc0) for longword checksums and $3f80*2 ($7f00) for byte based checksums. Figure 23c shows the results of the search for longword checksums. The addresses that come back don’t seem to contain much in the way of valid code but one of the previous routines we saw reset the data using an absolute address so it’s worth doing an FAQ to see if we can find anything interesting (see figure. 23d). With some search around the values seen I managed to track down that I think are 5 checksum routines that need patching at the following addresses:
$1f0e (the first one we identified)
$16c4 (the original suspicious code we saw)
$4efe (this one references the address $9d3a we saw in figure 23d)
$3fc8 (this is a longword one that references $9a9a we saw in figure 23d)
$3b4c (a second longword one that references $9eb0 we saw in figure 23d).
I wasn’t able to detect any byte based checksum routines.
We can now disable all of these checksums with the following:
1f0e - clr.w d5,nop,nop
16c4 - clr.w d5, nop,nop
4efe - clr.w d0, nop,nop
3fc8 - clr.l d6, nop,nop
3b4c - clr.l d3, nop,nop
After doing this I was able to play the game for around 45 levels without noticing any negative side effects. I then tried to enter a level code and restart the game and it crashed. Seems like we have at least one more checksum to find. All of the routines we have found so far end with one of two possible codes:
SUB(.w/l) absolute_addr,Dn
SUB(.w/l) offset(a5),Dn
See figure 23e and 23f for a list of all the possible combinations of instructions of this type. We can get a list of these opcodes and search for them using the F command. (see figure 23g for the memory dump of these instructions). So we need to search for the following opcodes:
$9079, $9279, $9479, $9679, $9879, $9a79, $9c79, $9e79 (sub.w addr, Dn)
$906d, $926d, $946d, $966d, $986d, $9a6d, $9c6d, $9e6d (sub.w addr(a5),Dn)
$90b9, $92b9, $94b9, $96b9, $98b9, $9ab9, $9cb9, $9eb9 (sub.l addr,Dn)
$90ad, $92ad, $94ad, $96ad, $98ad, $9aad, $9cad, $9ead (sub.l addr(a5),Dn)
Using the F command to search for all of these opcodes between $400 and $80000 we find a couple of interesting pieces of code (see figure 23h and figure 23i). The routine at $2b64 shouldn’t affect us as it starts at $4348 and runs through the memory up to $6348 and we haven’t changed anything here so we can leave this be. The second routine looks more bothersome. It starts by moving a6 to a3 and subtracting $dfec00. This results in $400 in a3 so we can see this one starts at $400 and the length is the same as in previous routines (-1 to account for the use of DBF). We will need to replace the SUB at $30c4 with a CLR.L d0, NOP, NOP as per the previous routines.
If we update our patch code to neutralise all of these checksum checks we end up with the final patch code shown in figure 24a and 24b. Write this code to our cracked disk 1 and you should be able to play the game with no problems.
I’ve played this through around 40 odd levels without cheating and played every single level and through to the final congratulations screen by training the number of lemmings required to complete each level down to 0 and I’ve not seen any sign of further checksums.

Figure 23a – patched checksum

Figure 23b – search for $3f $80

Figure 23c – search for $1f c0 – searching for longword based checksums.

Figure 23d – FAQ search for longword checksums.

Figure 23e – list of instructions we need to search for (1)

Figure 23f – list of instructions we need to search for(2)

Figure 23g – memory dump of instructions to search for

Figure 23h – more possible checksums (1)

Figure 23i – more possible checksums (2)

Figure 24a – final patch code (1)

Figure 24b – final patch code(2)
All that remains is to insert an intro (plenty of room left on disk 1) and a trainer if you so feel.
CONGRATULATIONS!
We made it through… that was a long one… see you next time
Phantasm….