* 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:

Filetable: $a9c
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
patchtable:
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
replaceloader:
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:

LEA LOADER+$A9C-$400(PC),A0
MOVE.L #0,$2e*8(a0)
ADDQ.L #8,a0
MOVE.L #1*$1600,D0
MOVEQ #47-1,D3
PATCHTABLE:
MOVE.L D0,D1
DIVS.W #$1600,D1
MOVE.B D1,4(A0)
MOVE.B #0,5(A0)
SWAP D1
MOVE.W D1,6(A0)
ADD.L (A0),D0
ADD.L #8,A0
DBF D3,PATCHTABLE

LEA NEWLOADER(PC),A0
LEA LOADER+$11E(PC),A1
MOVE.L #(NEWLOADERENDE-NEWLOADER)-1,D0
REPLACELOADER:
MOVE.B (A0)+,(A1)+
DBF D0,REPLACELOADER
RTS

LOADER: INCBIN “LOADER” ; <--- Insert your loaderfile here! ;
; This is the new trackloader which will replace the old one!
;

NEWLOADER:
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!

; HARDWARE-DISKLOADER.
; ********************
; IN: A6=$DFF000
; A0=BUFFER.L
; D0=LENGTH.L
; D1=TRACKNR.L
; D2=BYTEOFFSET.L

TRACKLOADER:

LEA $BFD100,A4
LEA $BFE001,A5
LEA HEADSTATUS(PC),A2
MOVEQ #0,D7
ADD.L D2,D0
MOVE.B #$7D,(A4)
NOP
NOP
MOVE.B #$75,(A4)
BSR.W DISKREADY
STEPHEADZERO:
BTST #4,(A5)
BEQ.B HEADONZERO
BSET #1,(A4)
BCLR #0,(A4)
BSET #0,(A4)
BSR.W DELAY
BRA.B STEPHEADZERO
HEADONZERO:
DIVS.W #2,D1
SWAP D1
TST.W D1
BEQ.B CHOOSEHEADDOWN
BCLR #2,(A4)
MOVE.B #0,(A2)
BRA.B MOVEHEADS
CHOOSEHEADDOWN:
BSET #2,(A4)
MOVE.B #1,(A2)
MOVEHEADS:
SWAP D1
MOVELOOP:
TST.B D1
BEQ.B READTRACK
BCLR #1,(A4)
BCLR #0,(A4)
BSET #0,(A4)
BSR.W DELAY
DBF D1,MOVELOOP
READTRACK:
BSR.W DISKREADY
MOVE.W #$7F00,$9E(A6)
MOVE.W #$8500,$9E(A6)
MOVE.W #$4489,$7E(A6)
MOVE.W #$4000,$24(A6)
MOVE.L #$C1C,$20(A6)
MOVE.W #$9900,$24(A6)
MOVE.W #$9900,$24(A6)
MOVE.W #$2,$9C(A6)
TRACKREADY:
BTST #1,$DFF01F
BEQ.B TRACKREADY
MOVE.W #$4000,$24(A6)
MOVEQ #0,D5 ; DECODE TRACK
DECODE:
LEA $C1C,A1
MOVE.L #$55555555,D4
FINDSYNC:
CMP.W #$4489,(A1)+
BNE.B FINDSYNC
CMP.W #$4489,(A1)
BEQ.B FINDSYNC
MOVE.L (A1),D3
MOVE.L 4(A1),D1
AND.L D4,D3
AND.L D4,D1
ASL.L #1,D3
OR.L D1,D3
ROR.L #8,D3
CMP.B D5,D3
BEQ.B SECTORFOUND
ADD.L #1086,A1
BRA.B FINDSYNC
SECTORFOUND:
ADD.L #56,A1
MOVE.L #(512/4)-1,D6
DECODESECTOR:
MOVE.L 512(A1),D1
MOVE.L (A1)+,D3
AND.L D4,D3
AND.L D4,D1
ASL.L #1,D3
OR.L D1,D3
CMP.L D7,D2
BGT.B BELOWOFFSET1
SWAP D3
MOVE.W D3,(A0)+
SWAP D3
BELOWOFFSET1:
ADDQ.L #2,D7
CMP.L D7,D2
BGT.B BELOWOFFSET2
MOVE.W D3,(A0)+
BELOWOFFSET2:
ADDQ.L #2,D7
CMP.L D7,D0
BLE.W READREADY
DBF D6,DECODESECTOR
ADDQ.B #1,D5
CMP.B #11,D5
BNE.B DECODE

TRACKDONE:
CMP.B #1,(A2)
BNE.B MOVECYLINDER
BCLR #2,(A4)
MOVE.B #0,(A2)
BRA.W READTRACK
MOVECYLINDER:
BSET #2,(A4)
MOVE.B #1,(A2)
BCLR #1,(A4)
BCLR #0,(A4)
BSET #0,(A4)
BSR.W DELAY
BRA.W READTRACK
READREADY:
MOVE.B #$FD,(A4)
NOP
NOP
MOVE.B #$E7,(A4)
RTS
DELAY:
MOVE.B $E00(A5),D4
ANDI.B #$C0,D4
ORI.B #$8,D4
MOVE.B D4,$E00(A5)
MOVE.B #$28,$400(A5)
MOVE.B #$21,$500(A5)
WAIT:
BTST #0,$D00(A5)
BEQ.B WAIT
BSET #0,$E00(A5)
RTS
DISKREADY:
BTST #5,(A5)
BNE.B DISKREADY
RTS
HEADSTATUS: DC.B 0
NEWLOADERENDE:



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 ***

0

Publication author

offline 1 month

aLpHa oNe

0
Comments: 261Publics: 7Registration: 07-08-2007

Subscribe
Notify of
guest

0 Comments
Newest
Oldest
Inline Feedbacks
View all comments
scenex
20 years ago

great tutorial! explains many interesting concepts!

please keep on writing mfm tuts 🙂

0
Authorization
*
*

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Registration
*
*
*

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Password generation

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

0
Would love your thoughts, please comment.x
()
x