Submitted by: xyzzy
Date: 2012-08-29 03:14
Software Creations (Capcom) 1988
2) An Amiga or WinUAE (I’m using WinUAE)
3) X-Copy or your favorite copier.
4) Action Replay Cartridge or your favorite monitor.
7) Pencil and paper
8) Blanks, if you use a real Amiga.
Given that this game was cracked over twenty years ago and the cracks/SPS originals are readily available on the Internet, we are not doing a disservice to anyone by analyzing the code. This is for educational purposes only and at your own risk and volition.
Make a copy of the disk using X-Copy to see what we are facing.
This looks interesting. From the error codes below, it looks like checksum errors.
The list of X-Copy Errors from the X-Copy Shrine WebSite (http://jope.fi/xcopy/index.html).
Booting the disk results in the game loading to the title screen and then freezing.
Directory the disk and see that it’s Amiga DOS. Type the startup-sequence.
Load the file boot (it’s in the C directory) and disassemble. One thing to note is that the addresses are offsets from the end of Amiga DOS header(e.g. LEA 74,A1 is actually loading the start address + 20, which is 30020+74 when the file is loaded to 30000, into A1).
Check the ASCII
LEA $74,A1 – points to the library name dos.library
CLR.L D0 – sets the version to 0 (any version) MOVE $4,A6 – sets the pointer to Execbase
JSR -228(A6) – Opens the library and returns the library base in D0
MOVEA.L D0,56 – Saves library base pointer
MOVE.L #4E,D1 – points to file bionic
MOVE.L 56,A6 – set the pointer to dos.library
JSR -96(A6) – LoadSeg – scatter loads a program into memory
MOVE.L DO,D3 – Saves the segment list pointer
MOVE.L #4E,D1 – set pointer to file bionic (call is CreateProc(name,pri,seglist,stacksize) (D1,D2,D3,D4) CLR.L D2 – sets the priority
MOVE.L #7D0,D4 – set stack size
JSR -8A(A6) – CreateProc call
It closes the dos.library (JSR -19E(A6)). Let’s disassemble bionic and see what’s happening.
This is all standard open library, allocate memory etc. The reference manuals (Amiga Machine Language book has library listings in the appendix) show the details of the library calls. After it allocates memory (JSR –C6(a6)), it then stores the memory location returned in D0 for the allocated memory in an address e.g. B48 (move.l d0, B48). Search for uses of that address as the program may load a file there. You can also search for the Amiga DOS open and read commands (e.g. F 4E AE FF D6 which corresponds to JSR -
The last address is the most interesting as the code at 3D07A reads the file.
The read call (JSR -2A(A6) has file(D1), buffer(D2) and length(D3) as parameters) is shown below
We could play the game and rip the files each time they load or write a program to read each file and write it out again. The routines are as follows (the open file (JSR -$1E(A6) is at 3D05C)) and this is called by a number of routines which pass the file name to be opened.
Load charset.dat at 3CEB0
Load level?.pi1 at 3CED8 where ? is the level number. This is derived from the base in A0 and the offset for the level in d0. This points to the address of the file name
The first address at 3d142(30020+D122) gives D136 which is 3d156 (30020+d136) and points to level1.pi1.
Load map?.dat at 3CF04 where ? is the level number. Uses the same method as level to point to the correct level.
Load mansp.spr at 3CF28
Load bigsol.spr at 3CF48
Load Level345.spr at 3CF6E
Load level1.spr at 3CF94 (This also loads level2.spr, level3.spr and heli.spr [note there are two pointers to heli.spr] using the offset method)
Load panel.pi1 at 3CFD2
Load spic.pi1 at 3CFF2
Load Level2.spr at 3D030
So we could use the games own code to read the files and add our code to write them straight out again to another disk. However, given it’s Amiga DOS, there’s a good chance that a file copy program or rudimentary disk copier will work. You can copy the files with the Amiga DOS copy command (copy df0: df1: all). Both the built in dcopy in the Action Replay and the standard utility diskcopy will copy the disk and correct the errors. Also, you can copy the files using Directory Opus.
The left panel is the IPF and the right panel is the ADF. The copy is identical.
Boot the copy
The game boots and plays. Let’s train the game and see if there’s any more protection. Also, search for any loader related addresses (DFF024, DFF07E, BFE001, BFD100). There doesn’t look like there are any custom loaders.
Load bionic into memory at 30000 and disassemble.
After scanning the code down to 30268, you’ll see a lot of moves. From the game, the life counter starts
at 6 and the time counter starts at 200. So 30268 could be the initialization of the life counter and
302b6 could be the initialization of the timer. Search for the two address initialized
Disassemble just in front of the address. The JSR CCAE call a routine which does a subtract (SBCD – subtract binary coded decimal). The subtract can be replaced with a NOP. For a trainer, the offset from the DOS header is CCBA.
Search for the timer address
The code at 3CC9C decrements the timer. NOP out the two SBCD.B at 3CCAE and 3CCB0. For a trainer, the offset for the CC8E.
The test for the time at 3CC9A is the countdown of the time during play. The test at 3CD54 is the routine to add the remaining time to the player’s score. This can be discerned by the following. If the two SBCDs are replaced at 3CCAE the in game timer stops decrementing. Disassemble back from the address 3cd54 by disassembling until the d command passes off the top of the screen and then hit the up cursor. Keep scrolling up and the start of the routine appears to be 3CCDE (NOTE: remember to subtract 30020 from the address when searching using faq)
Disassemble from 3c996
By comparing the two sets of code at cdee and ce0e it looks like a level setting. When you finish the first level it runs the code at 3CE0E and sets the addresses 8d36 and ba0 to 1. From our previous work with the files it is most likely that the address at BA0 is the level point for loading the file. The starting code
for level 1 is at 3CDF0 (offset CDD0) and searching for CDD0 reveals the two calls.
The first one is the setup when starting up the game. The second one is when continuing the game. By changing the branches to this address to point to another level start address e.g.
CDEE the game will start at that level. This is done by adding a move.w #$CE,(A0) after setting A0 to point to the base load address plus $1F2 (30212 is where the word $CDD0 is held and the difference between $30212 and $30020 is $1F2). The level offsets are at 3C99C and are as follows
Level 2 – $CDEE, Level 3 – $CE0E, Level 4 – $CE2E and Level 5 – $CE4E.
There doesn’t appear to be any code working with the invalid checksums when I played through to the end.
Let’s add a trainer menu. Get the trainer source from the Chuck Rock 2 trainer tutorial. Set the number of lines to three as the start level, lives and time will be trained. Set the lines, levelsel and levels as follows.
LINES=3 ; COUNT OF ON/OFF LINES
; (WITHOUT !START GAME! OPTION)
LEVELSEL=3 ; WHICH ON/OFF LINE REMAINS
; TO THE LEVELSELECTOR
; 0 = NO LEVELSELECTOR LEVELS=5 ; MAXIMUM NUMBER OF LEVELS
In the section ENDE, add the line
MOVE.L A1,$80 – this moves the patch address into the TRAP #0 vector. Set the PATCHADR to an unused piece of memory such as 200.
/Save the old trap #0 register
/load our patch into the trap #0 register
Add the following code in the patch section. The MOVE.L A(A7),A0 puts the return address in the game code in A0 which allow the position of the life counter and time counter to be calculated.
MOVEM.L A0/A1,-(A7) /save A0 and A1 on the stack before we start
MOVE.L A(A7),A0 /Save the return address
SUB.L #2,A0 /subtract 2 from the address to get the start of the code
MOVE.L A0,A(A7) /write the return address back to the stack
MOVE.W #$23CF,(A0)+ /overwrite our TRAP #0
LIVES: CMPI.B #$01,$c0.W /Check whether the user selected infinite lives BNE TIME /if not then branch to the check for infinite time MOVE.L A(A7),A0 /move the base of the bionic code to A0
ADD.L #$CCAC,A0 /add the offset for the subtract from the life counter
MOVE.W #$4E71,(A0) /NOP out the subtract
TIME: CMPI.B #$01,$c1.W / Check whether the user selected infinite lives
BNE LEVEL1 / if not then branch to the return to the level set code
ADD.L #$CC80,A0 / add the offset for the subtract from the time counter
MOVE.L #$4E714E71,(A0) /NOP out the subtracts
LEVEL1: CMPI.B #$01,$c2.W /Check the level selected by the user
BEQ RETURN LEVEL2: CMPI.B #$02,$c2.W
ADD.L #$CDEE,A0 /load the JSR address for the level
ADD.L #$1f0,A1 /load the location of the address part of the JSR inst. MOVE.L A0,(A1) /patch the JSR to point to the selected level
BRA RETURN LEVEL3: CMPI.B #$03,$c2.W
MOVE.L A0,(A1) BRA RETURN
LEVEL4: CMPI.B #$04,$c2.W BNE LEVEL5
BRA RETURN LEVEL5: CMPI.B #$05,$c2.W
BNE RETURN MOVE.L $A(A7),A0
RETURN: MOVE.L A(A7),A0
ADD.L #$24A,A0 / add the offset for the instruction that sets life counter
MOVE.W #$0009,(A0) /change 0006 to 0009 so we have 9 lives
ADD.L #$298,A0 / add offset for the instruction that sets time counter MOVE.W #$0999,(A0) /change 0200 to 0999 so we have 999 in the time MOVE.L OLD80,$80 /restore the TRAP #0 vector
MOVEM.L (A7)+,A0/A1 /restore A0 and A1
RTE /return to the exception we caused with trap #0
OLD80 dc.l 0 /location for the old TRAP #0 vector
In the life section, the MOVE.W #$4E71,(A0) just NOPs out the subtract. In the time section, the similar command removes the two subtracts. Compile with ASM-One (v1.20 as v1.48 exe does not work in WinUAE) and crunch with StoneCracker using the default settings.
Load the file bionic into memory at 30000. Replace the MOVE.L A7,B5C with TRAP #0. You could also use JSR $200 and then RTS back in the patch.
When the game runs the first line of the code will restored removing our TRAP instruction. The return address is changed so that the game runs the MOVE.L A7,B5C code.
Filesize: 0KB, downloaded 33 times