▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ░▒▓█ rapid1337.com █▓▒░ ╓──────────────────────────────────────────╖ ║ Strider ║ ╟──────────────────────────────────────────╢ ║ Written by Codetapper/Action on ║ ║ 20/08/2004! ║ ╟──────────────────────────────────────────╢ ║ ·· security/strider.html ·· ║ ╙──────────────────────────────────────────╜ ============================================================================ Title ..: Strider Author .: staff Date ...: 2004-08-23 Section : Security ============================================================================ ## HOW TO WRITE A HARD DRIVE INSTALLER FOR STRIDER (MFM) Written by Codetapper/Action on 20/08/2004! ## INTRODUCTION This tutorial has been written to explain how to write a hard drive installer for the Amiga game Strider which is in a non-standard disk format. The game uses a custom MFM format and loading system and we will reverse engineer the disk format to save the game to a single file, and write a WHDLoad slave to allow it to run from a hard drive. To follow this tutorial you must have a working knowledge of 680×0 assembly language as well as the Amiga hardware. I have not explained how to use tools like Resource – you must know how to use them yourself or read the documentation that comes with the program. You can also use tools like the Action Replay cartridge to disassemble code but once you turn off your Amiga you have lost everything. With a disassembler you can save your work and reload it later. Hence there is no real support for Action Replay like monitors in this document. ## TOOLS REQUIRED In this tutorial I have used the following tools: ``` Resource – Disassembler WWarp – Processing the disk image Grab – Saves part of a file RawDIC – Ripping the disk ``` You should not need any of the tools to follow the tutorial, but it is a good exercise to try them out yourself: WWarp and RawDIC are available in the WHDLoad developer package at [http://www.whdload.de](http://www.whdload.de/) The Grab utility is on Aminet or the Action website at [http://zap.to/action](http://zap.to/action/) Resource is a commercial product and you will have to obtain this by some other means. Please do not ask me for it! ## WRITING THE GAME BACK TO A FLOPPY DISK If you have the file Strider.wwp you can write the game back to a floppy disk and try it out. To do this, execute the following commands: ``` 1:> WWarp Strider.wwp W writing track 0, format dosf writing track 1, format tiertex testing drive speed at track 1, 3142.7 3143.2 3143.0 3142.1 3142.3, using writelen 3154. writing track 2, format tiertex writing track 3, format tiertex … writing track 152, format tiertex writing track 153, format tiertex ``` WWarp happens to know the disk layout for this game and can therefore produce a working copy of the disk minus any special longtrack copy protection the game might have. ## PART 1: THE BOOTBLOCK First we need to grab the bootblock of the game. There are several tools you can use for this, but since we have Strider as a WWarp file let’s use WWarp: ``` 1:> WWarp Strider.wwp S 0 save file track.000.dosf ``` This means save track 0 of strider. The output file is track.000.dosf. Now we only really care about the bootblock, so grab the first 1024 bytes of it into a new file: ``` 1:> Grab track.000.dosf boot FIRST 1024 Grab v0.01 ? 1998 Codetapper (codetapper@hotmail.com) Reading from track.000.dosf to boot from 0 to 1024 ``` You now have the bootblock as a single file called “boot” which is 1024 bytes in length. You can now move onto the next phase. ## PART 2: DISASSEMBLING THE BOOTBLOCK Start off by disassembling the bootblock by loading it into your disassembler. It will look like this: ``` neg.w ???? subq.b #1,d0 move.w -(a5),-(sp) chk.w ????,d4 ori.b #0,d0 lea ( DFF000).l,a0 move.w # 7FFF,( 9A,a0) move.w # 7FFF,( 96,a0) move.w # F00,( 180,a0) lea (START+ 2E,pc),a1 move.l a1,( 20).l move.w # 2000,sr lea ( 80000).l,sp lea (START+ 50,pc),a1 lea ( 80).l,a2 movea.l a2,a3 move.w # 1FF,d0 move.l (a1)+,(a2)+ dbra d0,START+ 48 jmp (a3) ``` The first 3 longwords are data – first off is the type of disk it is, the string “DOS” followed by a number. Then comes the checksum for the bootblock. Following that is a magic number that is ignored so can be anything. A lot of crackers put their name or handle here! Change the first 3 longwords to display as data, and set the next part to code: ``` dc.l (‘DOS’ ``` Now disassemble the bootblock: ``` dc.l (‘DOS’ ``` Now let’s work out what the bootblock is doing: ``` lea ( DFF000).l,a0 ;a0 = Custom registers move.w # 7FFF,(intena,a0) ;Disable interrupts move.w # 7FFF,(dmacon,a0) ;Disable DMA move.w # F00,(color,a0) ;Set background to red ``` The background turns red because we wrote f00 which is an RGB value. The red component is f, green is 0 and blue is 0. Hence the screen will turn red. All standard stuff so far – killing the operating system. Now the game wants to go into supervisor mode: ``` lea (lbC00002E,pc),a1 move.l a1,( 20).l lbC00002E move.w # 2000,sr lea ( 80000).l,sp ``` The game sets up the routine lbC00002E at 20 so if a violation occurs, the routine will be run. The game sets the status register to 2000 which causes a violation to occur, so the 68000 follows the vector at 20 which points to the routine lbC00002E. Let’s comment this: ``` lea (_Supervisor,pc),a1 ;Go into supervisor mode move.l a1,( 20).l _Supervisor move.w # 2000,sr lea ( 80000).l,sp ;Supervisor stack = 80000 ``` The Amiga is now taken over and the stack has been moved to 80000. Chances are the game requires 512k of chip memory (512k = 80000). Let’s continue: ``` lea (lbC000050,pc),a1 lea ( 80).l,a2 movea.l a2,a3 move.w # 1FF,d0 lbC000048 move.l (a1)+,(a2)+ dbra d0,lbC000048 jmp (a3) lbC000050 move.w # 7CF,d7 ``` The game wants to copy the code to 80 because currently the routine could be located anywhere in memory: ``` lea (_Hex80,pc),a1 ;Code we want to copy lea ( 80).l,a2 ;Setup destination movea.l a2,a3 ;a3 = Destination move.w # 1FF,d0 _CopyTo80 move.l (a1)+,(a2)+ ;Copy 200-1 longs from dbra d0,_CopyTo80 ;a1 to a2 jmp (a3) ;Jump to 80 _Hex80 move.w # 7CF,d7 ``` Now the game is clearing some memory at 70000: ``` _Hex80 move.w # 7CF,d7 ;Clear 7d0 longwords at lea ( 70000).l,a2 ; 70000 (which is 1f40 _Clear70000 clr.l (a2)+ ;bytes, which is 8000 dbra d7,_Clear70000 ;bytes in decimal) ``` The next piece of code is rather stupid and pointless: ``` lea (lbW00031C,pc),a1 lea ( 70000).l,a2 move.w # 1F,d7 lbC00006E move.w #2,d6 lbC000072 dbra d6,lbC000072 adda.w # 22,a2 dbra d7,lbC00006E ``` The d6 and d7 dbra loops are run but there is nothing in between the d6 loop (as it just decrements d6 until d6 is false = ffff) and then it adds on 22 to a2 for d7 loops. It is most likely setting up some kind of copperlist, but we don’t need to worry about it now. The next part is more interesting as it is setting up a screen and a copperlist: ``` move.w #0,( 180,a0) move.w # 200,( 96,a0) move.w # 1200,( 100,a0) clr.w ( 102,a0) clr.w ( 108,a0) move.w # 38,( 92,a0) move.w # D0,( 94,a0) move.w # 2C81,( 8E,a0) move.w # F4C1,( 90,a0) lea (lbW000290,pc),a1 move.l a1,( 80,a0) move.w ( 88,a0),d0 move.w # 8380,( 96,a0) … lbW000290 dc.w E0 dc.w 7 dc.w E2 dc.w 0 dc.w 182 dc.w A00 dc.w 3201 dc.w FFFE dc.w 182 dc.w A10 dc.w 3401 dc.w FFFE … dc.w 182 dc.w EC0 dc.w 4E01 dc.w FFFE dc.w FFFF dc.w FFFE ``` The first part is referencing dff000 (stored in a0 remember!) which is custom hardware references, so let’s fill in some comments: ``` move.w #0,(color,a0) ;Background to black move.w # 200,(dmacon,a0) ;Master DMA switch off move.w # 1200,(bplcon0,a0) ;Setup 1 bitplane screen clr.w (bplcon1,a0) ;No playfield scroll clr.w (bpl1mod,a0) ;No bitplane modulo move.w # 38,(ddfstrt,a0) ;Setup data display fetch move.w # D0,(ddfstop,a0) ;start and stop values move.w # 2C81,(diwstrt,a0) ;Setup display window move.w # F4C1,(diwstop,a0) ;start and stop values lea (_Copperlist,pc),a1 ;a1 = Copperlist move.l a1,(cop1lc,a0) ;Move into copperlist move.w (copjmp1,a0),d0 ;counter 1 and strobe move.w # 8380,(dmacon,a0) ;Enable DMA, bitplane ;and copper ``` Now we come to the fun part, the custom disk accessing! Here is the routine before commenting – see if you can spot the loading parameters: ``` lea ( BFE001).l,a1 lea ( BFD000).l,a2 lea (lbL000314,pc),a6 move.b # 7F,( 100,a2) andi.b # F7,( 100,a2) ori.b #2,( 100,a2) lbC0000E4 andi.b # FE,( 100,a2) ori.b #1,( 100,a2) bsr.w lbC000158 btst #4,(0,a1) bne.b lbC0000E4 clr.w (2,a6) move.w #1,(0,a6) lea ( 800).l,a5 move.l # 2BF20,d5 move.l d5,d7 addi.l # 17FF,d7 divu.w # 1800,d7 subq.w #1,d7 bsr.w lbC000162 ori.b # 88,( 100,a2) andi.b # F7,( 100,a2) move.b # FF,( 100,a2) move.w (2,a6),d1 move.w (0,a6),d0 jmp ( 81C).l ``` The important registers for disk accessing are bfe001 and bfd100. The CIA register bfe001 tells you when a disk has been removed, is write protected, has it’s heads on track 0, and is ready to receive commands. The CIA register bfd100 controls moving the heads, setting the direction to move, selecting the side of the disk, selecting which floppy drive is under control and turning on the disk motors. ``` lea ( BFE001).l,a1 lea ( BFD000).l,a2 lea (_DiskParams,pc),a6 move.b # 7F,( 100,a2) ;Motor on andi.b # F7,( 100,a2) ;Select DF0: ori.b #2,( 100,a2) ;Set direction outwards ``` To step the disk drive heads you must set bit 0 of bfd100 to 1, then 0 and then 1 again followed by a 3 millisecond delay. If the delay is not at least 3ms, the heads will not have finished moving, so you might not have the heads over the correct cylinder. ``` _GoToTrack0 andi.b # FE,( 100,a2) ;Prepare to step heads ori.b #1,( 100,a2) bsr.w _Delay btst #4,(0,a1) bne.b _GoToTrack0 … _Delay move.w # 10C8,d0 _EmptyLoop dbra d0,_EmptyLoop rts ``` The first thing to notice is the stupid programmer has made a nasty CPU dependant loop for the delay. It counts down from 10c8 to -1 and then returns. On a fast CPU with a cache, the loop may be almost instant, so the heads probably will not have moved in time. Hence this game is unlikely to work on most grunty Amigas without degrading. Because the game has just loaded the bootblock, the heads should be on track 0 but if they weren’t, the loop would repeat by setting bit 0 to 0, then to 1, delaying for a while and then checking if the disk is on track 0. If it isn’t on track 0 (because bit 4 is not 0) it goes back in a loop: ``` _GoToTrack0 andi.b # FE,( 100,a2) ;Set bit 0 to 0 ori.b #1,( 100,a2) ;Set bit 0 to 1 bsr.w _Delay ;Wait 3ms (yeah right!) btst #4,(0,a1) ;Test if we are on track bne.b _GoToTrack0 ;0 and loop until we are … _Delay move.w # 10C8,d0 ;Lame 3ms delay _EmptyLoop dbra d0,_EmptyLoop rts ``` The Amiga hardware has no idea where the disk drive heads are. The only thing it can tell you is if they are on track 0. So to go and load data on track 10, you have to go to track 0, then keep a counter yourself of how many tracks you have stepped over. We know the disk drive heads for DF0: are on track 0 so let’s continue. Usually after moving the heads you do some kind of load and jmp to start the next part, so keep that in mind when analysing the next part of the code: ``` clr.w (2,a6) ;Track heads are over move.w #1,(0,a6) ;Track to load perhaps? lea ( 800).l,a5 ;Load address perhaps? move.l # 2BF20,d5 ;Length perhaps? move.l d5,d7 addi.l # 17FF,d7 divu.w # 1800,d7 ;Divide length by 1800 subq.w #1,d7 ;and subtract one bsr.w _Loader ;Load data ori.b # 88,( 100,a2) ;Motor off, deselect DF0: andi.b # F7,( 100,a2) ;Deselect DF0: move.b # FF,( 100,a2) move.w (2,a6),d1 ;Setup parameters move.w (0,a6),d0 jmp ( 81C).l ;Start game ``` I usually find it easier to guess what the parameters are for a loader, and then change the labels later if I guessed incorrectly. Giveaways for offset or lengths include division. A normal AmigaDos disk has 1600 bytes per track of data. If you wanted to start reading the 5000th byte of a disk, you need to work out which track that would be on. To do that you divide 5000 by 1600 and take just the integer part. 5000 / 1600 = 3 remainder e00. So you would load track 3 and start copying data from position e00. ``` addi.l # 17FF,d7 divu.w # 1800,d7 ;Divide length by 1800 subq.w #1,d7 ;and subtract one ``` In Striders case, it is adding on 17ff, dividing by 1800 and subtracting one. If you put some numbers into the formula, you can see what is happening: ``` 0 + 17ff = 17ff / 1800 = 0 – 1 = -1 200 + 17ff = 19ff / 1800 = 1 – 1 = 0 1800 + 17ff = 2fff / 1800 = 1 – 1 = 0 2bf20 + 17ff = 2d71f / 1800 = 30 – 1 = 29 ``` If there is nothing to load, d7 is -1. If there is one track to load, d7 is 0. In our case, we have d7 equal to 29. Whenever you see a minus one after a calculation you can be pretty sure the game uses a DBF loop in it’s code to count how many loops to perform. DBF loops count down until they are false which means they do one extra loop. Hence programmers subtract one from calculations so the DBF loop will be the correct number of loops. The division is the length of a track, so in this case Strider expects 1800 bytes of data per track. This is more than AmigaDos can handle, so this is a custom MFM format. Whenever you disassemble a routine you should try and think about everything you would do yourself to write a safe generic routine. If you do this, it will often help you work out what code is doing. This is part of the art of Zen cracking – identifying code just by a quick glance. Back to Strider – if I had to write a routine to load a big chunk of data, I would start by moving to the correct track. On the Amiga a disk has 2 sides, so either you load all data from one side of the disk, then switch to the other, or you alternate between the disk sides. I would setup a retry counter so if the read failed, it would retry a few times. If the read was successful, move to the next track, otherwise just reload that track. If a track could not be read a number of times, flash the screen or print up an error message. If the data appeared to load OK, check if the data matches what we expect. Keep loading until we have read enough data correctly. Keep all this in the back of your mind as it will help analysing loaders! Not all games will have retry counts, checksums etc but if you try and consider everything that could exist, it will help you guess what each part is doing. Here is the rest of the bootblock commented: ``` _FatalError move.w # F00,(color,a0) ;Red background bsr.w _WaitAWhile move.w #0,( 180,a0) bra.w _ChecksumOK _Loader move.w #8,(4,a6) ;Retry count perhaps? move.w (0,a6),d0 btst #0,d0 ;Test for odd number beq.b _MoveTracks ;If it’s even, skip ahead andi.b # FB,( 100,a2) ;Select top of disk bra.b _WaitDiskReady _MoveTracks ori.b #4,( 100,a2) ;Select bottom of disk andi.b # FD,( 100,a2) ;Direction inwards, bit 0 andi.b # FE,( 100,a2) ;to 1, then 0, then 1 ori.b #1,( 100,a2) ;(step a track) bsr.w _Delay ;3ms delay _WaitDiskReady btst #5,(0,a1) ;Wait for the disk drive bne.b _WaitDiskReady ;to be ready clr.w (dsklen,a0) ;Track length = 0 move.w # 8210,(dmacon,a0) ;Enable disk DMA move.w # 8400,(adkcon,a0) ;Word sync move.w # A245,(dsksync,a0) ;Sync = a245 move.l # 7C000,(dskpt,a0) ;Destination for MFM move.w # 9C00,(dsklen,a0) ;Write to disk length move.w # 9C00,(dsklen,a0) ;twice to start disk DMA _WaitDiskInt move.w ( 1E,a0),d0 ;Wait for the disk andi.w #2,d0 ;interrupt to show data beq.b _WaitDiskInt ;has finished loading move.w d0,( 9C,a0) ;Acknowledge interrupt clr.w (dsklen,a0) ;Clear track length lea ( 7C002).l,a3 ;a3 = Start of data (one movea.l a5,a4 ;word past first sync) move.l # 55555555,d3 ;MFM Mask move.w (a3)+,d2 ;Merge 2 MFM words of move.w (a3)+,d1 ;data into one word and.w d3,d2 and.w d3,d1 add.w d2,d2 or.w d2,d1 cmp.w (0,a6),d1 ;Check track number is beq.b _TrackNumberOK ;what we expected move.w # FF,(color,a0) ;Cyan background to show bsr.w _WaitAWhile ;error and wait a bit _TrackNumberOK moveq #0,d4 move.w # 5FF,d6 ;Decode 600-1 longs _DecodeLoop move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits add.l d1,d4 ;Add to checksum move.l d1,(a4)+ ;Store decoded longword dbra d6,_DecodeLoop ;Decode rest of track move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits not.l d4 cmp.l d4,d1 ;Compare checksum from beq.b _ChecksumOK ;data with expected value subq.w #1,(4,a6) ;Checksum did not match so beq.w _FatalError ;decrement retry count move.w #15,(color,a0) ;Blue background bra.w _WaitDiskReady _ChecksumOK movea.l a4,a5 ;Move destination address move.w (0,a6),d1 ;Read previous track addq.w #1,d1 ;number, add one and move.w d1,(0,a6) ;store this new value lsr.w #1,d1 ;Divide track number by 2 move.w d1,(2,a6) ;to get cylinder and store dbra d7,_Loader ;Loop around loading rts _WaitAWhile move.l d0,-(sp) _WaitButtonDown btst #6,( BFE001).l bne.w _WaitButtonDown move.w # FFFF,d0 _SmallDelay nop dbra d0,_SmallDelay _WaitButtonUp btst #6,( BFE001).l beq.w _WaitButtonUp move.w # FFFF,d0 _AnotherDelay nop dbra d0,_AnotherDelay move.l (sp)+,d0 rts ``` To start disk access on the Amiga you must write to the disklen register twice. This is a safety mechanism so that rogue programs that have crashed are not as likely to destroy data when they have access faults. MFM data has a clock bit between each real bit of data. The clock bit is set to 0 or 1 depending on the data bit and this is done so that long runs of 0’s or 1’s do not occur. It is much easier for a disk controller to synchronise when the data changes between 0 and 1 regularly. Once you have identified the loading routine, you need to write a program to rip all the data from the disk. Some crackers will try and play through the game with a cartridge and save the files as they load. This is obviously dodgy when the game isn’t sequential or has lots of hidden sections that might be missed. Others will use the games own loader and call the routine with the parameters to get at the data. This can also cause problems because sometimes you do not have a table of everything the game loads. The safest method is to rip every track of the disk and store it in one big chunk. Then you alter the game loader so instead of using the built in loader (which works in chunks of 1800 bytes/track) it calls your loader. We will use the great program RawDIC to write a “slave” which knows the layout of the disk and will include enough code to decode a track of MFM data into a track of useful data. Before we proceed you should understand the 3 vital disk hardware registers. Here is some background information: ``` DFF020 DSKPT Disk Pointer Status: Write-Only. Chip: Agnus This is where you store the starting address of your disk data prior to activating disk DMA. DSKPT actually consists of two separate hardware registers – DSKPTH (H for the High bits of the address) and DSKPTL (L for the Low bits of the address). Since they’re mapped as two consecutive memory locations, it’s easiest to treat them as one 32-bit register. DFF024 DSKLEN Disk Data Length Status: Write-Only. Chip: Paula Bits 0-13: LENGTH: Number of words to read or write Bit 14: WRITE: 1 = Activate write mode; 0 = Activate read mode Bit 15: DMAENA: 1 = Activate disk DMA; 0 = Deactivate disk DMA DFF07E DSKSYNC Disk Sync Pattern Status: Write-Only. Chip: Agnus Before reading data from the disk, it’s often necessary to syncronize the drive’s head on a particular bit pattern. This register allows you to do just that. When the WORDSYNC bit (bit 10) in the ADKCON register ( DFF09E) is set, the disk controller’s DMA is enabled and the controller prepares to search the disk for the sync pattern found in this register. The disk controller doesn’t start searching until this register is written to. When the sync pattern is found, subsequent data is read into RAM. Bit 12 of the DSKBYTR register ( DFF01A) is set to 1 for two or four microseconds (depending on the setting of ADKCON’s bit 8) as soon as the sync pattern is located. This event can also be used to trigger a level 6 interrupt. In MFM format (the disk format used by AmigaDOS), the sync pattern should be a value that is impossible to create using MFM data coding. This way it can’t be confused with actual data. ``` Now let’s work through the loader in stages and identify the main parts: ``` move.w # A245,(dsksync,a0) ;Sync = a245 move.l # 7C000,(dskpt,a0) ;Destination for MFM move.w # 9C00,(dsklen,a0) ;Write to disk length move.w # 9C00,(dsklen,a0) ;twice to start disk DMA ``` A normal AmigaDOS disk uses the sync 4489. Strider uses a non-standard sync of a245. This game uses the address 7c000 as it’s MFM buffer. This is where the data will appear once it has been read from the disk. Bits 0-13 of DSKLEN ( dff024) store the length, so mask out the 2 high bits of the length register – ie. 9c00 AND 3fff = 1c00. Strider therefore is reading 1c00 words of MFM data. Now the game waits for the disk interrupt to happen: ``` _WaitDiskInt move.w ( 1E,a0),d0 ;Wait for the disk andi.w #2,d0 ;interrupt to show data beq.b _WaitDiskInt ;has finished loading move.w d0,( 9C,a0) ;Acknowledge interrupt clr.w (dsklen,a0) ;Clear track length ``` Now we need to work out how to decode this MFM data into normal data. This is because the data at 7c000 has 2 bits of data for every one useful bit, so we must skip the useless stuff. ``` lea ( 7C002).l,a3 ;a3 = Start of data (one movea.l a5,a4 ;word past first sync) move.l # 55555555,d3 ;MFM Mask move.w (a3)+,d2 ;Merge 2 MFM words of move.w (a3)+,d1 ;data into one word and.w d3,d2 and.w d3,d1 add.w d2,d2 or.w d2,d1 cmp.w (0,a6),d1 ;Check track number is beq.b _TrackNumberOK ;what we expected ``` Track 1 as MFM data looks like this: ``` a245 4489 2aaa aaa9 12a5 2aa9… ``` The sync a245 was read by the disk controller and as soon as it is found it starts copying the data into the DSKPT register ( 7c000). The first word of data will therefore be the 4489 at address 7c000. The register a3 points at 7c002 which is 2 bytes past the sync. Hence a3 is pointing to the 2aaa part. Now tracing through the code: ``` lea ( 7C002).l,a3 ;a3 = 7c002 movea.l a5,a4 ;a4 = 800 move.l # 55555555,d3 ;d3 = 55555555 ``` Now it starts processing the data, starting with the 2aaa: ``` move.w (a3)+,d2 ;d2 = 2aaa move.w (a3)+,d1 ;d1 = aaa9 and.w d3,d2 ;d2 = 0000 and.w d3,d1 ;d3 = 0001 add.w d2,d2 ;d2 = 0000 or.w d2,d1 ;d1 = 0001 ``` Now after reading 2 words of MFM data, stripping out the clock bits and adjusting for odd bits, we have one word of real data. The register d1 is now equal to 1, which funnily enough matches the track number we loaded: ``` cmp.w (0,a6),d1 ;Check track number is beq.b _TrackNumberOK ;what we expected ``` So far so good! We read track 1 from the disk, found the sync we wanted, and read the next useful word of data which is the track number. If the track number didn’t match (because of a bad disk or a faulty read) then the following code would turn the screen cyan and wait a while: ``` move.w # FF,(color,a0) ;Cyan background to show bsr.w _WaitAWhile ;error and wait a bit ``` Continuing on: ``` _TrackNumberOK moveq #0,d4 move.w # 5FF,d6 ;Decode 600-1 longs _DecodeLoop move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits add.l d1,d4 ;Add to checksum move.l d1,(a4)+ ;Store decoded longword dbra d6,_DecodeLoop ;Decode rest of track ``` The game now processes 600 longwords. d6 is set to 5ff and because of the dbra loop it does one extra loop which is 600. 600 loops processing one longword of data means 600 x 4 = 1800 bytes. So this is the guts of the loader, as we decode the MFM buffer into 1800 bytes of real data. Looking at the label _DecodeLoop, you can see that it reads an even longword of data from 1804 bytes past the current position of a3, and then reads the odd longword from the present position of a3 and then increments it. It then loops around 600 times. Note that at the top of the routine it sets d4 to 0, and everytime it decodes a longword, it adds the value onto d4. This is a dead giveaway for a checksum! Continuing on: ``` move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits ``` We have read the sync, decoded the track number, decoded the main data on the track and now are decoding another longword. At the end of the routine, d1 contains the longword read from the disk. ``` not.l d4 cmp.l d4,d1 ;Compare checksum from beq.b _ChecksumOK ;data with expected value ``` Now d4 is inverted with the not.l operation, and the value d1 is compared to d4. If the values match, it branches to the label _ChecksumOK. If the value did not match, the following code is run which decrements a retry count and sets the background colour to blue: ``` subq.w #1,(4,a6) ;Checksum did not match so beq.w _FatalError ;decrement retry count move.w #15,(color,a0) ;Blue background bra.w _WaitDiskReady ``` If the checksum was OK it adjusts the destination address, increments the track number and loops around loading until d7 is ffff: ``` _ChecksumOK movea.l a4,a5 ;Move destination address move.w (0,a6),d1 ;Read previous track addq.w #1,d1 ;number, add one and move.w d1,(0,a6) ;store this new value lsr.w #1,d1 ;Divide track number by 2 move.w d1,(2,a6) ;to get cylinder and store dbra d7,_Loader ;Loop around loading rts ``` After the data has been loaded, the disk drive is turned off, and a few parameters are passed into the game via d0 and d1, and then jmp 81c starts the action: ``` ori.b # 88,( 100,a2) ;Motor off, deselect DF0: andi.b # F7,( 100,a2) ;Deselect DF0: move.b # FF,( 100,a2) move.w (2,a6),d1 ;Setup parameters move.w (0,a6),d0 jmp ( 81C).l ;Start game ``` We now have enough information to rip track 1 from the game. And 99% of games use the same disk loader for the entire game, so once you can rip track 1 you can generally rip the entire disk. ## PART 3: RIPPING THE DISK WITH RAWDIC RawDIC is the tool of choice for cleanly ripping a disk in a system friendly way. It consists of a series of lists that describe the format of the disk, as well as routines to decode a track of data. All we know from the game so far is that track 0 is standard AmigaDOS format and track 1 is a custom format with sync a245 which has 1800 bytes/track. We can assume the rest of the disk is also this format and change it later if this isn’t the case. We will start off by creating the routine to decode a track of data. RawDIC passes the MFM data from the disk in the register a0, and expects you to fill the buffer a1 with the decoded data. RawDIC passes the track number into your routine in the register d0, and expects you to return a success or error code in d0 depending on your results. Once you have worked out the loading routine you can lift the entire routine and drop it into your source. Then you usually just need to change a few addresses to make it work: ``` move.l # 7C000,(dskpt,a0) ;Destination for MFM lea ( 7C002).l,a3 ;a3 = Start of data (one movea.l a5,a4 ;word past first sync) ``` These absolute values of 7c000 and 7c002 were used in Strider but you cannot use these in the RawDIC imager as the data could be loaded anywhere. RawDIC takes care of all the disk seeking and reading so all you need to worry about is converting the data. All registers can be destroyed so you do not need to worry about preserving anything. Strider uses a3 as the source data and a5 (which was moved into a4) as the destination for the data. So in the RawDIC imager, you can put set the RawDIC parameters to what Strider wanted: ``` _RipTrack lea (2,a0),a3 ;lea ( 7C002).l,a3 movea.l a1,a4 ;movea.l a5,a4 ``` Now we copy in the code from Strider: ``` move.l # 55555555,d3 ;MFM Mask move.w (a3)+,d2 ;Merge 2 MFM words of move.w (a3)+,d1 ;data into one word and.w d3,d2 and.w d3,d1 add.w d2,d2 or.w d2,d1 ``` At this poing the game checks if the track number matched. Strider had a small structure in register a6 which stored a few parameters such as the track number, so we must change this: ``` cmp.w (0,a6),d1 ;Check track number is beq.b _TrackNumberOK ;what we expected ``` RawDIC passed the actual track number in register d0, so rather than comparing (0,a6) with d1, we can just compare d0 with d1: ``` cmp.w d0,d1 ``` If the track number does not match, we need to bail out with an error code, so rather than branching if the track number matched, let’s abort if it didn’t. There are 3 main error codes in RawDIC: ``` IERR_OK = Sucess! IERR_CHECKSUM = Checksum error IERR_NOSECTOR = No Sector error ``` We’ll make RawDIC abort with the no sector error if it failed, so the code becomes: ``` cmp.w d0,d1 bne _NoSector … _NoSector moveq #IERR_NOSECTOR,d0 rts ``` Now we can keep copying Striders loader because it just processes data and will safely work no matter where in memory it is running from: ``` moveq #0,d4 move.w # 5FF,d6 ;Decode 600-1 longs _DecodeLoop move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits add.l d1,d4 ;Add to checksum move.l d1,(a4)+ ;Store decoded longword dbra d6,_DecodeLoop ;Decode rest of track move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits not.l d4 ``` The checksum part needs a small change aswell: ``` cmp.l d4,d1 ;Compare checksum from beq.b _ChecksumOK ;data with expected value subq.w #1,(4,a6) ;Checksum did not match so beq.w _FatalError ;decrement retry count move.w #15,(color,a0) ;Blue background bra.w _WaitDiskReady _ChecksumOK movea.l a4,a5 ;Move destination address ``` If the checksum matched, it means we have successfully decoded a track of data, so we are done. There is no need to step the heads and alter destination addresses because RawDIC takes care of all of this. Just tell RawDIC we have succeeded if the checksum matched, and if it failed then return the error code IERR_CHECKSUM. RawDIC will then re-read the track a few times in case it just had a weak read. The code above therefore becomes: ``` cmp.l d4,d1 ;Compare checksum from beq.b _OK ;data with expected value bra _Checksum _OK moveq #IERR_OK,d0 rts _Checksum moveq #IERR_CHECKSUM,d0 rts ``` And that is basically it! RawDIC now just needs to know which tracks to decode on the disk by setting up a tracklist: ``` TL_1 TLENTRY 000,000, 1600,SYNC_STD,DMFM_STD TLENTRY 001,159, 1800, a245,_RipTrack TLEND EVEN ``` This means for the first tracklist entry we are reading from track 000 to 000, storing 1600 bytes/track, using a standard sync ( 4489) and using the built in standard decoder. For tracks 001 to 159, we are storing 1800 bytes/track and the sync pattern is a245. Everytime it decodes a track it calls the routine _RipTrack. Here is the RawDIC imager source so far: ``` ; Strider Imager by Codetapper/Action! incdir include: include RawDIC.i OUTPUT “Strider.islave” ;===================================================================== SLAVE_HEADER dc.b 1 ;Slave version dc.b 0 ;Slave flags dc.l DSK_1 ;Pointer to the first disk structure dc.l Text ;Pointer to the text displayed in the imager window dc.b ” VER:” Text dc.b “Strider imager V1.0”,10 dc.b “by Codetapper/Action (20.06.2002)” dc.b 0 cnop 0,4 ;===================================================================== DSK_1 dc.l 0 ;Pointer to next disk structure dc.w 1 ;Disk structure version dc.w DFLG_NORESTRICTIONS ;Disk flags dc.l TL_1 ;List of tracks which contain data dc.l 0 ;UNUSED, ALWAYS SET TO 0! dc.l FL_DISKIMAGE ;List of files to be saved dc.l 0 ;Table of certain tracks with CRC values dc.l 0 ;Alternative disk structure, if CRC failed dc.l 0 ;Called before a disk is read dc.l 0 ;Called after a disk has been read TL_1 TLENTRY 000,000, 1600,SYNC_STD,DMFM_STD TLENTRY 001,159, 1800, a245,_RipTrack TLEND EVEN ;===================================================================== _RipTrack lea (2,a0),a3 ;lea ( 7C002).l,a3 movea.l a1,a4 ;movea.l a5,a4 move.l # 55555555,d3 ;MFM Mask move.w (a3)+,d2 ;Merge 2 MFM words of move.w (a3)+,d1 ;data into one word and.w d3,d2 and.w d3,d1 add.w d2,d2 or.w d2,d1 cmp.w d0,d1 ;Check track number is bne _NoSector ;what we expected moveq #0,d4 move.w # 5FF,d6 ;Decode 600-1 longs _DecodeLoop move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits add.l d1,d4 ;Add to checksum move.l d1,(a4)+ ;Store decoded longword dbra d6,_DecodeLoop ;Decode rest of track move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits not.l d4 cmp.l d4,d1 ;Compare checksum from beq.b _OK ;data with expected value bra _Checksum ;===================================================================== _OK moveq #IERR_OK,d0 rts _Checksum moveq #IERR_CHECKSUM,d0 rts _NoSector moveq #IERR_NOSECTOR,d0 rts ``` Now to test it! Assemble the code and call RawDIC with the name of the slave as the parameter: ``` 1:> RawDIC SLAVE=Strider.slave ``` If you have the original Strider disk (or have copied the game back onto a disk) you can just insert the disk and let RawDIC run. If you don’t have the original disk, it used to be a problem! However you can now use MFMWarp or WWarp files and “insert” these into RawDIC so instead of reading from the floppy it reads from the files. This is also much quicker than a physical floppy disk! You can also use a CAPS image inserted into the disk drive and run the imager in WinUAE. After letting RawDIC run, it comes up with “sector missing on track 154”. This is most likely because the game only has data on tracks 1-153, with 154 onwards being unformatted or some kind of copy protection. Just skip the track and let RawDIC continue, noting down all tracks that have errors. In Striders case, tracks 154-159 all say “sector missing”. For now we have no idea what format these other tracks are so let’s change the tracklist so we don’t bother imaging them: ``` TL_1 TLENTRY 000,000, 1600,SYNC_STD,DMFM_STD TLENTRY 001,153, 1800, a245,_RipTrack TLEND EVEN ``` Now RawDIC will image track 0 as an AmigaDOS track, and all other tracks as the format used on Strider. There is just one other change to make now to make the install more elegant. Strider has 1800 bytes/track, but for track 0 we are imaging it as a standard AmigaDOS track with only 1600 bytes. It is nicer to pad this track out so it is also 1800 bytes. That way we can work out all offsets just by dividing by 1800 rather than having to take into account track 0. There are other techniques you could do this but this is the easiest for a WHDLoad install: ``` TL_1 TLENTRY 000,000, 1800,SYNC_STD,DMFM_STD TLENTRY 001,153, 1800, a245,_RipTrack TLEND EVEN ``` Run the imager again. It completes successfully and you have a file called Disk.1 which is 946176 bytes in length. Have a quick look at the file and notice that at f700 is a whole bunch of text from the game. This is a quick way to tell you have ripped it correctly! I’ve added a couple of comments to the top and the completed RawDIC imager source is now: ``` ; Strider Imager by Codetapper/Action! ; ; Tracks 000-000: Dos ; Tracks 001-153: MFM (Sync a245, 1800 bytes) incdir include: include RawDIC.i OUTPUT “Strider.islave” ;===================================================================== SLAVE_HEADER dc.b 1 ;Slave version dc.b 0 ;Slave flags dc.l DSK_1 ;Pointer to the first disk structure dc.l Text ;Pointer to the text displayed in the imager window dc.b ” VER:” Text dc.b “Strider imager V1.0”,10 dc.b “by Codetapper/Action (20.06.2002)” dc.b 0 cnop 0,4 ;===================================================================== DSK_1 dc.l 0 ;Pointer to next disk structure dc.w 1 ;Disk structure version dc.w DFLG_NORESTRICTIONS ;Disk flags dc.l TL_1 ;List of tracks which contain data dc.l 0 ;UNUSED, ALWAYS SET TO 0! dc.l FL_DISKIMAGE ;List of files to be saved dc.l 0 ;Table of certain tracks with CRC values dc.l 0 ;Alternative disk structure, if CRC failed dc.l 0 ;Called before a disk is read dc.l 0 ;Called after a disk has been read TL_1 TLENTRY 000,000, 1800,SYNC_STD,DMFM_STD TLENTRY 001,153, 1800, a245,_RipTrack TLEND EVEN ;===================================================================== _RipTrack lea (2,a0),a3 ;lea ( 7C002).l,a3 movea.l a1,a4 ;movea.l a5,a4 move.l # 55555555,d3 ;MFM Mask move.w (a3)+,d2 ;Merge 2 MFM words of move.w (a3)+,d1 ;data into one word and.w d3,d2 and.w d3,d1 add.w d2,d2 or.w d2,d1 cmp.w d0,d1 ;Check track number is bne _NoSector ;what we expected moveq #0,d4 move.w # 5FF,d6 ;Decode 600-1 longs _DecodeLoop move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits add.l d1,d4 ;Add to checksum move.l d1,(a4)+ ;Store decoded longword dbra d6,_DecodeLoop ;Decode rest of track move.l ( 1804,a3),d1 ;Read even longword and.l d3,d1 ;Mask out clock bits move.l (a3)+,d2 ;Read odd longword and.l d3,d2 ;Mask out clock bits add.l d2,d2 ;Rotate odd bits left or.l d2,d1 ;Merge odd and even bits not.l d4 cmp.l d4,d1 ;Compare checksum from beq.b _OK ;data with expected value bra _Checksum ;===================================================================== _OK moveq #IERR_OK,d0 rts _Checksum moveq #IERR_CHECKSUM,d0 rts _NoSector moveq #IERR_NOSECTOR,d0 rts ``` ---------------------------------------------------------------------------- -- rapid1337.com -- The dumping ground for all things tech // knowledge is the weapon --