▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ░▒▓█ rapid1337.com █▓▒░ ╓──────────────────────────────────────────╖ ║ Bionic Commando ║ ╟──────────────────────────────────────────╢ ║ 1) ║ ║ BionicCommando(1988)(Capcom)(US)[1074] ║ ╟──────────────────────────────────────────╢ ║ ·· security/bionic-commando.html ·· ║ ╙──────────────────────────────────────────╜ ============================================================================ Title ..: Bionic Commando Author .: staff Date ...: 2012-08-29 Section : Security ============================================================================ Bionic Commando Bionic Commando Software Creations (Capcom) 1988 Required items 1)   Bionic_Commando_(1988)(Capcom)(US)[1074] 2)   An Amiga or WinUAE (I’m using WinUAE) 3)   X-Copy or your favorite copier. 4)   Action Replay Cartridge or your favorite monitor. 5)   ASM-One 6)   StoneCracker 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. ![image001.jpg](../assets/img/tutorials/bionic-commando/image001.jpg) 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). ![image002.jpg](../assets/img/tutorials/bionic-commando/image002.jpg) 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. ![image003.jpg](../assets/img/tutorials/bionic-commando/image003.jpg) 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). ![image004.jpg](../assets/img/tutorials/bionic-commando/image004.jpg) Check the ASCII ![image005.jpg](../assets/img/tutorials/bionic-commando/image005.jpg) ``` LEA $74,A1 – points to the library name dos.library ``` ![image006.jpg](../assets/img/tutorials/bionic-commando/image006.jpg) ``` 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 ``` ![image007.jpg](../assets/img/tutorials/bionic-commando/image007.jpg) ``` 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. ![image008.jpg](../assets/img/tutorials/bionic-commando/image008.jpg) 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 – 2A(A6)). ![image009.jpg](../assets/img/tutorials/bionic-commando/image009.jpg) The last address is the most interesting as the code at 3D07A reads the file. ![image010.jpg](../assets/img/tutorials/bionic-commando/image010.jpg) The read call (JSR –2A(A6) has file(D1), buffer(D2) and length(D3) as parameters) is shown below ![image011.jpg](../assets/img/tutorials/bionic-commando/image011.jpg) 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 ![image012.jpg](../assets/img/tutorials/bionic-commando/image012.jpg) 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. ![image013.jpg](../assets/img/tutorials/bionic-commando/image013.jpg) The left panel is the IPF and the right panel is the ADF. The copy is identical. ![image014.jpg](../assets/img/tutorials/bionic-commando/image014.jpg) Boot the copy ![image015.jpg](../assets/img/tutorials/bionic-commando/image015.jpg) 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. ![image016.jpg](../assets/img/tutorials/bionic-commando/image016.jpg) 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 ![image017.jpg](../assets/img/tutorials/bionic-commando/image017.jpg) 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. ![image018.jpg](../assets/img/tutorials/bionic-commando/image018.jpg) Search for the timer address ![image019.jpg](../assets/img/tutorials/bionic-commando/image019.jpg) 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. ![image020.jpg](../assets/img/tutorials/bionic-commando/image020.jpg) 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) ![image021.jpg](../assets/img/tutorials/bionic-commando/image021.jpg) Disassemble from 3c996 ![image022.jpg](../assets/img/tutorials/bionic-commando/image022.jpg) ![image023.jpg](../assets/img/tutorials/bionic-commando/image023.jpg) ![image024.jpg](../assets/img/tutorials/bionic-commando/image024.jpg) 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. ![image025.jpg](../assets/img/tutorials/bionic-commando/image025.jpg) 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. ![image026.jpg](../assets/img/tutorials/bionic-commando/image026.jpg) 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. ENDE: LEA PATCH(PC),A0 MOVE.L #PATCHADR,A1 MOVE.L $80,OLD80 /Save the old trap #0 register MOVE.L A1,$80 /load our patch into the trap #0 register MOVE.L PATCHENDE–PATCH,D0 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. PATCH: ``` 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 ``` MOVE.L A(A7),A0 ``` 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 BNE LEVEL3 ``` MOVE.L $A(A7),A0 ``` ADD.L #$CDEE,A0 /load the JSR address for the level ``` MOVE.L $A(A7),A1 ``` 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 BNE LEVEL4 ``` MOVE.L $A(A7),A0 ``` ADD.L #$CE0E,A0 ``` MOVE.L $A(A7),A1 ``` ADD.L #$1F0,A1 ``` MOVE.L A0,(A1) ``` BRA RETURN LEVEL4: CMPI.B #$04,$c2.W BNE LEVEL5 ``` MOVE.L $A(A7),A0 ``` ADD.L #$CE2E,A0 ``` MOVE.L $A(A7),A1 ``` ADD.L #$1F0,A1 ``` MOVE.L A0,(A1) ``` BRA RETURN LEVEL5: CMPI.B #$05,$c2.W BNE RETURN ``` MOVE.L $A(A7),A0 ``` ADD.L #$CE4E,A0 ``` MOVE.L $A(A7),A1 ``` ADD.L #$1F0,A1 ``` MOVE.L A0,(A1) ``` GAME: 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 ``` MOVE.L A(A7),A0 ``` 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 PATCHENDE: 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. ![image027.jpg](../assets/img/tutorials/bionic-commando/image027.jpg) 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. ![image028.jpg](../assets/img/tutorials/bionic-commando/image028.jpg) ---------------------------------------------------------------------------- -- rapid1337.com -- The dumping ground for all things tech // knowledge is the weapon --