----------------Dig Dug---------------- A 4am crack 2015-06-15 --------------------------------------- Name: Dig Dug Genre: arcade Year: 1984 Publisher: Datasoft, Inc. Media: single-sided 5.25-inch floppy OS: custom Other versions: none (There were other licensed versions of the same game that differed only by the title screen and copy protection. As far as I can tell, Datasoft's version was never preserved because the other versions were easier to reduce to a single file.) Similar cracks: Pac-Man (Datasoft) (crack no. 257) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) works Copy ][+ nibble editor T00 -> modified address epilogue (CC AA EB) T01-T0C -> obviously data, but no visible structure, no address or data fields T0D+ -> unformatted Disk Fixer ["O" -> "Input/Output Control"] set Address Epilogue to "CC AA EB" T00 readable -> custom bootloader everything else still a black box Why didn't COPYA work? not a standard 16-sector disk Why didn't Locksmith FDB work? ditto The original disk switches to an uninitialized hi-res graphics screen immediately on boot, then gradually displays the title screen as it loads from disk. The game is a single-load; it doesn't access the disk once it starts. I can probably extract the entire thing from memory and save it. Next steps: 1. Trace the boot until the entire game is in memory 2. Save it (in chunks, if necessary) 3. Write it out to a standard disk with a fastloader to reproduce the original disk's boot experience ~ Chapter 1 Everyone Boots [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 As expected, my AUTOTRACE script didn't get very far, since this disk uses a custom bootloader. ]BLOAD BOOT0,A$800 ]CALL -151 *801L 0801- 74 ??? 0802- 85 B0 STA $B0 0804- 58 CLI 0805- 6A ROR 0806- EA NOP 0807- 73 ??? 0808- 4B ??? 0809- CD 53 CA CMP $CA53 080C- C8 INY 080D- 42 ??? 080E- 1A ??? 080F- 3D 63 CA AND $CA63,X Um, there must be some mistake. This is not executable code. I mean, it's just not. Yet, address $0800 contains $01, so this is the only sector the disk controller ROM reads into memory before passing control. I have no idea how this disk boots. . . [time passes] . According to , $74 is an undocumented 6502 opcode that takes a single byte argument and does nothing. Like a double NOP, but with two bytes instead of one. According to , $74 is relatively obscure variant of the STZ (STore Zero) instruction, which was introduced in the 65C02. This form of the STZ instruction takes a one byte operand, a zero page memory location. The disassembler built into the Apple monitor assumes all unknown opcodes are single-byte, so it misrepresents opcode $74 as a single-byte instruction and incorrectly prints a three-byte JMP instruction on the next line. When the 65c02 made some of those opcodes valid instructions, the monitor disassembler was never updated with information on their mnemonics or arguments, so it has the same problem. Opcode $64 does nothing of consequence on either CPU, but more importantly, it does nothing in 2 bytes instead of 1. This is the actual code: 0801- 74 85 DOP $85,X ; NOPx2 0803- B0 58 BCS $085D The carry bit is always set coming out of the disk controller routine, so this is an unconditional jump. Everyone boots. ~ Chapter 2 Too Fast, Too Furious *85DL 085D- A9 CA LDA #$CA 085F- 8D 9D 08 STA $089D 0862- B0 31 BCS $0895 Another unconditional jump (since the carry is still set). *895L ; X register has the slot number (x16) 0895- 8A TXA 0896- 48 PHA ; decrypt part of boot0 into text page 0897- A0 98 LDY #$98 0899- B9 00 08 LDA $0800,Y 089C- 49 AA EOR #$AA 089E- 99 00 07 STA $0700,Y 08A1- 88 DEY 08A2- D0 F5 BNE $0899 ; slot number (x16) 08A4- 68 PLA 08A5- 4A LSR 08A6- 4A LSR 08A7- 4A LSR 08A8- 4A LSR ; Y register is 0 at this point (after ; decryption loop), so this is just ; triggering write access on RAM bank 2 08A9- 99 81 C0 STA $C081,Y ; munge slot number into $Cx byte 08AC- 09 C0 ORA #$C0 08AE- 85 3F STA $3F 08B0- 8D 94 07 STA $0794 ; continue with (decrypted) bootloader 08B3- 4C 05 07 JMP $0705 I'll reproduce the decryption loop and save it somewhere other than the text page. *8A0:27 *8A4:60 *897L 0897- A0 98 LDY #$98 0899- B9 00 08 LDA $0800,Y 089C- 49 AA EOR #$AA 089E- 99 00 27 STA $2700,Y 08A1- 88 DEY 08A2- D0 F5 BNE $0899 08A4- 60 RTS *897G *2705L 2705- C0 40 CPY #$40 2707- D9 E1 67 CMP $67E1,Y 270A- F9 60 62 SBC $6260,Y 270D- E8 INX 270E- B0 97 BCS $26A7 2710- C9 60 CMP #$60 2712- F9 E1 A0 SBC $A0E1,Y 2715- ED 92 63 SBC $6392 Well, that's technically real code, but I get the feeling it's not the code I was looking for. Let's back up. . . [time passes] . Oh look, here's the problem: *85DL 085D- A9 CA LDA #$CA 085F- 8D 9D 08 STA $089D That's changing the decryption loop, specifically the XOR key. And I blew right past it. *89D:CA *897G *2705L 2705- A0 20 LDY #$20 2707- B9 81 07 LDA $0781,Y 270A- 99 00 02 STA $0200,Y 270D- 88 DEY 270E- D0 F7 BNE $2707 Look at that. Real code after all. *2781L 2781- A9 00 LDA #$00 2783- AA TAX 2784- A0 BC LDY #$BC 2786- 9D 00 04 STA $0400,X 2789- CA DEX 278A- D0 FA BNE $2786 278C- EE 07 02 INC $0207 278F- 88 DEY 2790- D0 F4 BNE $2786 2792- 4C 00 C6 JMP $C600 That would be "The Badlands", a.k.a. the point of no return. It clears most of main memory and reboots slot 6. It gets copied to $0200. Don't end up in The Badlands. *2710L 2710- A9 00 LDA #$00 ; finish triggering the switch to RAM ; bank 2 (again, Y is 0 by this point) 2712- 99 81 C0 STA $C081,Y ; set the reset vector in page 3 and ; the lower-level reset vector at $FFFC ; to point to The Badlands at $0200 2715- 8D F2 03 STA $03F2 2718- 8D FC FF STA $FFFC 271B- A9 02 LDA #$02 271D- 8D F3 03 STA $03F3 2720- 8D FD FF STA $FFFD 2723- A9 A7 LDA #$A7 2725- 8D F4 03 STA $03F4 ; zap encrypted boot0 (at $0800) 2728- A9 40 LDA #$40 272A- A0 00 LDY #$00 272C- 99 00 08 STA $0800,Y 272F- 88 DEY 2730- D0 FA BNE $272C ; switch to reading RAM bank 2 2732- 99 80 C0 STA $C080,Y ; finish setting up vector to $Cx5C (to ; reuse the disk controller ROM routine ; to read more sectors) 2735- A9 5C LDA #$5C 2737- C8 INY 2738- 85 3E STA $3E ; $01 in $0800 273A- 8C 00 08 STY $0800 ; looks like a multi-sector read loop 273D- AC 80 07 LDY $0780 2740- F0 22 BEQ $2764 2742- A9 07 LDA #$07 2744- 48 PHA 2745- B9 6F 07 LDA $076F,Y 2748- 85 3D STA $3D 274A- CE 80 07 DEC $0780 274D- AD 7F 07 LDA $077F 2750- 85 27 STA $27 2752- A9 3D LDA #$3D 2754- 48 PHA 2755- CE 7F 07 DEC $077F 2758- 98 TYA 2759- 48 PHA 275A- 6C 3E 00 JMP ($003E) OK, this is fascinating. ($3E) points to the start of the "read sector" routine in the disk controller ROM ($C65C if we booted from slot 6). That routine reads the physical sector given in $3D into the page given in $27. When it's done, it jumps to $0801. This is not anything extraordinary; DOS 3.3 works the same way. What's different here is that $0801 isn't connected to the read loop (as it is in DOS 3.3). We flooded page 8 with $40, which is the rarely used "RTI" instruction. (I had to look that up.) What does "RTI" do? It pulls one byte off the stack and sets the processor flags (like PLP). Then it pulls an address off the stack and jumps to it. But unlike RTS, the address on the stack is the actual address, not the actual address -1. The PHA at $0744 puts $07 on the stack. The PHA at $0754 puts $3D on the stack. The PHA at $0759 puts $00 on the stack. Then we just to ($3E), which is $C65C, which reads a sector and jumps to $801, which is an RTI, which pops the stack and sets all the processor flags to 0, then pops the stack two more times and jumps to $073D. So it's a loop. $780 is the sector count. $77F is the highest page (decremented after each sector read). *277F.2780 277F- 05 2780- 02 So this reads two sectors into $0400.. $05FF and continues at $0764 (by the BEQ at $0740). *2764L 2764- EE 7F 07 INC $077F 2767- EE 7F 07 INC $077F 276A- A6 2B LDX $2B 276C- 4C 84 04 JMP $0484 The next stage of the boot begins at $0484. ~ Chapter 3 Who Needs Sectors Anyway? Let's capture the code that ends up at $0400. I'm betting it's an RWTS of some sort, and that it loads the rest of the disk. *9600 To test my assumption that the game never touches the original disk once it's in memory, I removed the disk from the drive and did *1EFFG and the game started right up. After re-running that trace a few times and carefully relocating chunks of memory, and I have the entire game in two files on my work disk: ]CATALOG,S5,D1 . . . B 067 DD.OBJ 1EFF-5FFF B 045 DD.OBJ 6000-8AFE (The disk loads exactly $6C00 bytes -- $09 tracks of $0C pages.) ~ Chapter 5 If You Wish To Play A Game, You Must First Create The Universe To reproduce the original disk's boot experience as faithfully as possible, I decided against releasing this as a file crack. The original disk displays the graphical title screen during boot. In fact, it *only* displays it during boot, then never again. [S6,D1=blank formatted disk] [S5,D1=my work disk] ]PR#5 ]CALL -151 ; wipe memory *800:0 N 801<800.BEFEM ; load the game chunks *BLOAD DD.OBJ 1EFF-5FFF,A$1EFF *BLOAD PM.OBJ 6000-8AFE,A$6000 Now a small MAKE program to write the game to disk so my bootloader can load it as quickly as possible. *300L ; page count (decremented) 0300- A9 70 LDA #$70 0302- 85 FF STA $FF ; logical sector (incremented) 0304- A9 00 LDA #$00 0306- 85 FE STA $FE ; call RWTS to write sector 0308- A9 03 LDA #$03 030A- A0 88 LDY #$88 030C- 20 D9 03 JSR $03D9 ; increment logical sector, wrap around ; from $0F to $00 and increment track 030F- E6 FE INC $FE 0311- A4 FE LDY $FE 0313- C0 10 CPY #$10 0315- D0 07 BNE $031E 0317- A0 00 LDY #$00 0319- 84 FE STY $FE 031B- EE 8C 03 INC $038C ; Convert logical to physical sector. ; 4boot reads tracks in physical sector ; order. 031E- B9 40 03 LDA $0340,Y 0321- 8D 8D 03 STA $038D ; increment page to write 0324- EE 91 03 INC $0391 ; loop until done with all $90 pages 0327- C6 FF DEC $FF 0329- D0 DD BNE $0308 032B- 60 RTS ; logical to physical sector mapping *340.34F 0340- 00 07 0E 06 0D 05 0C 04 0348- 0B 03 0A 02 09 01 08 0F ; RWTS parameter table, pre-initialized ; with slot 6, drive 1, track $01, ; sector $00, address $1EFF, and RWTS ; write command ($02) *388.397 0388- 01 60 01 00 01 00 FB F7 0390- FF 1E 00 00 02 00 00 60 *BSAVE MAKE,A$300,L$98 *300G Now I have the entire game on tracks $01-$07 of a standard format disk. The bootloader (which I've named 4boot) lives on track $00. T00,S00 is boot0, which reuses the disk controller ROM routine to load boot1, which lives on sectors $0C-$0E and is loaded into $0900..$0B00. Boot0 looks like this: ; decrement sector count 0801- CE 12 08 DEC $0812 ; branch once we've read enough sectors 0804- 30 0B BMI $0811 ; increment physical sector to read 0806- E6 3D INC $3D ; $0880 is a sparse table of $C1..$C6, ; so this sets up the proper jump to ; the disk controller ROM based on the ; slot number 0808- BD 80 08 LDA $0880,X 080B- 8D 10 08 STA $0810 ; read a sector (exits via $0801) 080E- 4C 5C 00 JMP $005C ; sector read loop exits to here (from ; $0804) -- note: by the time execution ; reaches here, $0812 is $FF, so this ; just resets the stack 0811- A2 03 LDX #$03 0813- 9A TXS ; set up zero page (used by RWTS) and ; push an array of addresses to the ; stack at the same time 0814- A2 0F LDX #$0F 0816- BD 80 08 LDA $0880,X 0819- 95 F0 STA $F0,X 081B- 48 PHA 081C- CA DEX 081D- D0 F7 BNE $0816 081F- 60 RTS *881.88F 0880- 88 FE 92 FE 3E 08 FF 0888- 08 FE 1E 1E 07 00 FF 00 These are pushed to the stack in reverse order, starting with $088F. When we hit the "RTS" at $081F, it pops the stack and jumps to $FE89, $FE93, $083F, $0900, and $1EFF. (Each of these routines exits via RTS and "returns" to the next address (+1) that we pushed on the stack.) - $FE89, and $FE93 are in ROM - $083F switches to the hi-res screen (uninitialized, like the original). - $0900 is the RWTS entry point. It reads tracks $01..$07 into $1EFF.. $8EFE. (These values are stored in zero page, which we just set.) - $1EFF is the game's entry point. It never returns, so the other values on the stack are irrelevant. The RWTS at $0900 is derived from the ProDOS RWTS. It uses in-place nibble decoding to avoid extra memory copying, and it uses "scatter reads" to read whatever sector is under the drive head when it's ready to load something. *900L ; set up some places later in the RWTS ; where we need to read from a slot- ; specific data latch 0900- A6 2B LDX $2B 0902- 8A TXA 0903- 09 8C ORA #$8C 0905- 8D 96 09 STA $0996 0908- 8D AD 09 STA $09AD 090B- 8D C3 09 STA $09C3 090E- 8D D7 09 STA $09D7 0911- 8D EC 09 STA $09EC ; advance drive head to next track 0914- 20 53 0A JSR $0A53 ; sectors-left-to-read-on-this-track ; counter 0917- A0 0F LDY #$0F 0919- 84 F8 STY $F8 ; Initialize array at $0100 that tracks ; which sectors we've read from the ; current track. The array is in ; physical sector order, thus the RWTS ; assumes data is stored in physical ; sector order on each track. Values ; are the actual pages in memory where ; that sector should go, and they get ; zeroed once the sector is read. 091B- 98 TYA 091C- 18 CLC 091D- 65 FB ADC $FB 091F- 99 00 01 STA $0100,Y 0922- 88 DEY 0923- 10 F6 BPL $091B ; find the next address prologue and ; store the address field in $2C..$2F, ; like DOS 3.3 0925- 20 0F 0A JSR $0A0F ; check if this sector has been read 0928- A4 2D LDY $2D 092A- B9 00 01 LDA $0100,Y ; if 0, we've read this sector already, ; so loop back and look for another 092D- F0 F6 BEQ $0925 ; if not 0, use the target page and set ; up some STA instructions in the RWTS ; so we write this sector directly to ; its intended page in memory 092F- A8 TAY 0930- 84 FF STY $FF 0932- 8C EA 09 STY $09EA 0935- A5 FE LDA $FE 0937- 8D E9 09 STA $09E9 093A- 38 SEC 093B- E9 54 SBC #$54 093D- 8D D1 09 STA $09D1 0940- B0 02 BCS $0944 0942- 88 DEY 0943- 38 SEC 0944- 8C D2 09 STY $09D2 0947- E9 57 SBC #$57 0949- 8D AA 09 STA $09AA 094C- B0 01 BCS $094F 094E- 88 DEY 094F- 8C AB 09 STY $09AB ; read the sector into memory 0952- 20 6D 09 JSR $096D ; if that failed, just loop back and ; look for another sector 0955- B0 CE BCS $0925 ; mark this sector as read 0957- A4 2D LDY $2D 0959- A9 00 LDA #$00 095B- 99 00 01 STA $0100,Y 095E- E6 FB INC $FB ; decrement sectors-left-to-read-on- ; this-track counter 0960- C6 F8 DEC $F8 ; loop until we've read all the sectors ; on this track 0962- 10 C1 BPL $0925 ; decrement tracks-left-to-read counter ; (set in boot0) 0964- C6 FC DEC $FC ; loop until we've read all the tracks 0966- D0 AC BNE $0914 ; turn off drive motor and exit 0968- 09 88 C0 LDA $C088,X 096B- 38 SEC 096C- 60 RTS The combination of - code on consecutive tracks starting on track $01 (minimizes drive head movement) - scatter reads (minimizes disk movement per track) - in-place denibblizing (minimizes memory copies) means you can be playing Dig Dug three seconds after a cold boot. Quod erat liberandum. --------------------------------------- A 4am crack No. 344 ------------------EOF------------------