AGA-Fixing | Artwork | Cracking | Demos | Emulation | FAQ | Feedback | Games | HD-Installing
History | Home | Icons | Join Us | Links | Memberlist | MFM-Installing | News | Patches | Rob Northen | Utilities
How to HD install All Terrain Racing XMas Coverdisk using WHDLoad
The All Terrain Racing Xmas Coverdisk is a nice easy game to install. I'll talk you through how I patched it for use with the magnificent WHDLoad system (and also works with JST).
A fully text version is also available if you prefer!
OK, the first thing to do is check if the disk is Dos or Non-Dos. Put the disk in the drive and see if it's a valid disk. In this case, no it isn't. You would see an icon and be able to see files if it was.
Insert the game disk and try and make an image of it. Simply type:
and let it image the disk. If all went well, you will have a file called Disk.1 and no errors reported.
You must rip the bootblock off the game and disassemble it to see where it is loading the data to. I use a program called grab to rip data from any file. The bootblock is the first 1024 bytes ($400) so you can type:
grab Disk.1 bootblock first 1024
Now load the bootblock into your disassembler (or cartridge :) I recommend Resource 6.xx as it is very powerful. You will need a lot of memory to use it however!
DC.B 'DOS',0 DC.L $2BAE11B5 DC.L $370 _BootStart move.w #$F80,($DFF180).l movea.l (4).w,a6 move.w #2,($1C,a1) move.l #$78000,($28,a1) move.l #$2C00,($24,a1) move.l #$400,($2C,a1) jsr (-$1C8,a6) jsr (-$96,a6) lea ($7F000).l,sp move.w #$2700,sr move.w #$7FFF,($DFF09A).l move.w #$7FFF,($DFF09C).l move.w #$7FFF,($DFF096).l bset #1,($BFE001).l bsr.w lbC000072 jmp ($78000).l lbC000072 movea.l (4).w,a6 cmpi.w #$24,($22,a6) blt.b lbC000086 moveq #0,d0 moveq #-1,d1 jsr (-$288,a6) lbC000086 rts
In this case, it's a very simple loader. When the bootblock is run, A6 contains Execbase, and A1 points to an IORequest structure which allows you to read more data off the disk.
If you have a fancy disassembling program such as Resource, you can use it to put the labels back into the program. The bootblock would then be as follows:
DC.B 'DOS',0 DC.L $2BAE11B5 DC.L $370 _BootStart move.w #$F80,($DFF180).l movea.l (4).w,a6 move.w #CMD_READ,(IO_COMMAND,a1) move.l #$78000,(IO_DATA,a1) move.l #$2C00,(IO_LENGTH,a1) move.l #$400,(IO_OFFSET,a1) jsr (_LVODoIO,a6) jsr (_LVOSuperState,a6) lea ($7F000).l,sp move.w #$2700,sr move.w #$7FFF,($DFF09A).l move.w #$7FFF,($DFF09C).l move.w #$7FFF,($DFF096).l bset #1,($BFE001).l bsr.w _DisableCache jmp ($78000).l _DisableCache movea.l (4).w,a6 cmpi.w #36,(SoftVer,a6) blt.b _NotWB2 moveq #0,d0 moveq #-1,d1 jsr (_LVOCacheControl,a6) _NotWB2 rts
Let's step through this one:
move.w #$F80,(_custom+color).l ;Background colour ;to orange.
The next part is the main loading from the bootblock:
movea.l (4).w,a6 move.w #CMD_READ,(IO_COMMAND,a1) move.l #$78000,(IO_DATA,a1) move.l #$2C00,(IO_LENGTH,a1) move.l #$400,(IO_OFFSET,a1) jsr (_LVODoIO,a6)
We are going to fill the IO request structure with a read tracks (CMD_READ) operation. A disk IO request needs a command to perform, and it needs to know what to load, how many sectors to load and where to load the data to.
move.l #$78000,(IO_DATA,a1) ;Load file at $78000 move.l #$2C00,(IO_LENGTH,a1) ;Load $2c00 bytes move.l #$400,(IO_OFFSET,a1) ;Load from $400 on ;the disk.
From the code, we can tell that the game is loading a file on the disk from $400 to $3000 ($400 + $2c00). At this point, the easiest way to rip the file off the disk is using a grab utility (there are many other ways). Using grab:
grab Disk.1 Load1 from $400 length $2c00
You now have the first file from the game called Load1. We'll come back to this in a minute.
Next the game performs the I/O which actually loads the file into memory. Another way to get this file is to rip the file using a monitor or Action Replay cartride and save from $78000 length $2c00.
jsr (_LVODoIO,a6) ;Load the file
The game now wants to take over the computer so changes into Supervisor mode:
jsr (_LVOSuperState,a6) ;Supervisor mode lea ($7F000).l,sp ;Change stack to ;$7f000 move.w #$2700,sr move.w #$7FFF,($DFF09A).l ;Disable interrupts move.w #$7FFF,($DFF09C).l move.w #$7FFF,($DFF096).l bset #1,($BFE001).l ;Toggle the LED
The next line branches to a routine to disable the cache.
bsr.w _DisableCache ;Kill the cache
Here's a breakdown of the routine:
_DisableCache movea.l (4).w,a6 cmpi.w #36,(SoftVer,a6) ;Check v36 (WB2.0+) blt.b _NotWB2 ;Branch ahead if ;the user does not ;have WB2 or higher moveq #0,d0 ;Disable the cache moveq #-1,d1 jsr (_LVOCacheControl,a6) _NotWB2 rts ;Return
The only other line was a jump which jumps to the file we loaded previously. We have the file called Load1:
jmp ($78000).l ;Run the file we ;just loaded.
OK, that's the easy part out of the way. Once you have done a few of these you can work out the above information in about 30 seconds! You just need practice!
OK, we're onto the next file, disassemble it and have a look. Often the game will firstly try to work out where there is memory to play with and sure enough, this one does aswell:
_Hex78000 movea.l (4).w,a6 move.l #$50000,d0 ;Allocate $50000 move.l #MEMF_CHIP,d1 ;bytes of chip jsr (_LVOAllocMem,a6) ;memory movea.l (4).w,a6 move.l #$40000,d0 ;Allocate $40000 clr.l d1 ;bytes of memory jsr (_LVOAllocMem,a6)
The code above is simply trying to find $80000 bytes of memory above your minimum 512k chip memory. Chances are the game will require 1Mb to play. You'll need this information later for your HD installer!
After that code, d0 will contain the address where there is $40000 bytes of memory (or 0 if you had none). Now it adds $40000 bytes so the value is guaranteed not to be in your first $80000 (512k) of chip memory.
addi.l #$40000,d0 ;Add $40000
Now the and operation rounds the memory to the nearest $80000 (512k) block and stores the memory at $f00 in memory.
andi.l #$FFF80000,d0 ;Round memory move.l d0,($F00).w ;Store expansion ;memory at $f00
The following code is all rather boring, disabling multitasking, going into supervisor mode, clearing the screen etc.
move.w #$2700,sr move.w #$3FFF,($9A).w move.w #$7CEF,($96).w move.l #_Supervisor,($20).w _Supervisor move.w #$2700,sr lea ($DFF180).l,a0 moveq #$1F,d7 _PaletteBlack clr.w (a0)+ dbra d7,_PaletteBlack lea (_Hex78000).l,sp lea ($DFF000).l,a6 move.w #$7FFF,($9A,a6) move.w #$8610,($96,a6) move.w #$FFFF,($EC0).w
The following code looks interesting and this could well be the loader we need to patch:
move.l #'game',d0 lea ($1000).w,a0 lea ($7CC00).l,a1 bsr.w lbC0000BE ;Possible loader tst.w (lbW0000BC).l ;Check something bne.w _FlashScreen ;If not 0, flash! jmp ($1000).w _FlashScreen move.w #14,($DFF180).l ;Infinite loop move.w #$E80,($DFF180).l ;flashing the bra.b _FlashScreen ;screen
The routine is a dead giveaway for a loader. It is passing in some variables, d0, a0 and a1 and branching to a subroutine. It then checks a value and if it's not 0, it goes into an infinite loop flashing the screen. This is probably because the disk was corrupt, not in the drive or some other error.
The routine labelled lbC0000BE needs investigation to work out how it works.
lbC0000BE move.l d0,(lbW0000B0).l ;Store something bsr.w lbC00017A ;Another routine
Now check the routine lbC00017A to see what it is doing.
lbC00017A move.l a0,-(sp) moveq #0,d0 move.w #$6DE,d1 move.w #2,d2 move.w #0,d3 move.l #0,d4 lea (lbL0001A2,pc),a0 bsr.w lbC0007A2 move.w d0,(lbW0000BC).l movea.l (sp)+,a0 rts
Once you've been in the patching business for a while, this is a dead giveaway as a loader. It's passing various values through to another routine at lbC0007A2:
lbC0007A2 movem.l d1-d7/a0-a5,-(sp) link.w a6,#-$24
These 2 lines are probably the most well known loader ever written on the Amiga, everybody knows a Rob Northen loader when they see it!
You can search in binary for $4e56ff to find Rob Northen loaders quickly!
Here you must patch the code so instead of loading from disk, it will load from your hard drive installer. All you need to remember at this point is the address, $7a2 bytes into the file is a Rob Northen loader which you must patch!
Now back to the routine which called this, we can guess that a0 is the loading address as it passes it into the loading routine and then a few lines down, does a jmp $1000 to start the game. However, we do not want the game to simply start as there is usually something else to patch in the main game.
move.l #'game',d0 lea ($1000).w,a0 lea ($7CC00).l,a1 bsr.w _Loader ;Loader tst.w (lbW0000BC).l ;Check something _Hex78096 bne.w _FlashScreen ;If not 0, flash! _Hex7809a jmp ($1000).w
We will change the jmp ($1000).w to go to our code so we can alter the main game before it does anything.
At this point we can start writing our WHDLoad slave! Firstly you save _resload (all slaves do this):
lea _resload(pc),a1 move.l a0,(a1) ;save for later use
Now load the first file via WHDLoad's resload_DiskLoad routine:
_restart move.l #$400,d0 ;offset move.l #$2c00,d1 ;size moveq #1,d2 ;diskno lea $78000,a0 ;destination address move.l a0,a5 move.l _resload(pc),a2 jsr resload_DiskLoad(a2) ;Load the first file
Now we must change the loader so it loads from our slave rather than from the original Rob Northen disk loader. In the code above, a5 is the address which the game loaded to. You could also use $787a2 instead of $7a2(a5).
lea $7a2(a5),a0 ;Patch Rob Northen loader move.w #$4ef9,(a0)+ ;Insert jmp pea _RobNorthenLoader(pc) move.l (sp)+,(a0)+
$4ef9 is the instruction for JMP, so we are replacing the code at $7a2 with a jmp _loader instruction!
Before we start the main game, we want to have the game return to us so we can do some more fixing:
lea $96(a5),a0 ;Don't start the game, move.w #$4ef9,(a0)+ ;jump back to our patch! pea _Patch(pc) move.l (sp)+,(a0)+
We've change the instruction at $78096 (which is another way of writing $96(a5)) to a jmp _Patch instruction.
The initial game worked out where the extra memory was and then stored it in d0. We'll bypass all that code since we know where the memory is as WHDLoad has already worked it out.
move.l _expmem(pc),d0 jmp $30(a5) ;Start game
The code above will start the game passing through the expansion memory location in d0 and has altered the loader. Now we're onto the next stage.
The game is now patched up until the main file which loads at $1000 and then we have modified it to jump to our patch code labelled _Patch. At this point we can fix anything else (while preserving the state of the game prior to out patch) and then jmp $1000 to begin!
If you run the game and break into it, you will see the level 2 interrupt points to $13aa. There are several ways you can work this out, play the game and break into it, follow the code through until it writes to $68, search for the value $bfec01 which is where the keypress is read from etc. At that address is the following code. You should always check the level 2 interrupt as it contains the keyboard routine so we can add a quit key and/or trainer key:
_Hex13aa move.l a0,-(sp) move.l d0,-(sp) moveq #0,d0 lea ($BFE001).l,a0 move.b ($D00,a0),d0 btst #0,d0 bne.b lbC001402 btst #3,d0 beq.b lbC001408 move.b ($C00,a0),d0 ;Keypress not.b d0 ror.b #1,d0 ;d0 = Raw key move.b d0,($131D).w _Hex13d2 bset #6,($E00,a0) move.b #1,($500,a0) lea (lbL00131E).l,a0 tst.b d0 bmi.w lbC0013F4 move.b #1,(a0,d0.w) bra.w lbC001408 lbC0013F4 bclr #7,d0 move.b #0,(a0,d0.w) bra.w lbC001408 lbC001402 bclr #6,($E00,a0) lbC001408 move.l (sp)+,d0 movea.l (sp)+,a0 move.w #8,($DFF09C).l rte
Somewhere around the part where we work out the key that was pressed, we want to alter the code to go to our routine and if it's the quit key in WHDLoad, completely exit the game.
In this case, I'll choose _Hex13d2 as the instruction is 6 bytes. 6 is a magic number in patching as we have to jump to our routine somewhere in memory. The instruction will need 2 bytes, plus the address requires 4 bytes so we need 6 in total.
We'll replace that with a subroutine which checks the key is the exit key, and if so, quit the game. If it's not the quit key, we'll do the instruction it used to do, and continue on as if nothing has happened!
The original code was this:
_Hex13d2 bset #6,($E00,a0)
We'll change it by having this code in our WHDLoad slave:
lea $13d2,a0 ;Quit key move.w #$4eb9,(a0)+ ;Insert a jsr pea _keybd(pc) move.l (sp)+,(a0)+
$4eb9 is the instruction for jsr, which jumps to a subroutine. In our case, it will do jsr _keybd. We do not know where in memory the _keybd routine is, so we'll push it onto the stack, and then move it into the game at $13d4.
In our slave we add the following keyboard routine:
_keybd cmp.b _keyexit(pc),d0 ;Check exit key matches beq _exit ;If so, exit the game bset #6,($e00,a0) ;Original stolen 6 bytes rts
The original 6 bytes of code that we overwrote (or stole) is done just prior to us returning control back to the game.
Also in the game is another Rob Northen loader at $5368. We'll need to redirect that to our slave aswell. You can find this by doing a search through the code for magic $4e56ff value.
lea $5368,a0 ;Patch Rob Northen loader move.w #$4ef9,(a0)+ ;Insert jmp pea _RobNorthenLoader(pc) move.l (sp)+,(a0)+
That should be enough to get the game going for now. Our WHDLoad slave looks like this:
INCDIR include: INCLUDE whdload.i INCLUDE whdmacros.i IFD BARFLY OUTPUT "ATRXmas.slave" BOPT O+ ;enable optimizing BOPT OG+ ;enable optimizing BOPT ODd- ;disable mul optimizing BOPT ODe- ;disable mul optimizing BOPT w4- ;disable 64k warnings BOPT wo- ;disable warnings SUPER ;disable supervisor warnings ENDC ;====================================================================== _base SLAVE_HEADER ;ws_Security + ws_ID dc.w 10 ;ws_Version dc.w WHDLF_NoError|WHDLF_EmulTrap ;ws_flags dc.l $80000 ;ws_BaseMemSize dc.l 0 ;ws_ExecInstall dc.w _Start-_base ;ws_GameLoader dc.w 0 ;ws_CurrentDir dc.w 0 ;ws_DontCache _keydebug dc.b 0 ;ws_keydebug _keyexit dc.b $5d ;ws_keyexit = PrtSc _expmem dc.l $80000 ;ws_ExpMem dc.w _name-_base ;ws_name dc.w _copy-_base ;ws_copy dc.w _info-_base ;ws_info ;============================================================================ IFND .passchk DOSCMD "WDate >T:date" .passchk ENDC _name dc.b "ATR Xmas Demo",0 _copy dc.b "1991 Team 17",0 _info dc.b "Installed by Codetapper/Action!",10 dc.b "Version 1.0 " INCBIN "T:date" dc.b -1,"Thanks to Riempie for the original!",0 EVEN ;====================================================================== _Start ; A0 = resident loader ;====================================================================== lea _resload(pc),a1 move.l a0,(a1) ;save for later use _restart move.l #$400,d0 ;offset ($3c00) move.l #$2c00,d1 ;size moveq #1,d2 ;diskno lea $78000,a0 ;destination address move.l a0,a5 move.l _resload(pc),a2 jsr resload_DiskLoad(a2) lea $7a2(a5),a0 ;Patch Rob Northen loader move.w #$4ef9,(a0)+ pea _RobNorthenLoader(pc) move.l (sp)+,(a0)+ lea $96(a5),a0 move.w #$4ef9,(a0)+ pea _Patch(pc) move.l (sp)+,(a0)+ move.l _expmem(pc),d0 jmp $30(a5) ;Start game _Patch move.l a0,-(sp) lea $13d2,a0 ;Quit key move.w #$4eb9,(a0)+ pea _keybd(pc) move.l (sp)+,(a0)+ lea $5368,a0 ;Patch Rob Northen loader move.w #$4ef9,(a0)+ pea _RobNorthenLoader(pc) move.l (sp)+,(a0)+ move.l (sp)+,a0 jmp $1000 ;====================================================================== _RobNorthenLoader movem.l d1-d7/a0-a6,-(sp) move.l _resload(pc),a2 ;a0 = dest address mulu #$200,d1 ;offset (sectors) mulu #$200,d2 ;length (sectors) exg.l d1,d0 ;d0 = offset (bytes) exg.l d2,d1 ;d1 = length (bytes) addq #1,d2 ;d2 = disk jsr resload_DiskLoad(a2) ;a0 = destination movem.l (sp)+,d1-d7/a0-a6 moveq #0,d0 rts ;====================================================================== _keybd cmp.b _keyexit(pc),d0 beq _exit bset #6,($e00,a0) ;Stolen code rts ;====================================================================== _resload dc.l 0 ;address of resident loader ;====================================================================== _exit pea TDREASON_OK bra _end _debug pea TDREASON_DEBUG bra _end _wrongver pea TDREASON_WRONGVER _end move.l (_resload),-(a7) add.l #resload_Abort,(a7) rts ;======================================================================
If this seems very difficult, relax! You have to serve your apprenticeship and become familiar with the Amiga, how memory is organised, interrupts, the stack, assembler etc. You cannot sit down and expect to be able to patch a game if you do not know hexidecimal and assembler pretty well.
Once you have patched a few games, you will be able to knock up slaves for easy ones like this in about 30 minutes. Later you will find difficult games with copy protection, blitter problems, stack frame problems and lots of other things.
Start off patching easy PD games and coverdisk demos. Generally they have easy loaders, no copy protection and are either a single load or only a few files.
After a while, you will become an expert at reusing another install as the basis for a game and patching it. Loaders will be the same, memory allocation the same etc. It just takes plenty of practice and time.
Once the game in installed, you can rip some images from the game and make a nice icon, add a ReadMe file, an install script and find a few beta-testers to test your work on their high-powered Amigas :)
Site updated: 28/8/2005 ©1997-2005 Codetapper/Action! All rights reserved.