Author: aLpHa oNe
Submitted by: aLpHa oNe
Date: 2004-10-09 13:04
Submitted by: aLpHa oNe
Date: 2004-10-09 13:04
* Hard 'n' Heavy (c) reLINE ?89 *Requirements
1. AMiGA or WINUAE
2. ACTION REPLAY freezer (or ROM Image)
3. Original Game or CAPS-Image
4. Assembler (ASM-One / Trash-M One / Seka or similar)
5. Knowledge in using one of the above mentioned Assembler?s!
Okay, let?s try to make a copy of the Original Disk to see how hard this game is protected!
As we can see, the copy of our game won?t surely do anything when it?s booted up!
So let?s begin our crack (as always) with reading in the first *only dos-readable* track into memory using our beloved action replay cartridge!
Enter it and type in the following: rt 0 1 $50000, which will read the first track into memory location $50000.
Let?s view that area as an Ascii dump using n $50000 and scroll down some lines...
Well, we can see the Bootblock in the range from $50000-$50400, the rest of track 0 is filled with "DOS"+$0 strings, so there must be a 'special' trackloader located in the bootcode... Let?s disassemble it:
Enter: d $5000c and hit enter a few times...
Without going deeper into the trackloading part of the bootcode we can see some interesting instructions beginning at adress $5003a ...
After 'moving' #0 into d0 and 'loading' $400 into a0 the code continues programcode at $20052 and then jumps to adress $400 !
From adress $50024 on the remaining bytes of the bootcode are copied to $2002e so there can?t be any gamecode stored at $400 that moment. So as you may have guessed... the routine that is being branched to before jumping to $400 must be trackloading something to that adress, hopefully the main loader! To catch these bytes we will let the game loop instead of jumping to $400 so that we can rip off that part. So let?s change that jump instruction in the bootcode like this:
Like in the picture above we will overwrite the instruction at $5004c (jmp $400) into "bra $5004c" which will make the processor loop. After overwriting that instruction we have to calculate the new bootblock-checksum coz otherwise it wouldn?t be executed. The action replay will do that for us if you type in:
bootchk $50000 (the adress where the bootblock is located!)
After writing that track back onto our original disk using wt 0 1 $50000 we will reset our machine and boot up the gamedisk to see what?s happening...
You?ll see a black screen and the disk-drive seems to do nothing.... this is our loop being active so let?s see what has happened to adress $400, the point the processor would have been jumped to with the normal bootblock being executed!
Enter your AR again and type in the following:
n $400 !
Yeah... we can see that some datas have been loaded so let?s switch from ascii to hex view to find out the length of the loaded datas...
Type in: m $400 and scroll down until you get to the point where only '0' bytes appear...
Some "enter's" later we have reached that area (picture).
Let?s insert a fresh new formatted save disk into drive df0: and write the loader to disk...
Type in: sm loader, $400 $d60
Normally trackloading-routines are used which take parameters like "where to load to" and "filename or filenumber" ... If you?ve read my tutorial of *Turrican 2* you?ll probably know what I mean. Instead of filenumbers or filenames there are also loaders out there that are called with parameters like "starttrack" and "number of tracks 2 load" or something (e.g. R-Type) so let?s disassemble the loadercode at $400 to find out what kind of trackloader this game uses...
Enter: d $400 and scroll down some (more) lines...
Now look at the code at $454 and $4b4... this looks interesting!
A value which looks like an adress is moved into a4 ($70000) and 'd0' is set to $2d, then the program jumps to $51e.
We can see something similar starting at adress $4b4... 'd0' is set to $2e and 'a4' to $50000, again followed by a jump to $51e!
If you scroll down some more lines (picture not included) the same action is done with 'd0' being set to $2f, 'a4' to $5000 and after $51e is called the program jumps to $5000.
This looks hardly like a trackloading routine which is working with a filenumber stored in 'd0' and an 'where to load' adress in 'a4'. Let?s have a closer look at that routine at $51e to find out what is going on...
We will have to find out what this routine is doing with the value given in 'd0' because the loader will need some more informations like 'where is the file located on disk' and 'what size does the file have' ... Like always, there must be a lookuptable stored somewhere in the loader which contains all these datas so let?s try 2 find it...
If you begin reading the instructions from adress $552 you can see that the value in 'd0' is being multiplied by 8 with shifting it 3 bits to the left, so we can be sure that the lookuptable has got a size of 8 bytes each fileentry. After that the adress $a9c is loaded into 'a1' which must be the beginning of our lookuptable coz the next instruction is moving a longword value from that adress + offset stored in 'd0' to register 'd5'. Some instructions later there is another value which is moved from that table into d1 (move.b 4(a1,d0.l),d1) and another one is being tested (tst.b 5(a1,d0.l) !
Let?s have a view onto the lookuptable stored at $a9c, type in:
m $a9c and hit enter a few times...
I won?t explain how to find out which value is used for what kind of action in this loader... if you want to you can check the loaderroutine with passing some values in 'd0' + an adress in 'a4' and then jumping to $51e to see what is happening. You will notice that this routine does really load the given file to the adress you have stored in 'a4'... 'a3' will contain the end adress of data if the loader has finished, so this will make the forthcoming fileripping very easy for us!
As I?ve seen many of these filetables I can tell you what the values are good for:
Offset 0: length of file.l
Offset 4: cylinder where the file is located on.b
Offset 5: track 0 or 1 on the current cylinder (lower side, upper side)
Offset 6: byteoffset on the current track.w (where the file begins)
If you want to, you can set a breakpoint to adress $51e now (bs $51e) and boot up the gamedisk with jumping to $400 to get an overview of the filenumbers that the game loads in before you can start playing the first level... as you may have guessed, I already did that and I can tell you the following:
The files $21 - $2f are needed to boot up the game (titlescreen, titlemusic) and the files $01 - $20 are used for the 32 game levels!!!
So we don?t need to search for another loaderfile.... the whole game uses the trackloader at $51e! Great, isn?t it?! :-)
Let?s continue our little crack with ripping off all files now.... Due to the fact that we are cool, we will code some lines which will do the fileripping from $1 - $2f ('0' is not needed as I found out) automatically...
Important: I will load up the files from memory pos $80000 so make sure (if using WinUAE) that you are using 2 MB Chip-Ram (if using fast ram, replace the adress $80000 to the location where your fast ram is located, we need about 600 kb for this game!) ... If you do not have so (much) workspace you?ll have to rip the files in 2 runs (e.g. from $1-$24 -the leveldatas are extremely short- and from $25-$2f).
Let?s put our code at $10000, type in: a $10000
I guess you know what these lines are doin?, but i?ll explain anyway:
^10000 move.l #1,d0 ; Start with File #01
^10006 move.l #$80000,a3 ; Start loading to $80000
^1000c move.l d0,-(a7) ; Push Filenr onto stack, loader kills 'd0'
^1000e move.l a3,a4 ; Puts loadadress into reg a4 where the loader needs it
^10010 jsr $51e ; Guess what, call the trackloader...
^10016 move.l (a7)+,d0 ; Restore our filenumber in 'd0'
^10018 addq.l #1,d0 ; Add #1 to our filenumber...
^1001a cmp.l #$30,d0 ; Next file to read no. $30 ?
^10020 bne $1000c ; No, continue reading...
(Remember that 'a3' points to the adress where loading has finished, so this is also our new loadadress for the next file!
^10022 move.w #$f00,$dff180 ; Set Backgroundcolor to red and loop
^1002a bne $1000c ; so that we know trackloading has come to an end
Okay, now let?s start the action with jumping to $10000 using g $10000 !!!
You will hear some strange noises coming from the disk-drive which tells us that trackloading has just begun... ;)
After about 60 seconds the loading stops and the screen turns red... this means that our little code hast finished ripping so let?s enter our AR ...
Enter r now so we can take a look at register 'a3' which will tell us up to where the files have been loaded.
You?ll see exactly this value in 'a3' (if started at $80000): $11101a
So now we have a nice memoryblock beginning from $80000 up to $11101a (!593946 Bytes) filled with all files from $1 - $2f ... Let?s save this big file to our savedisk, naming it for example: "files1-2f" !
Type in: sm files1-2f, $80000 $11101a
Well, we?ve just reached the point where the original disk is no longer needed, congratulations! ;-)
Before we start to create our diskimage we?ll have to take a look onto our loader at $400 again coz there was something that I found strange...
Let?s disassemble it again using d $400 and pressing enter a few times...
The interesting thing starts at adress $4b4:
What made me wonder is that the file $2e is called twice, both times the file seems to be loaded up to $50000... but after the first load the adress $a92 is cleared and after the second loadercall that value is being compared with $1f000... Well if we try to load the file $2e by passing the number into 'd0' and e.g. $50000 into 'a4' the game seems to load up some data but if the loader returns the value in 'a3', which normaly points to the end of the loaded datablock, will keep the same value as the startadress passed into 'a4' ... after hex viewing the adress $50000 I noticed that the trackloader really loaded nothing!
If looking into the filetable at offset $2e*8 we can see that the file does not have, as I expected first, a size of 0 bytes but of $4e20 ! Hmmm... I don?t know what kind of strange copyprotection that is but let?s remember that our saved fileblock now does consist of a file $2e with a size of 0 bytes so the trackposition of file $2f is the same as $2e!!! We?ll have to remember that fact when writing the diskimage! Due to the fact that we will overwrite the trackloader with our own one we also should remember to pass the compared value $1f000 to adress $a92 when file $2e shall be loaded!!! This is really important coz the game will crash if the value isn?t set! (Believe me, I?ve tested it!)
Okay dudes, reset your machine now and load up your favourite assembler, in my case 'AsmOne'. On our crackdisk we won?t store the files into the same positions as on the original disk (this isn?t possible in most cases coz the games are putting more bytes onto a track) ... the new trackloader being inserted will need the following parameters to work:
A0.l = Loadadress
D0.l = Length of file
D1.l = Starttrack (Track, not Cylinder)
D2.l = Byteoffset (from Trackpos 0 to the beginning of the file)
So we will code some lines now that will replace the cylindernumbers and offsets in the old filetable with our new positions... Due to the fact that we will fill up the first !5632 bytes (which means track 0) with the bootblock (!1024 bytes) followed by the loaderfile (!2400 bytes) we will start writing our ripped datablock containing files $1 - $2f from track position 1 on...
We ripped the files from $1 - $2f in the correct order so calculation of the new trackpositions will go like this...
We are setting up a bytecounter which represents the byteposition of the current file on disk... we?ll put our datafiles on disk starting at track 1 so the byteposition on disk for the first file $01 is $1600 (remember the size of a dos-track: $1600 -!5632- bytes). So the bytecounter is set to $1600 at first... now we?ll write the position of file $01 into the table ... we need to know the track where it?s located on so we divide the bytecounter with $1600 which, of course, is 1 at the beginning. We can write this into the table -> File '$01' starts at track 1 !!! The rest of our divison, 0 in this case, is the byteoffset on the track so we?ll move this into the filetable as well !!!
The trackposition for the second file is calculated the same way...
We?ll take our bytecounter again which still carries the value $1600 and add the length of the first file onto it to get the byteoffset for filenumber $02. This means $1600 + $4ea (we know the length coz it?s stored in the filetable) which gives us the result of $1aea as diskposition for file $02! Now to find out on which track file $02 begins, we will divide this value by $1600 again. $1aea / $1600 = $1 -> Rest $4ea !! So file $02 begins on track 01, trackoffset $4ea !
The next table describes this procedure for the first seven files:
File: BSize: Trck: Trckofs: Diskofs:
$01 - $4ea - $01 - $0000 -> $00001600
$02 - $59c - $01 - $04ea -> $00001aea
$03 - $63c - $01 - $0a86 -> $00002570
$04 - $7a4 - $01 - $10c2 -> $00003632
$05 - $be6 - $02 - $0266 -> $00003898
$06 - $b7e - $02 - $0e4c -> $000046e4
$07 - $5ec - $03 - $03ca -> $00004aae
... and so on!
Okay, coming up next is the sourcecode which replaces the trackloader and patches the filetable in the old loader...
If you?ve started your 'AsmOne' let?s reserve about 800 kb chipram for the patchcode and the diskimage with entering C, followed by enter and 800 for the amount of kilobytes ram we want to use. I?ll post the complete sourcecode at the end of this tutorial so you can 'copy' and 'paste' it ...
I?m going to explain the patching part now... the main trackloading engine is, as always, not explained... there are tutorials out there that do so!
lea loader+$a9c-$400(pc),a0 ; the adress of the filetable is stored in a0
move.l #0,$2e*8(a0) ; Set the size of file $2e to 0 !!!
addq.l #8, a0 ; we begin with filenumber $01 in the table
move.l #1*$1600,d0 ; our bytecounter for the diskposition
moveq #47-1,d3 ; number of files in the filetable
move.l d0,d1 ; stores actual byteoffset in d1
divs.w #$1600,d1 ; find out tracknumber
move.b d1,4(a0) ; stores tracknumber in the filetable offset +4
move.b #0,5(a0) ; not really neccessary, we don?t need this information
swap d1 ; gets the rest of our division
move.w d1,6(a0) ; puts byteoffset on the track into the filetable
add.l (a0),d0 ; adds size of current file to our diskpositioncounter
addq.l #8,a0 ; points a0 to the next entry in the filetable
dbf d3, patchtable ; loop 48 times (dbf counts to -1)
The first lea looks a bit strange but as we know the filetable was stored at $a9c in memory and due to the fact that the loader was located at $400 we get the correct offset to the filetable with $a9c - $400 !!! We add the value 8 to the adress because we start with file $01 in the table...
The upcoming 6 instructions will overwrite the old trackloader with our new one:
lea newloader(pc),a0 ; adress of our new loader is stored in a0
lea loader+$11e(pc),a1 ; adress of the old trackloader ($51e-$400)
move.l #(newloaderende-newloader)-1,d0 ; well, bytesize of our new loader
move.b (a0)+,(a1)+ ; replace the old loader byte for byte...
dbf d0,replaceloader ; ... guess what?! :-)
rts ; patching is done !!!
Now here comes the complete sourcecode for your pleasure:
LOADER: INCBIN "LOADER" ; <--- Insert your loaderfile here!
; This is the new trackloader which will replace the old one!
MOVE.L A4,A0 ; These four instructions were taken from the original
MOVE.W #$8210,$DFF096 ; loader, switching on disk-dma and saving filenumber
MOVE.L D0,$A62 ; and loadadress at $a64 and $a66 !
MOVE.L A0,$A66 ;
CMP.B #$2E,D0 ; Our special '0-byte' file ?!
BNE.B LOADIT ; No, 4get it ...
MOVE.L #$1F000,$A92 ; ... otherwise set $a92 to $1f000
BRA.B RAUS ; and return to main without calling the loader!
LOADIT: CMP.B #$26,D0 ; Highscorefile?
BNE.B NORMAL ; No, continue...
BRA.B RAUS ; Otherwise, don?t load it!!
NORMAL: ANDI.L #$FF,D0 ; not neccessary
LSL.L #3,D0 ; filenumber * 8
LEA $A9C,A1 ; adress of filetable -> 'a1'
MOVEQ #0,D1 ; clear 'd1'
MOVEQ #0,D2 ; clear 'd2'
MOVE.B 4(A1,D0.L),D1 ; puts trackpos into 'd1'
MOVE.W 6(A1,D0.L),D2 ; puts byteoffset on track into 'd2'
MOVE.L (A1,D0.L),D0 ; puts filesize in 'd0'
LEA $DFF000,A6 ; customchipbase into a6, our loader needs that!
BSR.W TRACKLOADER ; all parameters set, call the trackloader
RAUS: MOVE.W #$0010,$DFF096 ; turning off disk-dma...
MOVE.L $A66,A0 ; not really neccessary, put loadadress back into 'a0'
RTS ; finished!
; IN: A6=$DFF000
MOVEQ #0,D5 ; DECODE TRACK
HEADSTATUS: DC.B 0
Phew... now that you hopefully inserted this code into your assembler (remember to press [ESC] to swith into edit mode) we are ready to execute the code... Leave your editor with [ESC] again, type in a followed by [enter] so that our code is assembled into memory... After getting NO ERRORS we can jump into the program using j followed by [enter] again! Now that the loader is patched we can start saving it naming it for example 'LOADERPATCHED' ... do it like shown in the picture below!
Now that the loader is patched we can finally write the diskimage ... Save your sourcecode onto disk now and get ready for the last stage!
Our diskimage will also consist of a new bootblock which loads the main loader up to $400 and then executes it... we can use the trackdisk-device for that coz our tracks will be saved in the normal $1600 byte trackformat so no special trackloading routine will be neccessary!
I won?t explain how a bootblock has to look like so that amiga-dos is booting it or what the bootcode is exactly doing coz I guess you will understand these few instructions... this tutorial is really not written for *total beginners* who never coded a single byte on amiga in their life! :-) Just one word to the instructions before the diskimage begins at label DISK:
The first instruction loads up the adress of the mainfile $2f into 'a0' (I know the offset coz it?s stated in the new filetable -> track $62, ofs $a52), then the following move.l is "nop'ing" out another compare instruction which will make the game crash if not booted from the original disk!! The next instruction is moving an 'rts' into another part of $2f which is jumping into a routine that saves the highscore... this will also make the game crash if the original trackformat isn?t found!!! So if you want 2 experience this for yourself you can write the diskimage without executing the code ... it?s not very hard to find out which instructions will cause the game to crash if you are familiar with using the ar cartridge! :-)
I won?t post the sourcecode of the diskimage coz I don?t want to bore you with only 'copy'ing and 'paste'ing everything... all neccessary instructions are shown in the picture above!
So... if you are ready with typing in the source we can start to write the diskimage. Leave the Editormode, save your imagesource to disk and behave like shown in the picture below...
First we assemble the source, then we execute it so that the two patches are made in the main file $2f and finally we write the tracks back onto that fresh formatted disk that you hopefully inserted into df0: before calling asmone?s wt (WriteTrack) command! :-)
If you want to know why I am writing 107 tracks then just calculate the bytesize between the labels DISKENDE and DISK which is nearly 600 kbytes and divide that value with $1600 to find out how many tracks are neccessary to write!
At last we have to calculate the new bootchecksum on disk using cc...
Okay dudes, reset your machine and let?s have a play ...
L8r in the next tutorial... *** Alpha One ?2004 ***