----------------Mr. Do!---------------- A 4am crack 2015-06-29 -------------------. updated 2015-09-04 |___________________ Name: Mr. Do! Genre: arcade Year: 1985 Authors: Rick Mirsky Publisher: Datasoft, Inc. Media: single-sided 5.25-inch floppy OS: custom Other versions: The Cloak _____________________________ { } { "I don't know anything, but } { I do know that everything } { is interesting if you go } { into it deeply enough." } { } { Richard Feynmann } {_____________________________} ~ 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) read errors on tracks $1C-$22 copy clears screen, turns off drive motor, and freezes Copy ][+ nibble editor T00-T1B appear to be 4-4 encoded data T1C-T22 appear unformatted Disk Fixer ["O" -> "Input/Output Control"] set "CHECKSUM ENABLED" to "NO" T00,S00 readable nothing else readable Why didn't COPYA work? not a 16-sector disk Why didn't Locksmith FDB work? ditto Why didn't my EDD copy work? I don't know, but it wasn't just a structural problem, because the drive motor turned off. That doesn't just happen. Someone turned it off. Next steps: 1. Trace the boot 2. ??? ~ Chapter 1 In Which I Have Just Met You, And I Love You We're starting from bare metal on this one. My automated tools, they do nothing for us. Strap in. [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 ... ]CALL -151 *9600, $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 a 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 $74 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 4C DOP $4C,X ; NOPx2 0803- B0 1C BCS $0821 The carry bit is always set coming out of the disk controller routine, so the branch-on-carry at $0803 functions as an unconditional jump. *821L 0821- 48 PHA 0822- 8D BD 08 STA $08BD 0825- B0 27 BCS $084E The accumulator is always $01 coming out of the disk controller routine. So that gets pushed to the stack and saved in $08BD (odd). And more unconditional jumping, since the carry is still set. *84EL 084E- B0 69 BCS $08B9 *8B9L 08B9- B9 05 08 LDA $0805,Y 08BC- 49 AA EOR #$AA 08BE- 99 00 02 STA $0200,Y 08C1- 88 DEY 08C2- D0 EE BNE $08B2 The Y register is always $00 coming out of the disk controller routine, so this loop will decrypt 256 bytes starting at $0805 and store it at $0200. But wait, this code was modified earlier (at $0822) -- the EOR value was changed from $AA to $01 (at $0822). That means this is the actual code: 08B9- B9 05 08 LDA $0805,Y 08BC- 49 01 EOR #$01 08BE- 99 00 02 STA $0200,Y 08C1- 88 DEY 08C2- D0 EE BNE $08B2 OK. So now we're going to jump to somewhere in the $0200 range, right? 08C4- A9 FF LDA #$FF 08C6- 48 PHA 08C7- A0 03 LDY #$03 08C9- 4C 58 FF JMP $FF58 Well, yes, but in the most roundabout way possible. Remember that we pushed $01 to the stack at $0821. Now we're pushing $FF to the stack as well. So an RTS will "return" to that address + 1, which equals $0200. Then we're jumping to $FF58. What's at $FF58? Just an RTS. The rest of the boot sector is not actually code. It's not even encrypted code. It's a message from the distant past, a simpler age when computers ran at 1 Mhz and floppy disks were the pinnacle of long-term storage: *FC58G N 400<8CC.8FFM DON'T WASTE YOUR TIME, ORIGINALS ARE INEXPENSIVE.JCR Let's waste some time together. ~ Chapter 2 In Which We Decrypt Code And Run It And Run Code Then Decrypt It And Then Run It Again The final JMP $FF68 (at $08C9) provides an easy way to interrupt the boot and capture the decrypted code at $0200. I can change that to jump to a routine under my control and copy page 2 to a page that won't get overwritten by rebooting or, you know, typing. (Page 2 is the input buffer for the monitor and DOS.) *9600 A 2458- 00 00 00 00 01 00 00 ; --> $5E 245F- 06 B9 26 35 35 39 05 ; --> Y 2466- 08 07 80 80 80 07 E0 ; --> X For example, if X=2 on entry at $0200, then the subroutine at $02D8 is called with A=$24, $5E=$00, Y=$26, and X=$80. Now let's see how they're used. *22D8L ; save parameters 22D8- 85 5C STA $5C 22DA- 86 6E STX $6E 22DC- A2 00 LDX #$00 22DE- 86 6D STX $6D 22E0- 84 57 STY $57 22E2- A5 5C LDA $5C ; slot number (x16) 22E4- A6 5D LDX $5D ; turn on drive motor 22E6- DD 8E C0 CMP $C08E,X 22E9- DD 89 C0 CMP $C089,X ; moves drive head to specified phase ; given in accumulator [not shown] 22EC- 20 54 02 JSR $0254 22EF- 4C 00 03 JMP $0300 ... ; look for track prologue ; "CC AA B5 96 DE" 2300- 38 SEC 2301- BD 8C C0 LDA $C08C,X 2304- 10 FB BPL $2301 2306- C9 CC CMP #$CC 2308- D0 F6 BNE $2300 230A- BD 8C C0 LDA $C08C,X 230D- 10 FB BPL $230A 230F- C9 AA CMP #$AA 2311- D0 F3 BNE $2306 2313- BD 8C C0 LDA $C08C,X 2316- 10 FB BPL $2313 2318- C9 B5 CMP #$B5 231A- D0 E4 BNE $2300 231C- BD 8C C0 LDA $C08C,X 231F- 10 FB BPL $231C 2321- C9 96 CMP #$96 2323- D0 DB BNE $2300 2325- BD 8C C0 LDA $C08C,X 2328- 10 FB BPL $2325 232A- C9 DE CMP #$DE 232C- D0 D2 BNE $2300 ; look for sector prologue "AD *", ; where the second value is taken from ; the array at $03C7 232E- A4 5E LDY $5E ; I think zero page $55 ends up with ; the sector number... 2330- A9 0D LDA #$0D 2332- 85 55 STA $55 ; ...because this looks for the ; sector prologue and decrements $55 ; until it finds the right one (so the ; sector prologue is different for ; each sector) 2334- BD 8C C0 LDA $C08C,X 2337- 10 FB BPL $2334 2339- C9 AD CMP #$AD 233B- D0 F7 BNE $2334 233D- C6 55 DEC $55 233F- BD 8C C0 LDA $C08C,X 2342- 10 FB BPL $233F 2344- D9 C7 03 CMP $03C7,Y 2347- D0 EB BNE $2334 2349- F0 15 BEQ $2360 ... ; now read the sector data 2360- A9 75 LDA #$75 2362- 85 56 STA $56 2364- A0 00 LDY #$00 ; sector data is 4-4 encoded 2366- BD 8C C0 LDA $C08C,X 2369- 10 FB BPL $2366 236B- 2A ROL 236C- 85 5F STA $5F 236E- BD 8C C0 LDA $C08C,X 2371- 10 FB BPL $236E 2373- 25 5F AND $5F 2375- 85 54 STA $54 ; sector data is encrypted 2377- 45 56 EOR $56 ; store it in ($6D) (set from X on ; entry, at $02DA) 2379- 91 6D STA ($6D),Y 237B- C8 INY ; do it again (main loop decodes two ; bytes per pass -- maybe timing was ; tight?) 237C- BD 8C C0 LDA $C08C,X 237F- 10 FB BPL $237C 2381- 2A ROL 2382- 85 5F STA $5F 2384- BD 8C C0 LDA $C08C,X 2387- 10 FB BPL $2384 2389- 25 5F AND $5F 238B- 85 56 STA $56 238D- 45 54 EOR $54 238F- 91 6D STA ($6D),Y ; loop until done with this sector 2391- C8 INY 2392- D0 D2 BNE $2366 ; increase target page 2394- E6 6E INC $6E ; checksum byte 2396- BD 8C C0 LDA $C08C,X 2399- 10 FB BPL $2396 239B- 2A ROL 239C- 85 5F STA $5F 239E- BD 8C C0 LDA $C08C,X 23A1- 10 FB BPL $239E 23A3- 25 5F AND $5F 23A5- 45 56 EOR $56 ; if checksum fails, try again 23A7- D0 19 BNE $23C2 ; decrement sector count (set from Y on ; entry, at $02E0) 23A9- C6 57 DEC $57 23AB- F0 11 BEQ $23BE ; increment the index into the array ; at $03C7 that determines the second ; nibble of the sector prologue 23AD- E6 5E INC $5E ; decrement the sector number 23AF- C6 55 DEC $55 ; loop back to read more 23B1- D0 98 BNE $234B ; done with all the sectors on this ; track, so reset the sector number, ; increment the phase by 2 (in $5C, ; set from A on entry at $02D8), and ; jump back to advance the drive head ; and keep reading from the next track 23B3- A9 00 LDA #$00 23B5- 85 5E STA $5E 23B7- E6 5C INC $5C 23B9- E6 5C INC $5C 23BB- 4C E2 02 JMP $02E2 23BE- 18 CLC 23BF- 60 RTS 23C0- C6 5E DEC $5E 23C2- C6 6E DEC $6E 23C4- 4C 00 03 JMP $0300 23C7- [97 9A 9B 9D 9E 9F CB CD] 23CF- [CE CF D3 D6] Here's what I know so far: - data is stored on whole tracks - tracks have a 5-nibble prologue (same on each track) - tracks are split up into sectors (but not 16 -- probably 12 because of the 4-4 encoding) - each sector is 256 bytes - sectors have a 2-nibble prologue (different for each sector on the track, but they follow the same pattern from track to track) - sector data is 4-4 encoded - sector data is encrypted with a rolling XOR - $02D8 is the entry point to read multiple sectors into memory on entry, A = phase (track x2) X = start address (high) Y = sector count $5E = starting sector (usually 0, but is 1 if X=4 on the main entry at $0200) - $0230 is a higher level entry point that takes an index in the X register and looks up how to read a block (using the arrays at $0451, $0458, $045F, and $0466) - there are 7 blocks (X=0-6) - $0200 is the highest level entry point; it functions identically to $0230, but if X=5 then it calls $03D3 first to do a nibble check And now we're ready to look at the one- time initialization routine at $046E. ~ Chapter 6 In Which All Apples Are Equal But Some Are More Equal Than Others *246EL ; save slot number (x16) in zero page 246E- A5 2B LDA $2B 2470- 85 5D STA $5D ; and set up the reboot instruction in ; The Badlands 2472- 4A LSR 2473- 4A LSR 2474- 4A LSR 2475- 4A LSR 2476- 09 C0 ORA #$C0 2478- 8D 1B 02 STA $021B ; NOP out the jump that called this ; routine (at $021C) -- so this entire ; routine will only ever be called once ; (specifically, the first time that ; $0200 is called with any value of X) 247B- A9 EA LDA #$EA 247D- 8D 1C 02 STA $021C 2480- 8D 1D 02 STA $021D 2483- 8D 1E 02 STA $021E ; check if language card is available 2486- 2C 83 C0 BIT $C083 2489- 2C 83 C0 BIT $C083 248C- A9 00 LDA #$00 248E- 85 CC STA $CC 2490- A9 82 LDA #$82 2492- 8D 00 E0 STA $E000 2495- CD 00 E0 CMP $E000 2498- D0 07 BNE $24A1 ; language card is available -- put $82 ; in zero page $CC 249A- 85 CC STA $CC ; and call this (more on this later) 249C- A2 06 LDX #$06 249E- 20 33 02 JSR $0233 ; execution continues here regardless ; of whether language card is available ; push $0B/$FF on the stack (perhaps so ; we can "return" to $0C00? there's ; nothing there yet, though) 24A1- A9 0B LDA #$0B 24A3- 48 PHA 24A4- A9 FF LDA #$FF 24A6- 48 PHA ; check for //e or later 24A7- AD B3 FB LDA $FBB3 24AA- C9 06 CMP #$06 24AC- F0 05 BEQ $24B3 ; no //e, continue at $0233 with X=0 24AE- A2 00 LDX #$00 24B0- 4C 33 02 JMP $0233 ; found //e or later, so re-enable the ; language card and copy some code ; there 24B3- 2C 83 C0 BIT $C083 24B6- 2C 83 C0 BIT $C083 24B9- A2 00 LDX #$00 24BB- BD CD 04 LDA $04CD,X 24BE- 9D 00 D0 STA $D000,X 24C1- BD CD 05 LDA $05CD,X 24C4- 9D 00 D1 STA $D100,X 24C7- CA DEX 24C8- D0 F1 BNE $24BB ; and continue with the code we just ; copied 24CA- 4C 00 D0 JMP $D000 I want to see this code in place, so here's a little program to help. ; write to RAM bank 2 0300- AD 81 C0 LDA $C081 0303- AD 81 C0 LDA $C081 ; copy everything (so monitor routines ; will still work) 0306- A2 30 LDX #$30 0308- A0 00 LDY #$00 030A- B9 00 D0 LDA $D000,Y 030D- 99 00 D0 STA $D000,Y 0310- C8 INY 0311- D0 F7 BNE $030A 0313- EE 0C 03 INC $030C 0316- EE 0F 03 INC $030F 0319- CA DEX 031A- D0 EE BNE $030A ; reproduce the copy loop at $04B9 031C- B9 CD 24 LDA $24CD,Y 031F- 99 00 D0 STA $D000,Y 0322- B9 CD 25 LDA $25CD,Y 0325- 99 00 D1 STA $D100,Y 0328- C8 INY 0329- D0 F1 BNE $031C 032B- 60 RTS *300G Now I can interactively switch over to full read/write access of the language card, and the monitor won't crash. Hooray! *C083 C083 *D000L ; check for auxiliary memory (128K) D000- 8D 05 C0 STA $C005 D003- A9 09 LDA #$09 D005- 8D 00 80 STA $8000 D008- A9 23 LDA #$23 D00A- 8D 01 80 STA $8001 D00D- 0E 00 84 ASL $8400 D010- 0E 01 84 ASL $8401 D013- 8D 04 C0 STA $C004 D016- 8D 03 C0 STA $C003 D019- AD 00 80 LDA $8000 D01C- C9 09 CMP #$09 D01E- D0 0A BNE $D02A D020- AD 01 80 LDA $8001 D023- C9 23 CMP #$23 D025- 8D 02 C0 STA $C002 D028- F0 06 BEQ $D030 D02A- 8D 02 C0 STA $C002 ; no usable auxiliary memory, jump back ; to the routine in main memory which ; called $0200 with X=0 D02D- 4C AE 04 JMP $04AE ; execution continues here (from $D028) ; we found 128K, and now we're going to ; use it D030- A0 02 LDY #$02 D032- 8C 6D 04 STY $046D ; get X from an array at $D0C9 D035- BE C9 D0 LDX $D0C9,Y Here is the array of $D0C9: D0C9- [02 03 04] ; set up a call to the blockread ; routine at $02D8 D038- BD 58 04 LDA $0458,X D03B- 85 5E STA $5E D03D- BC 5F 04 LDY $045F,X D040- BD 51 04 LDA $0451,X ; but always load into $7000 D043- A2 70 LDX #$70 D045- 8E 61 D0 STX $D061 ; read from disk D048- 20 D8 02 JSR $02D8 So we get a block index from the array at $D0C9, then we do a modified disk read to store that block at $7000. D04B- AC 6D 04 LDY $046D ; get block index again D04E- BE C9 D0 LDX $D0C9,Y ; set up a memory copy based on another ; array at $D0C3 D051- BD C3 D0 LDA $D0C3,X D054- 8D 64 D0 STA $D064 ; this is the sector count from the ; blockread we just called D057- BC 5F 04 LDY $045F,X D05A- A2 00 LDX #$00 ; write to auxiliary memory D05C- 8D 05 C0 STA $C005 ; memory copy loop D05F- BD 00 70 LDA $7000,X D062- 9D 00 70 STA $7000,X D065- CA DEX D066- D0 F7 BNE $D05F D068- EE 61 D0 INC $D061 D06B- EE 64 D0 INC $D064 D06E- 88 DEY D06F- D0 EE BNE $D05F D071- AC 6D 04 LDY $046D ; write to main memory again D074- 8D 04 C0 STA $C004 ; do it for all the sectors we read D077- 88 DEY D078- 10 B8 BPL $D032 D07A- A6 5D LDX $5D ; move drive head back to track 0 D07C- A9 00 LDA #$00 D07E- 20 54 02 JSR $0254 ; change the BIT instruction at $0230 ; to a JMP, so it jumps to $D08E D081- A9 4C LDA #$4C D083- 8D 30 02 STA $0230 ; turn off drive motor D086- 9D 88 C0 STA $C088,X ; call the RWTS with X=0 D089- A2 00 LDX #$00 D08B- 4C 33 02 JMP $0233 Here are the arrays: D0C3- [FF FF 02 28 5D FF] ;target addr ;in auxmem D0CC- [00 00 26 35 35 00] ;page count D0D2- [FF FF 80 80 80 FF] ;target addr ;in main mem This is a preloader. It has an array of block indexes (at $D0C9 -- so blocks 2, 3, and 4) that it loads from disk and stores in auxiliary memory. Then it modifies the RWTS entry point at $0230 to jump to $D08E instead. I'm guessing that routine will check if the block is cached and copy it back from auxiliary memory if it can. *D08EL ; read/write access to language card D08E- AD 83 C0 LDA $C083 D091- AD 83 C0 LDA $C083 ; check if block (given in X) is one of ; the ones we cached D094- BC CC D0 LDY $D0CC,X D097- D0 03 BNE $D09C ; not cached, continue with disk read ; as usual D099- 4C 33 02 JMP $0233 ; yes it's cached ; set up the copy from its address in ; auxiliary memory to its address in ; main memory D09C- BD D2 D0 LDA $D0D2,X D09F- 8D B2 D0 STA $D0B2 D0A2- BD C3 D0 LDA $D0C3,X D0A5- 8D AF D0 STA $D0AF D0A8- A2 00 LDX #$00 ; read from auxiliary memory D0AA- 8D 03 C0 STA $C003 ; copy loop D0AD- BD 00 FF LDA $FF00,X D0B0- 9D 00 FF STA $FF00,X D0B3- CA DEX D0B4- D0 F7 BNE $D0AD D0B6- EE AF D0 INC $D0AF D0B9- EE B2 D0 INC $D0B2 D0BC- 88 DEY D0BD- D0 EE BNE $D0AD ; read from main memory again D0BF- 8D 02 C0 STA $C002 D0C2- 60 RTS Fun fact: one nice side effect of putting this code in the language card is that it's unaffected by the $C002/ $C003/$C004/ $C005 switches to read and write from main or auxiliary memory. You can even have self-modifying copy loops without having to switch back and forth between main and auxiliary memory inside the loop. And that really was a fun fact. ~ Chapter 7 In Which We Experiment With Half-Measures I've traced the entire boot process, and it's insane. Here's the flow of execution: 1. $0800 ---decrypts---> $0200 2. $0200 ---reads disk---> $0700 3. $0700 ---decrypts--> $0700 4. 5. $0700 ---decrypts---> $0700 (again) 6. $0700 ---reads disk---> $0200..$05FF 7. $046E ---copies---> $D000..$D1FF 8. $D000 ---reads disk---> aux mem 9. $0C00 starts the game Of the first six steps, very little is actually useful. Clearing the graphics screen, copying ROM to RAM, and reading the RWTS into $0200. That's it. I could probably fit all of that into a single sector and still have room for a hidden message about how inexpensive various things are. Let's do that. Starting with my failed EDD bit copy, I can replace T00,S00 with just the useful parts of the original boot: ; clear hi-res screen and show it ; (originally at $0239 during boot) 0801- A2 20 LDX #$20 0803- A9 00 LDA #$00 0805- A8 TAY 0806- 99 00 20 STA $2000,Y 0809- 88 DEY 080A- D0 FA BNE $0806 080C- EE 08 08 INC $0808 080F- CA DEX 0810- D0 F4 BNE $0806 0812- 2C 81 C0 BIT $C081 0815- 2C 50 C0 BIT $C050 0818- 2C 81 C0 BIT $C081 081B- 2C 57 C0 BIT $C057 081E- 2C 52 C0 BIT $C052 ; copy ROM to language card ; (originally at $025D during boot) 0821- A2 30 LDX #$30 0823- B9 00 D0 LDA $D000,Y 0826- 99 00 D0 STA $D000,Y 0829- 88 DEY 082A- D0 F7 BNE $0823 082C- EE 25 08 INC $0825 082F- EE 28 08 INC $0828 0832- CA DEX 0833- D0 EE BNE $0823 0835- 2C 80 C0 BIT $C080 ; read RWTS into $0200..$05FF ; (originally at $0785 during boot) 0838- 84 FC STY $FC 083A- A9 02 LDA #$02 083C- 85 FD STA $FD 083E- 0A ASL 083F- 85 FB STA $FB 0841- A6 2B LDX $2B 0843- BD 8C C0 LDA $C08C,X 0846- 10 FB BPL $0843 0848- C9 D5 CMP #$D5 084A- D0 F7 BNE $0843 084C- BD 8C C0 LDA $C08C,X 084F- 10 FB BPL $084C 0851- C9 BD CMP #$BD 0853- D0 F3 BNE $0848 0855- BD 8C C0 LDA $C08C,X 0858- 10 FB BPL $0855 085A- C9 96 CMP #$96 085C- D0 EA BNE $0848 085E- BD 8C C0 LDA $C08C,X 0861- 10 FB BPL $085E 0863- 2A ROL 0864- 85 FF STA $FF 0866- BD 8C C0 LDA $C08C,X 0869- 10 FB BPL $0866 086B- 25 FF AND $FF 086D- 91 FC STA ($FC),Y 086F- C8 INY 0870- D0 EC BNE $085E 0872- BD 8C C0 LDA $C08C,X 0875- 10 FB BPL $0872 0877- E6 FD INC $FD 0879- C6 FB DEC $FB 087B- D0 E1 BNE $085E ; disable in-RWTS nibble check by ; putting an "RTS" at $03D3 087D- A9 60 LDA #$60 087F- 8D D3 03 STA $03D3 ; start the game 0882- 4C 00 02 JMP $0200 Turning to my trusty Disk Fixer sector editor, I typed all that into T00,S00 and wrote it to disk. ]PR#6 ...works... Call that "Mr. Do (4am crack).nib". I'm not done yet, but this proves that I understand enough about the copy protection to bypass it and enough about the bootloader to replace it. The resulting disk is easily copyable with any bit copier. I also imaged it as a .nib file, which works in modern emulators. ~ Chapter 8 In Which We Examine Our Options Not content with a bit-copyable crack, it is now time to consider my next steps. A straight track-by-track conversion is out of the question; the original uses a 4-4 encoding scheme with custom prologues. I'll need to reconstruct the disk from scratch. I'm reasonably confident that I can use the disk's own RWTS to read each of the blocks (passing X=0-6 into $0200 and capturing the result in memory). Here's the problem: this is not a single-load game. Even with 128K and the built-in preloader, the original still accesses the disk to reload the main menu after you finish a game. (This is block 5.) The preloader caches blocks 2, 3, and 4 in auxiliary memory, but that only leaves $2E00 for block 5, which is $3900. (Block 0 is only loaded once to calibrate the joystick and show the credits. Block 6 is at $E000.) I might be able to split up the block and cache part of it in the language card, assuming that's not used for storage during the game. If I can't fit everything in memory, then I'll need an RWTS. I could dictate that my crack requires 64K and stash a DOS-shaped RWTS in the language card. But several parts of the language card are already in use ($D000 for the preloader and cache routines, and six pages at $E000 that are loaded in block 6). Those may or may not be easy to relocate. Also, I'd need to be careful about which text page addresses are in use, because the DOS 3.3 RWTS uses the "screen holes" in the text page for temporary storage. Another option: leave the RWTS in low memory but rewrite it to support 16- sector tracks. Looking at the blockread arrays, block 1 starts at $0700 and goes all the way up to $BFFF. Further investigation reveals that the game uses pages 5, 6, and most of the stack for data. (This "investigation" consisted of me naively assuming those pages were available and trying to put code there, then cursing profusely when they got overwritten.) So, I would need an RWTS that is relocatable to $0200 and fits in three pages of memory... including temporary storage used during denibblizing. Memory is extremely tight, but I like the last option. It would allow my crack to run on all the same machines as the original game (48K, 64K, 128K), while taking full advantage of each. ~ Chapter 9 In Which We Attempt To Use The Disk As A Weapon Against Itself I need to write a boot tracer that will boot the original disk right up until it jumps to the game, then break into the monitor. But not just that; I need to modify it so it reads every possible block (0-6). Here is the heart of it, inside the one-time initialization routine that starts at $048E: ``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-., ``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-., `` ., `` 04A1- A9 0B LDA #$0B ., `` 04A3- 48 PHA ., `` 04A4- A9 FF LDA #$FF ., `` 04A6- 48 PHA ., `` 04A7- AD B3 FB LDA $FBB3 ., `` 04AA- C9 06 CMP #$06 ., `` 04AC- F0 05 BEQ $04B3 ., `` 04AE- A2 00 LDX #$00 ., `` 04B0- 4C 33 02 JMP $0233 ., `` ., ``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-., ``'-.,_,.-'``'-.,_,.='``'-.,_,.-'``'-., I can control the values pushed on the stack at $04A3 and $04A6, so I can break to the monitor instead of jumping to $0C00 to start the game. And I can control which block is read by changing the value of X (at $04AF) going into the RWTS entry point at $0233. (I'll also need to disable the branch at $04AC so it always falls through to immediately read the block I want.) $04A2 --> $FF (pushed to stack) $0435 --> $58 (pushed to stack) $04AC --> $24 (never branch over $04AE) $04AF --> $00..$06 (block to read) Let's do it. *9600 *C00L 0C00- AD 55 C0 LDA $C055 0C03- AD 52 C0 LDA $C052 0C06- AD 51 C0 LDA $C051 0C09- A2 28 LDX #$28 0C0B- 20 A8 FC JSR $FCA8 0C0E- CA DEX 0C0F- 10 FA BPL $0C0B 0C11- A9 00 LDA #$00 0C13- 85 BF STA $BF 0C15- 85 3E STA $3E 0C17- A9 08 LDA #$08 0C19- 85 41 STA $41 0C1B- A9 00 LDA #$00 0C1D- 85 40 STA $40 *C00G ...shows credits screen, then starts joystick calibration... Hello, Mr. Do. It's a pleasure to finally meet you. ~ Chapter 10 In Which We Try To Come Up With A Pun About "Building Blocks" But Finally Lego I think I finally know enough about the boot process to capture all 7 blocks and save them to my work disk. First, let's reboot and save block 0. ]PR#5 ... ]BRUN TRACE6 ...reboots slot 6... *2800<800.DFF ; block 0 is $06 pages *C500G ... ]BSAVE OBJ0.0800-0DFF,A$2800,L$600 ]CALL -151 *9600 Good news: I have block 1 in memory. Bad news: it's $B900 long, starting at $0700 (text page) and filling all the rest of main memory. So we'll do this in stages. *2800<800.1FFFM *C500G ... ]BSAVE OBJ1.0800-1FFF,A$2800,L$1800 ]BRUN TRACE7 ...reboots slot 6... *C500G ... ]BSAVE OBJ1.2000-5FFF,A$2000,L$4000 ]BRUN TRACE7 ...reboots slot 6... *2000<6000.9FFFM *C500G ... ]BSAVE OBJ1.6000-9FFF,A$2000,L$4000 ]BRUN TRACE7 ...reboots slot 6... *2000 *C500G ... ]BSAVE OBJ1.0700-07FF,A$2700,L$100 ]CALL -151 *9600 *2000<8000.A5FFM *C500G ... ]BSAVE OBJ2.8000-A5FF,A$2000,L$2600 ]CALL -151 *9600 *2000<8000.B4FFM *C500G ... ]BSAVE OBJ3.8000-B4FF,A$2000,L$3500 ]CALL -151 *9600 *2000<8000.B4FFM *C500G ... ]BSAVE OBJ4.8000-B4FF,A$2000,L$3500 ]CALL -151 *9600 *C500G ... ]BSAVE OBJ5.0700-3FFF,A$2700,L$3900 ]CALL -151 *9600 *C500G ... ]BSAVE OBJ6.E000-E4FF,A$2000,L$500 ]CATALOG C1983 DSR^C#254 012 FREE A 002 HELLO *B 003 TRACE0 B 003 BOOT0 *B 003 TRACE1 B 003 BOOT0 0200-02FF *B 003 TRACE2 B 003 BOOT1 0700-07FF *B 003 TRACE3 B 003 BOOT1 0700-07FF DECRYPTED *B 003 TRACE4 B 003 BOOT1 0700-07FF DECRYPTED 2 *B 003 TRACE5 B 006 BOOT1 0200-05FF B 003 TRACE6 B 008 OBJ0.0800-0DFF *B 003 TRACE7 B 026 OBJ1.0800-1FFF B 066 OBJ1.2000-5FFF B 066 OBJ1.6000-9FFF B 034 OBJ1.A000-BFFF *B 003 TRACE8 B 003 OBJ1.0700-07FF *B 003 TRACE9 B 040 OBJ2.8000-A5FF *B 003 TRACE10 B 055 OBJ3.8000-B4FF *B 003 TRACE11 B 055 OBJ4.8000-B4FF *B 003 TRACE12 B 059 OBJ5.0700-3FFF *B 003 TRACE13 B 007 OBJ6.E000-E4FF Whew. ~ Chapter 11 In which Two Wrongs Make A Write Here's the plan: 1. write out each block to a standard 16-sector disk 2. find an RWTS that fits @ $0200-$05FF 3. create a bootloader that loads the RWTS at $0200, loads the preloader at $D000, and reproduces the other useful bits from the original disk 4. mimic the calling convention of the original RWTS at $0200 (X = block index from $00-$06) so I don't need to change any game code Looking at the length of each block and dividing by 16, I can space everything out on separate tracks and still have plenty of room. This means each block can start on its own track, which saves a few bytes by being able to hard-code the starting sector for each block. (Space for the memory-resident RWTS is *extremely* tight.) The disk map will look like this: X | tr | sector | address range ---+----+--------+-------------- 0 | 01 | 0F..0A | $0800..$0DFF ---+----+--------+-------------- 1 | 02 | 0F..00 | $0700..$16FF 1 | 03 | 0F..00 | $1700..$26FF 1 | 04 | 0F..00 | $2700..$36FF 1 | 05 | 0F..00 | $3700..$46FF 1 | 06 | 0F..00 | $4700..$56FF 1 | 07 | 0F..00 | $5700..$66FF 1 | 08 | 0F..00 | $6700..$76FF 1 | 09 | 0F..00 | $7700..$86FF 1 | 0A | 0F..00 | $8700..$96FF 1 | 0B | 0F..00 | $9700..$A6FF 1 | 0C | 0F..00 | $A700..$B6FF 1 | 0D | 0F..07 | $B700..$BFFF ---+----+--------+-------------- 2 | 0E | 0F..00 | $8000..$8FFF 2 | 0F | 0F..00 | $9000..$9FFF 2 | 10 | 0F..0A | $A000..$A5FF ---+----+--------+-------------- | 11 | dummy disk catalog ---+----+--------+-------------- 3 | 12 | 0F..00 | $8000..$8FFF 3 | 13 | 0F..00 | $9000..$9FFF 3 | 14 | 0F..00 | $A000..$AFFF 3 | 15 | 0F..0B | $B000..$B4FF ---+----+--------+-------------- 4 | 16 | 0F..00 | $8000..$8FFF 4 | 17 | 0F..00 | $9000..$9FFF 4 | 18 | 0F..00 | $A000..$AFFF 4 | 19 | 0F..0B | $B000..$B4FF ---+----+--------+-------------- 5 | reuses block 1 ---+----+--------+-------------- 6 | 1A | 0F..0B | $E000..$E4FF ---+----+--------+-------------- | 1B | | 1C | | 1D | | 1E | unused | 1F | | 20 | | 21 | | 22 | ---+----+--------+-------------- Here's a little utility program to help write out each block. ; write a sector 0300- A9 03 LDA #$03 0302- A0 28 LDY #$28 0304- 20 D9 03 JSR $03D9 ; decrement sector, wrap around to $0F 0307- AC 2D 03 LDY $032D 030A- 88 DEY 030B- 10 05 BPL $0312 030D- A0 0F LDY #$0F ; increment track 030F- EE 2C 03 INC $032C 0312- 8C 2D 03 STY $032D ; increment memory page 0315- EE 31 03 INC $0331 ; decrement sector count 0318- CE 21 03 DEC $0321 ; loop until done 031B- D0 E3 BNE $0300 031D- 60 RTS $0321 is the sector count, and the RWTS parameter table starts at $0328. *BSAVE WRITER,A$300,L$40 Write block 0 ($06 sectors) to track 1: *321:06 ; sector count *32C:01 0F ; start track/sector *331:08 ; start address (high) *320.33F 0320- 00 06 00 00 00 00 00 00 ^^ sector count 0328- 01 60 01 00 01 0F 3B 03 ^^ ^^ start track/sector 0330- 00 08 00 00 02 00 FE 60 ^^^^^ ^^ start address write command 0338- 01 00 00 00 01 EF D8 00 *BLOAD OBJ0.0800-0DFF,A$800 *300G Write block 1 (first $02 sectors): *321:02 *32C:02 0F *331:27 *320.33F 0320- 00 02 00 00 00 00 00 00 0328- 01 60 01 00 02 0F 3B 03 0330- 00 27 00 00 02 00 FE 60 0338- 01 00 00 00 01 EF D8 00 *BLOAD OBJ1.0700-07FF,A$2700 *BLOAD OBJ1.0800-1FFF,A$2800 *300G Write block 1 (last $B7 sectors): *321:B7 *32C:02 0D *331:08 *320.33F 0320- 00 B7 00 00 00 00 00 00 0328- 01 60 01 00 02 0D 3B 03 0330- 00 08 00 00 02 00 FE 60 0338- 01 00 00 00 01 EF D8 00 *BLOAD OBJ1.0800-1FFF,A$700 *BLOAD OBJ1.2000-5FFF,A$1F00 *BLOAD OBJ1.6000-9FFF,A$5F00 *BLOAD OBJ1.A000-BFFF,A$9F00 *300G Write block 2 ($26 sectors): *321:26 *32C:0E 0F *331:80 *BLOAD OBJ2.8000-A5FF,A$8000 *300G Write block 3 ($35 sectors): *321:35 *32C:12 0F *331:80 *BLOAD OBJ3.8000-B4FF,A$8000 *300G Write block 4 ($35 sectors): *321:35 *32C:16 0F *331:80 *BLOAD OBJ4.8000-B4FF,A$8000 *300G Block 5 is actually just the first part of block 1, so I don't need to write it to disk. (This is true on the original disk, too -- look closely at the phase array at $0451.) Write block 6 ($05 sectors): *321:05 *32C:1A 0F *331:20 *BLOAD OBJ6.E000-E4FF,A$2000 *300G And that's all she wrote (to disk).(*) (*) sorry(**) (**) not sorry ~ Chapter 12 In Which We Pull Ourselves Up By Our Bootsector Tracks $01-$1A now contain the original game code, but in a standard 16-sector format. Now for the fun part: designing track $00. Of course, sector $00 will be loaded at $0800 by the disk controller ROM. From there, I'll need to load four sectors. I can't load them directly into their final locations, because I need to re- use the disk controller ROM and it uses $0200..$037F as temporary storage. sc | initial | final ----+---------+------- 0E | $0900 | $0200 0D | $0A00 | $0300 0C | $0B00 | $0400 0B | $0C00 | $D000 ; read four sectors into $0900..$0CFF 0801- CE 19 08 DEC $0819 0804- 30 0F BMI $0815 0806- E6 3D INC $3D 0808- 8A TXA 0809- 4A LSR 080A- 4A LSR 080B- 4A LSR 080C- 4A LSR 080D- 09 C0 ORA #$C0 080F- 8D 14 08 STA $0814 0812- 4C 5C 00 JMP $005C ; execution continues here (from $0804) ; after all sectors are read ; store boot slot (x16) in RWTS 0815- 8E 1B 09 STX $091B ; $0819 will be $FF by the time this is ; executed, so this just resets the ; stack 0818- A2 04 LDX #$04 081A- 9A TXS ; clear hi-res screen 081B- E8 INX 081C- A0 20 LDY #$20 081E- 8A TXA 081F- 9D 00 40 STA $4000,X 0822- E8 INX 0823- D0 FA BNE $081F 0825- EE 21 08 INC $0821 0828- 88 DEY 0829- D0 F4 BNE $081F ; ...and show it 082B- 2C 50 C0 BIT $C050 082E- 2C 55 C0 BIT $C055 0831- 2C 57 C0 BIT $C057 0834- 2C 52 C0 BIT $C052 ; copy ROM to language card (original ; does this and relies on it, because ; the languard card remains active ; throughout the game) 0837- 2C 81 C0 BIT $C081 083A- 2C 81 C0 BIT $C081 083D- A0 30 LDY #$30 083F- BD 00 D0 LDA $D000,X 0842- 9D 00 D0 STA $D000,X 0845- E8 INX 0846- D0 F7 BNE $083F 0848- EE 41 08 INC $0841 084B- EE 44 08 INC $0844 084E- 88 DEY 084F- D0 EE BNE $083F ; move RWTS into place ; S0E --> $0200 ; S0D --> $0300 ; S0C --> $0400 0851- BD 00 09 LDA $0900,X 0854- 9D 00 02 STA $0200,X 0857- BD 00 0A LDA $0A00,X 085A- 9D 00 03 STA $0300,X 085D- BD 00 0B LDA $0B00,X 0860- 9D 00 04 STA $0400,X 0863- E8 INX 0864- D0 EB BNE $0851 ; zero page $57 is used by the RWTS to ; hold the last read track 0866- 86 57 STX $57 ; zero page $5B is used by the RWTS as ; the low byte of the target address to ; store data read from disk 0868- 86 5B STX $5B ; this code is taken from the original ; game to determine whether it should ; load the autoplaying demo into $E000 ; (the game will work on 48K machines ; but autoplaying demo requires 64K) 086A- 86 CC STX $CC 086C- 2C 83 C0 BIT $C083 086F- 2C 83 C0 BIT $C083 0872- A9 82 LDA #$82 0874- 8D 00 E0 STA $E000 0877- CD 00 E0 CMP $E000 087A- D0 07 BNE $0883 ; hey, we have at least 64K, so set the ; zero page indicator at $CC and load ; block 6 at $E000 087C- 85 CC STA $CC 087E- A2 06 LDX #$06 0880- 20 00 02 JSR $0200 ; set "return" address via stack since ; this page will be overwritten when we ; read block 0 0883- A9 0B LDA #$0B 0885- 48 PHA 0886- A9 FF LDA #$FF 0888- 48 PHA ; check machine ID byte to determine ; whether we should call the preloader ; at $D000 or just start the game 0889- AD B3 FB LDA $FBB3 088C- C9 06 CMP #$06 088E- F0 05 BEQ $0895 ; Apple II+ --> just start the game 0890- A2 00 LDX #$00 0892- 4C 00 02 JMP $0200 ; //e or later --> set up the preloader 0895- 2C 83 C0 BIT $C083 0898- 2C 83 C0 BIT $C083 089B- A2 00 LDX #$00 089D- BD 00 0C LDA $0C00,X 08A0- 9D 00 D0 STA $D000,X 08A3- E8 INX 08A4- D0 F7 BNE $089D ; ...and continue from there 08A6- 4C 00 D0 JMP $D000 ~ Chapter 13 In Which Smaller Is Better, But Mini Is Best For the RWTS, I chose Mini-RWTS by The Stack. It's only $297 bytes long, plus an additional $56 bytes for temporary storage during denibblizing. It decodes nibbles in place into the target memory page, so it doesn't require a 256-byte buffer like DOS 3.3. It has an easy API for moving the drive head to a track and reading multiple sectors into consecutive memory, which are the two things I need to do. It also comes with a maker program to relocate it to any page. By default, Mini-RWTS uses zero page $F1-$FF, but I had to change that to $51-$5F because those are the only zero page addresses left unused during the game. I also had to strip out some functionality (like the ability to read from drive 2) to make space for the blockread API at $200, but it all fits. Barely. ; if 128K, the initialization routine ; at $D000 will change this to a JMP ; so preloaded resources are copied ; from auxiliary memory 0200- 2C 8E D0 BIT $D08E ; switch to page 2 (it turns out it's ; always safe to do this, not only on ; block 5) 0203- 2C 55 C0 BIT $C055 ; My trials and tribulations getting ; this to fit in $300 bytes left me ; with one byte to spare. Here it is: 0206- EA NOP I thought about using the illegal two- byte NOP $74, in honor of the original disk's boot sector, but I literally couldn't spare the extra byte. Now to set up The Stack's Mini-RWTS. ; $5C is high byte of starting address 0207- BD EB 03 LDA $03EB,X 020A- 85 5C STA $5C ; $5D is sector count 020C- BD F2 03 LDA $03F2,X 020F- 85 5D STA $5D ; $5E is starting track 0211- BD F9 03 LDA $03F9,X 0214- 85 5E STA $5E ; $5F is starting sector (always $0F) 0216- A9 0F LDA #$0F 0218- 85 5F STA $5F ; X is the slot number (x16) ; this was actually set at $0815 to the ; boot slot x16, so my crack will boot ; from any slot 021A- A2 60 LDX #$60 ; Mini-RWTS starts here 021C- A5 5D LDA $5D 021E- D0 01 BNE $0221 0220- 60 RTS ; drive initialization (like DOS 3.3) 0221- BD 8E C0 LDA $C08E,X 0224- BD 8E C0 LDA $C08E,X 0227- A0 08 LDY #$08 0229- BD 8C C0 LDA $C08C,X 022C- 48 PHA 022D- 68 PLA 022E- 48 PHA 022F- 68 PLA 0230- DD 8C C0 CMP $C08C,X 0233- D0 03 BNE $0238 0235- 88 DEY 0236- D0 F1 BNE $0229 0238- 08 PHP 0239- BD 89 C0 LDA $C089,X 023C- A9 D8 LDA #$D8 023E- 85 5A STA $5A 0240- A9 EF LDA #$EF 0242- 85 59 STA $59 0244- 28 PLP 0245- 08 PHP 0246- D0 08 BNE $0250 0248- A0 08 LDY #$08 024A- 20 00 04 JSR $0400 024D- 88 DEY 024E- D0 FA BNE $024A ; move drive head to start track 0250- A5 5E LDA $5E 0252- 20 8A 03 JSR $038A 0255- 28 PLP 0256- D0 11 BNE $0269 0258- A4 5A LDY $5A 025A- 10 0D BPL $0269 025C- A0 12 LDY #$12 025E- 88 DEY 025F- D0 FD BNE $025E 0261- E6 59 INC $59 0263- D0 F7 BNE $025C 0265- E6 5A INC $5A 0267- D0 F3 BNE $025C 0269- A0 30 LDY #$30 026B- 84 52 STY $52 026D- A0 FC LDY #$FC 026F- 84 55 STY $55 0271- C8 INY 0272- D0 04 BNE $0278 0274- E6 55 INC $55 0276- F0 4F BEQ $02C7 ; read address prologue 0278- BD 8C C0 LDA $C08C,X 027B- 10 FB BPL $0278 027D- C9 D5 CMP #$D5 027F- D0 F0 BNE $0271 0281- EA NOP 0282- BD 8C C0 LDA $C08C,X 0285- 10 FB BPL $0282 0287- C9 AA CMP #$AA 0289- D0 F2 BNE $027D 028B- A0 03 LDY #$03 028D- BD 8C C0 LDA $C08C,X 0290- 10 FB BPL $028D 0292- C9 96 CMP #$96 0294- D0 E7 BNE $027D ; read and store address field 0296- A9 00 LDA #$00 0298- 85 58 STA $58 029A- BD 8C C0 LDA $C08C,X 029D- 10 FB BPL $029A 029F- 2A ROL 02A0- 85 53 STA $53 02A2- BD 8C C0 LDA $C08C,X 02A5- 10 FB BPL $02A2 02A7- 25 53 AND $53 02A9- 99 53 00 STA $0053,Y 02AC- 45 58 EOR $58 02AE- 88 DEY 02AF- 10 E7 BPL $0298 02B1- A8 TAY 02B2- D0 13 BNE $02C7 ; read address epilogue 02B4- BD 8C C0 LDA $C08C,X 02B7- 10 FB BPL $02B4 02B9- C9 DE CMP #$DE 02BB- D0 0A BNE $02C7 02BD- EA NOP 02BE- BD 8C C0 LDA $C08C,X 02C1- 10 FB BPL $02BE 02C3- C9 AA CMP #$AA 02C5- F0 17 BEQ $02DE 02C7- C6 52 DEC $52 02C9- 10 A2 BPL $026D ; can't read sector -- move to track 0 ; then back, and try again 02CB- A5 57 LDA $57 02CD- 48 PHA 02CE- A9 60 LDA #$60 02D0- 85 57 STA $57 02D2- A9 00 LDA #$00 02D4- 20 8A 03 JSR $038A 02D7- 68 PLA 02D8- 20 8A 03 JSR $038A 02DB- 18 CLC 02DC- 90 8B BCC $0269 ; execution continues here (from $02C5) 02DE- A4 55 LDY $55 02E0- C4 57 CPY $57 02E2- F0 07 BEQ $02EB 02E4- A5 57 LDA $57 02E6- 84 57 STY $57 02E8- 18 CLC 02E9- 90 ED BCC $02D8 02EB- A4 5F LDY $5F ; is this the sector we wanted? 02ED- B9 F0 04 LDA $04F0,Y 02F0- C5 54 CMP $54 ; no, try again 02F2- D0 D3 BNE $02C7 02F4- A0 20 LDY #$00 02F6- 88 DEY 02F7- F0 CE BEQ $02C7 ; we're on the right sector, now find ; the data prologue 02F9- BD 8C C0 LDA $C08C,X 02FC- 10 FB BPL $02F9 02FE- 49 D5 EOR #$D5 0300- D0 F4 BNE $02F6 0302- EA NOP 0303- BD 8C C0 LDA $C08C,X 0306- 10 FB BPL $0303 0308- C9 AA CMP #$AA 030A- D0 F2 BNE $02FE 030C- A0 56 LDY #$56 030E- BD 8C C0 LDA $C08C,X 0311- 10 FB BPL $030E 0313- C9 AD CMP #$AD 0315- D0 E7 BNE $02FE ; decode nibbles and store directly in ; target page (uses a small buffer at ; $0497 for temporary storage) 0317- A9 00 LDA #$00 0319- 88 DEY 031A- 84 56 STY $56 031C- BC 8C C0 LDY $C08C,X 031F- 10 FB BPL $031C 0321- 59 97 03 EOR $0397,Y 0324- A4 56 LDY $56 0326- 99 97 04 STA $0497,Y 0329- D0 EE BNE $0319 032B- 84 56 STY $56 032D- BC 8C C0 LDY $C08C,X 0330- 10 FB BPL $032D 0332- 59 97 03 EOR $0397,Y 0335- A4 56 LDY $56 0337- 91 5B STA ($5B),Y 0339- C8 INY 033A- D0 EF BNE $032B 033C- BC 8C C0 LDY $C08C,X 033F- 10 FB BPL $033C 0341- D9 97 03 CMP $0397,Y 0344- D0 81 BNE $02C7 ; read data epilogue 0346- BD 8C C0 LDA $C08C,X 0349- 10 FB BPL $0346 034B- C9 DE CMP #$DE 034D- D0 F5 BNE $0344 034F- EA NOP 0350- BD 8C C0 LDA $C08C,X 0353- 10 FB BPL $0350 0355- C9 AA CMP #$AA 0357- D0 EB BNE $0344 ; finish denibblizing 0359- 86 58 STX $58 035B- A0 00 LDY #$00 035D- A2 56 LDX #$56 035F- CA DEX 0360- 30 FB BMI $035D 0362- B1 5B LDA ($5B),Y 0364- 5E 97 04 LSR $0497,X 0367- 2A ROL 0368- 5E 97 04 LSR $0497,X 036B- 2A ROL 036C- 91 5B STA ($5B),Y 036E- C8 INY 036F- D0 EE BNE $035F ; turn off drive motor (we'll turn it ; back on if there are more sectors) 0371- A6 58 LDX $58 0373- BD 88 C0 LDA $C088,X ; increment target page 0376- E6 5C INC $5C ; decrement sector, wrap around to $0F 0378- A4 5F LDY $5F 037A- 88 DEY 037B- 10 04 BPL $0381 037D- A0 0F LDY #$0F ; increment track 037F- E6 5E INC $5E 0381- 84 5F STY $5F ; decrement sector count 0383- C6 5D DEC $5D ; are we done yet? 0385- F0 0B BEQ $0392 ; no, loop back to read another sector 0387- 4C 38 02 JMP $0238 ; entry point to move drive head to ; desired track (called from $0252) ; accumulator holds track (not phase) 038A- 0A ASL 038B- 06 57 ASL $57 038D- 20 93 03 JSR $0393 0390- 46 57 LSR $57 0392- 60 RTS ; this is essentially identical to the ; SEEKABS routine at $B9A0 in DOS 3.3 0393- 86 58 STX $58 0395- 85 55 STA $55 0397- C5 57 CMP $57 0399- F0 4F BEQ $03EA 039B- A9 00 LDA #$00 039D- 85 53 STA $53 039F- A5 57 LDA $57 03A1- 85 54 STA $54 03A3- 38 SEC 03A4- E5 55 SBC $55 03A6- F0 31 BEQ $03D9 03A8- B0 06 BCS $03B0 03AA- 49 FF EOR #$FF 03AC- E6 57 INC $57 03AE- 90 04 BCC $03B4 03B0- 69 FE ADC #$FE 03B2- C6 57 DEC $57 03B4- C5 53 CMP $53 03B6- 90 02 BCC $03BA 03B8- A5 53 LDA $53 03BA- C9 0C CMP #$0C 03BC- B0 01 BCS $03BF 03BE- A8 TAY 03BF- 38 SEC 03C0- 20 DD 03 JSR $03DD 03C3- B9 15 04 LDA $0415,Y 03C6- 20 00 04 JSR $0400 03C9- A5 54 LDA $54 03CB- 18 CLC 03CC- 20 DF 03 JSR $03DF 03CF- B9 21 04 LDA $0421,Y 03D2- 20 00 04 JSR $0400 03D5- E6 53 INC $53 03D7- D0 C6 BNE $039F 03D9- 20 00 04 JSR $0400 03DC- 18 CLC 03DD- A5 57 LDA $57 03DF- 29 03 AND #$03 03E1- 2A ROL 03E2- 05 58 ORA $58 03E4- AA TAX 03E5- BD 80 C0 LDA $C080,X 03E8- A6 58 LDX $58 03EA- 60 RTS Here are the game-specific arrays for the block reads (referenced from $0207, $020C, and $0211): 03EB- 08 07 80 80 80 07 E0 ; address 03F2- 06 B9 26 35 35 39 05 ; length 03F9- 01 02 0E 12 16 02 1A ; track # ; wait loop (at $BA00 in DOS 3.3) ; (needs to be page-aligned because ; otherwise the branch takes too long ; and throws off the cycle count) 0400- 86 56 STX $56 0402- A2 11 LDX #$11 0404- CA DEX 0405- D0 FD BNE $0404 0407- E6 59 INC $59 0409- D0 02 BNE $040D 040B- E6 5A INC $5A 040D- 38 SEC 040E- E9 01 SBC #$01 0410- D0 F0 BNE $0402 0412- A6 56 LDX $56 0414- 60 RTS ; arm move delay table ; (at $BA11 in DOS 3.3) 0415- 01 30 28 0418- 24 20 1E 1D 1C 1C 1C 1C 0420- 1C 70 2C 26 22 1F 1E 1D 0428- 1C 1C 1C 1C 1C ; nibble read translate table ; (at $BA96 in DOS 3.3) 042D- 00 01 98 0430- 99 02 03 9C 04 05 06 A0 0438- A1 A2 A3 A4 A5 07 08 A8 0440- A9 AA 09 0A 0B 0C 0D B0 0448- B1 0E 0F 10 11 12 13 B8 0450- 14 15 16 17 18 19 1A C0 0458- C1 C2 C3 C4 C5 C6 C7 C8 0460- C9 CA 1B CC 1C 1D 1E D0 0468- D1 D2 1F D4 D5 20 21 D8 0470- 22 23 24 25 26 27 28 E0 0478- E1 E2 E3 E4 29 2A 2B E8 0480- 2C 2D 2E 2F 30 31 32 F0 0488- F1 33 34 35 36 37 38 F8 0490- 39 3A 3B 3C 3D 3E 3F 0497- [temporary denibblizing storage] ; physical to logical sector map ; (referenced from $02ED) 04F0- 00 0D 0B 09 07 05 03 01 04F8- 0E 0C 0A 08 06 04 02 0F ~ Chapter 14 In Which We Note The Small Irony Of Saving The Preloader For Last Finally, the preloader and cache-read routines at $D000. (This is T00,S0B.) ; load block 0 (X=0 here) D000- 20 00 02 JSR $0200 ; show credits on text page 2 (loaded ; as part of block 0) D003- 2C 55 C0 BIT $C055 D006- 2C 51 C0 BIT $C051 ; test for 128K D009- 8D 05 C0 STA $C005 D00C- A9 09 LDA #$09 D00E- 8D 00 80 STA $8000 D011- A9 23 LDA #$23 D013- 8D 01 80 STA $8001 D016- 0E 00 84 ASL $8400 D019- 0E 01 84 ASL $8401 D01C- 8D 04 C0 STA $C004 D01F- 8D 03 C0 STA $C003 D022- AD 00 80 LDA $8000 D025- C9 09 CMP #$09 D027- D0 0A BNE $D033 D029- AD 01 80 LDA $8001 D02C- C9 23 CMP #$23 D02E- 8D 02 C0 STA $C002 D031- F0 04 BEQ $D037 ; no 128K, nothing left to do D033- 8D 02 C0 STA $C002 D036- 60 RTS ; preload blocks 2, 3, and 4 into aux ; memory (adapted from original game ; preloader) D037- A0 02 LDY #$02 D039- 8C FF D0 STY $D0FF D03C- BE CC D0 LDX $D0CC,Y D03F- A9 70 LDA #$70 D041- 85 5C STA $5C D043- 8D 6E D0 STA $D06E D046- BD F2 03 LDA $03F2,X D049- 85 5D STA $5D D04B- BD F9 03 LDA $03F9,X D04E- 85 5E STA $5E D050- 20 16 02 JSR $0216 D053- AC FF D0 LDY $D0FF D056- BE CC D0 LDX $D0CC,Y D059- A9 00 LDA #$00 D05B- 9D F9 03 STA $03F9,X D05E- BD C6 D0 LDA $D0C6,X D061- 8D 71 D0 STA $D071 D064- BC F2 03 LDY $03F2,X D067- A2 00 LDX #$00 D069- 8D 05 C0 STA $C005 D06C- BD 00 FF LDA $FF00,X D06F- 9D 00 FF STA $FF00,X D072- CA DEX D073- D0 F7 BNE $D06C D075- EE 6E D0 INC $D06E D078- EE 71 D0 INC $D071 D07B- 88 DEY D07C- D0 EE BNE $D06C D07E- AC FF D0 LDY $D0FF D081- 8D 04 C0 STA $C004 D084- 88 DEY D085- 10 B2 BPL $D039 ; set RWTS entry point to jump to the ; following routine (at $D08E) to check ; the cache in aux memory D087- A9 4C LDA #$4C D089- 8D 00 02 STA $0200 D08C- 60 RTS ; entry point to load preloaded blocks ; from cache instead of disk D08E- AD 83 C0 LDA $C083 D091- AD 83 C0 LDA $C083 ; is this block cached in aux memory? D094- BD F9 03 LDA $03F9,X D097- F0 03 BEQ $D09C ; no, jump back to RWTS to read block ; from disk D099- 4C 03 02 JMP $0203 ; yes, copy it from aux memory D09C- BD C6 D0 LDA $D0C6,X D09F- 8D B2 D0 STA $D0B2 D0A2- BD EB 03 LDA $03EB,X D0A5- 8D B5 D0 STA $D0B5 D0A8- BC F2 03 LDY $03F2,X D0AB- A2 00 LDX #$00 D0AD- 8D 03 C0 STA $C003 D0B0- BD 00 FF LDA $FF00,X D0B3- 9D 00 FF STA $FF00,X D0B6- CA DEX D0B7- D0 F7 BNE $D0B0 D0B9- EE B2 D0 INC $D0B2 D0BC- EE B5 D0 INC $D0B5 D0BF- 88 DEY D0C0- D0 EE BNE $D0B0 D0C2- 8D 02 C0 STA $C002 D0C5- 60 RTS Arrays for which blocks to cache and where to put them in aux memory: D0C8- 02 28 5D ; address (high byte) D0CC- 04 03 02 ; block index Quod erat liberandum. ~ Epilogue: Cheats and Hacks I have not enabled any of these hacks, but I have verified that they work. Infinite lives: T03,S09,$A7 change "C6" to "A5" Make jump to next level instead of toggling sound: T0B,S00,$39 change "A5 3E 49 01 85 3E" to "A2 FF 9A 4C DC 09" T18,S09,$39 change "A5 3E 49 01 85 3E" to "A2 FF 9A 4C DC 09" Make give you an extra life instead of toggling sound: T0B,S00,$39 change "A5 3E 49 01 85 3E" to "A2 FF 9A 4C AA 09" T18,S09,$39 change "A5 3E 49 01 85 3E" to "A2 FF 9A 4C AA 09" Make pause the game instead of : T0B,S00,$30 change "A0" to "9B" T18,S09,$30 change "A0" to "9B" Turn off 128K preloader (game will boot faster but access disk more later): T00,S00,$8E change "F0" to "24" ~ Changelog 2015-09-04 - better explanation of why a long sequence of zero bits on the disk appears to return random noise 2015-09-03 - typos (thanks qkumba) 2015-07-05 - re-rip block 3 (was corrupted on work disk due to typo during original rip) - fix auxmem block array in preloader (caused corruption on "extra life" screen because not all blocks are the same size, so some cached blocks were being overwritten by others) - added note on extra life cheat since I had to find it anyway to test the fix for the cache corruption Thanks to usotsuki for finding these bugs and testing the fixes. 2015-06-29 - initial release --------------------------------------- A 4am crack No. 350 ------------------EOF------------------