----------------Falcons---------------- A 4am crack 2015-04-28 --------------------------------------- Name: Falcons Genre: arcade Year: 1981 Authors: Thomas Ball and Eric Varsanyi Publisher: Piccadilly Software, Inc. Media: single-sided 5.25-inch floppy OS: custom Other versions: several uncredited cracks on Asimov Similar cracks: Ribbit, also from Piccadilly (4am crack no. 73) ~ 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) tons of read errors; copy never gets off track $00 during boot Copy ][+ nibble editor T00 has a modified address prologue (D5 AA B5) and modified epilogues T01+ appears to be 4-4 encoded data with custom prologue/delimiter (neither 13 nor 16 sectors) Disk Fixer not much help 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. A nibble check during boot? The original disk switches to a 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 In Which We Reap The Benefits Of Automation Before The Robots Rise Up And Kill Us All [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 /!\ BOOT0 RELOCATES TO $0200 CAPTURING BOOT0 STAGE 2 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 STAGE 2 Some recent upgrades to my AUTOTRACE program come in handy here. T00,S00 relocates itself to low memory, then re-uses the disk controller ROM routine to read a few sectors from track $00 (probably containing an RWTS of sorts). The pattern is common enough that I automated detection and tracing of it. ]BLOAD BOOT0,A$800 ]CALL -151 *801L ; immediately move this code to the ; input buffer at $0200 0801- A2 00 LDX #$00 0803- BD 00 08 LDA $0800,X 0806- 9D 00 02 STA $0200,X 0809- E8 INX 080A- D0 F7 BNE $0803 080C- 4C 0F 02 JMP $020F *20F<80F.8FFM ; set up a nibble write table at $0800 020F- A0 AB LDY #$AB 0211- 98 TYA 0212- 85 3C STA $3C 0214- 4A LSR 0215- 05 3C ORA $3C 0217- C9 FF CMP #$FF 0219- D0 09 BNE $0224 021B- C0 D5 CPY #$D5 021D- F0 05 BEQ $0224 021F- 8A TXA 0220- 99 00 08 STA $0800,Y 0223- E8 INX 0224- C8 INY 0225- D0 EA BNE $0211 0227- 84 3D STY $3D ; $00 into zero page $26 and $03 into ; $27 means we're probably going to be ; loading data into $0300..$03FF later. 0229- 84 26 STY $26 022B- A9 03 LDA #$03 022D- 85 27 STA $27 022F- A6 2B LDX $2B 0231- 20 5D 02 JSR $025D *25DL ; read a sector from track $00 (this is ; actually derived from the code in the ; disk controller ROM routine at $C65C, ; but looking for an address prologue ; of "D5 AA B5" instead of "D5 AA 96") ; and using the nibble write table we ; set up earlier at $0800 025D- 18 CLC 025E- 08 PHP 025F- BD 8C C0 LDA $C08C,X 0262- 10 FB BPL $025F 0264- 49 D5 EOR #$D5 0266- D0 F7 BNE $025F 0268- BD 8C C0 LDA $C08C,X 026B- 10 FB BPL $0268 026D- C9 AA CMP #$AA 026F- D0 F3 BNE $0264 0271- EA NOP 0272- BD 8C C0 LDA $C08C,X 0275- 10 FB BPL $0272 0277- C9 B5 CMP #$B5 0279- F0 09 BEQ $0284 027B- 28 PLP 027C- 90 DF BCC $025D 027E- 49 AD EOR #$AD 0280- F0 1F BEQ $02A1 0282- D0 D9 BNE $025D 0284- A0 03 LDY #$03 0286- 84 2A STY $2A 0288- BD 8C C0 LDA $C08C,X 028B- 10 FB BPL $0288 028D- 2A ROL 028E- 85 3C STA $3C 0290- BD 8C C0 LDA $C08C,X 0293- 10 FB BPL $0290 0295- 25 3C AND $3C 0297- 88 DEY 0298- D0 EE BNE $0288 029A- 28 PLP 029B- C5 3D CMP $3D 029D- D0 BE BNE $025D 029F- B0 BD BCS $025E 02A1- A0 9A LDY #$9A 02A3- 84 3C STY $3C 02A5- BC 8C C0 LDY $C08C,X 02A8- 10 FB BPL $02A5 ; use the nibble write table we set up ; earlier 02AA- 59 00 08 EOR $0800,Y 02AD- A4 3C LDY $3C 02AF- 88 DEY 02B0- 99 00 08 STA $0800,Y 02B3- D0 EE BNE $02A3 02B5- 84 3C STY $3C 02B7- BC 8C C0 LDY $C08C,X 02BA- 10 FB BPL $02B7 02BC- 59 00 08 EOR $0800,Y 02BF- A4 3C LDY $3C ; store in $0300 02C1- 91 26 STA ($26),Y 02C3- C8 INY 02C4- D0 EF BNE $02B5 02C6- BC 8C C0 LDY $C08C,X 02C9- 10 FB BPL $02C6 02CB- 59 00 08 EOR $0800,Y 02CE- D0 8D BNE $025D 02D0- 60 RTS Continuing from $0237... *237L 0237- A9 A9 LDA #$A9 0239- 8D 0F 03 STA $030F 023C- A9 02 LDA #$02 023E- 8D 10 03 STA $0310 0241- 4C 01 03 JMP $0301 This is where I need to interrupt the boot, before it jumps to $0301. Here is the relevant portion of my AUTOTRACE program that does that automatically: *97BFL ; replicate the memory move 97BF- A2 00 LDX #$00 97C1- BD 00 08 LDA $0800,X 97C4- 9D 00 02 STA $0200,X 97C7- E8 INX 97C8- D0 F7 BNE $97C1 ; now set up a callback to a routine ; under my control 97CA- A9 D7 LDA #$D7 97CC- 8D 42 02 STA $0242 97CF- A9 97 LDA #$97 97D1- 8D 43 02 STA $0243 ; start the boot 97D4- 4C 0F 02 JMP $020F ; callback is here -- move the code ; from $0300 to the graphics page so it ; can survive a reboot 97D7- A0 00 LDY #$00 97D9- B9 00 03 LDA $0300,Y 97DC- 99 00 23 STA $2300,Y 97DF- C8 INY 97E0- D0 F7 BNE $97D9 ; set up markers to tell AUTOTRACE what ; to do after we reboot 97E2- A9 83 LDA #$83 97E4- 8D 00 01 STA $0100 97E7- 49 A5 EOR #$A5 97E9- 8D 01 01 STA $0101 ; turn off the slot 6 drive motor 97EC- AD E8 C0 LDA $C0E8 ; reboot to my work disk (this will run ; AUTOTRACE again and complete the ; process) 97EF- 4C 00 C5 JMP $C500 Once this reboots, AUTOTRACE saves the captured code from $0300 at $2300 into a file. Let's see what that looks like. ~ Chapter 2 In Which Things Get A Little Weird ]BLOAD BOOT0 0300-03FF,A$300 ]CALL -151 *301L 0301- 78 SEI 0302- D8 CLD ; I have no idea what this is doing 0303- B9 00 08 LDA $0800,Y 0306- 0A ASL 0307- 0A ASL 0308- 0A ASL 0309- 99 00 08 STA $0800,Y 030C- C8 INY 030D- D0 F4 BNE $0303 ; This is even weirder -- it literally ; does nothing, repeatedly. It's not a ; wait loop. Maybe a simplified version ; of a more complicated boot routine? 030F- A9 02 LDA #$02 0311- A0 1E LDY #$1E 0313- EA NOP 0314- EA NOP 0315- EA NOP 0316- EA NOP 0317- EA NOP 0318- EA NOP 0319- EA NOP 031A- EA NOP 031B- C8 INY 031C- D0 F5 BNE $0313 ; write-enable RAM bank 1 031E- AD 89 C0 LDA $C089 0321- AD 89 C0 LDA $C089 ; wipe all memory (including RAM bank) 0324- 20 7B 03 JSR $037B ; write-enable RAM bank 2 0327- AD 81 C0 LDA $C081 032A- AD 81 C0 LDA $C081 ; wipe all memory again (including RAM ; bank) 032D- 20 7B 03 JSR $037B ; now *this* looks like a normal sector ; read loop (much like DOS 3.3 uses in ; its boot0 code) 0330- A9 09 LDA #$09 0332- 85 27 STA $27 0334- 4A LSR 0335- 85 39 STA $39 0337- 85 3F STA $3F 0339- 84 38 STY $38 033B- 84 3E STY $3E 033D- AD 0F 03 LDA $030F 0340- 8D 50 03 STA $0350 0343- AD 10 03 LDA $0310 0346- 8D 51 03 STA $0351 ; set up entry point to disk controller ; ROM routine, based on the slot number ; we booted from (still in zero page ; $2B at this point) 0349- A6 2B LDX $2B 034B- 8A TXA 034C- 4A LSR 034D- 4A LSR 034E- 4A LSR 034F- 4A LSR 0350- 09 C0 ORA #$C0 0352- 85 37 STA $37 0354- A9 5D LDA #$5D 0356- 85 36 STA $36 0358- E6 3D INC $3D ; turn on hi-res graphics page 035A- AD 54 C0 LDA $C054 035D- AD 57 C0 LDA $C057 0360- AD 52 C0 LDA $C052 0363- AD 50 C0 LDA $C050 ; read sector via ($0036), a.k.a. $C65D 0366- 20 78 03 JSR $0378 0369- 20 9E 03 JSR $039E ; after 4 sectors, break out of read ; loop and continue execution at $039A 036C- A5 3D LDA $3D 036E- 49 03 EOR #$03 0370- F0 28 BEQ $039A 0372- E6 39 INC $39 0374- E6 3D INC $3D 0376- D0 EE BNE $0366 ... ; execution continues here 039A- A0 18 LDY #$18 039C- D0 62 BNE $0400 So this is loading 4 sectors into the text page at $0400..$07FF. I'm guessing this is the RWTS that will read the rest of the disk. Whatever it is, I need to capture it. Luckily, there's more than enough space at $039A to put a JMP to a routine under my control. Unluckily, this code wiped memory (twice!) before reading the RWTS, so I will need to disable that before I can set up a callback. AUTOTRACE didn't automate this part, so I'll have to write a boot tracer by hand like some kind of 20th century peasant. *9600 $0201. $0201 = "4C 00 60" (set at $04E9), so this jumps to $6000 to start the game. Whew. I kind of hope that turns out to be important. ~ Chapter 4 In Which We Snatch Defeat From The Jaws of Victory, And Vice-Versa I can interrupt the boot at $061E and capture the entire game in memory. *9600 *6000G ...hangs... No further disk activity, but it is definitely hanging after the asterisks- clearing-the-graphics-screen animation. Curses! Foiled in my moment of triumph! If I had to guess, I'd say there is a routine early on in the game code that checks either $0200 (set to $7C at $060E) or $07F7 (set to $FE at $061B). Both of these got overwritten when I broke into the monitor (the first because it's part of the input buffer, the second because it's part of the text page). ]PR#5 ]BRUN TRACE2 ...reboots slot 6... ...read read read... *200:7C N 7F7:FE N 6000G ...works... ]PR#5 ]BRUN TRACE2 ... *6000L 6000- AD 50 C0 LDA $C050 6003- AD 54 C0 LDA $C054 6006- AD 57 C0 LDA $C057 6009- AD 52 C0 LDA $C052 ; memory move 600C- 20 C8 68 JSR $68C8 600F- A9 00 LDA #$00 6011- 8D CE 06 STA $06CE ; memory move 6014- 20 74 68 JSR $6874 6017- A9 01 LDA #$01 6019- 8D CE 06 STA $06CE ; memory move 601C- 20 74 68 JSR $6874 ; initializes globals 601F- 20 68 68 JSR $6868 6022- A9 00 LDA #$00 6024- 8D E1 06 STA $06E1 6027- 20 BC 9A JSR $9ABC *9ABCL ; asterisk effect 9ABC- A9 FF LDA #$FF 9ABE- 8D C4 06 STA $06C4 9AC1- 20 CE 9A JSR $9ACE 9AC4- EE C4 06 INC $06C4 9AC7- 20 CE 9A JSR $9ACE 9ACA- 20 A5 9E JSR $9EA5 *9EA5L ; bingo 9EA5- AD 00 02 LDA $0200 9EA8- C9 7C CMP #$7C 9EAA- D0 08 BNE $9EB4 ; double bingo 9EAC- AD F7 07 LDA $07F7 9EAF- C9 FE CMP #$FE 9EB1- D0 01 BNE $9EB4 9EB3- 60 RTS It looks like I can put an "RTS" at $9EA5 to neutralize the final in-game check. A few memory moves and reboots later, I have the entire game ($0800..$B6FF) in a series of files: ]CATALOG,S5,D1 C1983 DSR^C#254 272 FREE A 019 HELLO B 004 AUTOTRACE B 003 BOOT0 B 003 BOOT0 0300-03FF B 003 TRACE B 006 BOOT1 0400-07FF B 003 TRACE2 B 026 FALCONS.OBJ 0800-1FFF B 066 FALCONS.OBJ 2000-5FFF B 066 FALCONS.OBJ 6000-9FFF B 025 FALCONS.OBJ A000-B6FF ~ 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. Classic cracks didn't include the title screen, because it was 1981 and 8192 bytes was expensive. The social mores of classic crackers allowed for discarding title screens altogether in pursuit of the smallest possible file crack. It's 2015. Let's write a bootloader. [S6,D1=blank formatted disk] [S5,D1=my work disk] ]PR#5 ]CALL -151 ; page count (decremented) 0300- A9 B0 LDA #$B0 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 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 $0800, and RWTS ; write command ($02) *388.397 0388- 01 60 01 00 01 00 FB F7 0390- 00 08 00 00 02 00 00 60 *BSAVE MAKE,A$300,L$98 *BLOAD FALCONS.OBJ 0800-1FFF,A$0800 *BLOAD FALCONS.OBJ 2000-5FFF,A$2000 *BLOAD FALCONS.OBJ 6000-9FFF,A$6000 *BLOAD FALCONS.OBJ A000-B6FF,A$A000 *9EA5:60 ; neutralize in-game check *300G ; write game to disk Now I have the entire game on tracks $01-$0B of a standard format disk. The bootloader (which I've named 4boot) lives on track $00. T00S00 is boot0, which reuses the disk controller ROM routine to load boot1, which lives on sectors $0C-$0E. Boot0 looks like this: ; decrement sector count 0801- CE 19 08 DEC $0819 ; branch once we've read enough sectors 0804- 30 12 BMI $0818 ; increment physical sector to read 0806- E6 3D INC $3D ; set page to save sector data 0808- A9 BF LDA #$BF 080A- 85 27 STA $27 ; decrement page 080C- CE 09 08 DEC $0809 ; $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 080F- BD 80 08 LDA $0880,X 0812- 8D 17 08 STA $0817 ; read a sector (exits via $0801) 0815- 4C 5C 00 JMP $005C ; sector read loop exits to here (from ; $0804) -- note: by the time execution ; reaches here, $0819 is $FF, so this ; just resets the stack 0818- A2 03 LDX #$03 081A- 9A TXS ; set up zero page (used by RWTS) and ; push an array of addresses to the ; stack at the same time 081B- A2 0F LDX #$0F 081D- BD 80 08 LDA $0880,X 0820- 95 F0 STA $F0,X 0822- 48 PHA 0823- CA DEX 0824- D0 F7 BNE $081D 0826- 60 RTS *881.88F 0880- 88 FE 92 FE 3E 08 FF 0888- BC FF 5F 08 0B 00 00 00 These are pushed to the stack in reverse order, starting with $088F. When we hit the "RTS" at $0826, it pops the stack and jumps to $FE89, then $FE93, then $083F, then $BD00, then $6000. - $FE89 and $FE93 are in ROM (IN#0 and PR#0). - $083F reproduces the initial color fill on the hi-res graphics screen, then displays it so you can watch the title screen load from disk. - $BD00 is the RWTS entry point. It loads T01-T0B into memory, starting at $0800. (These values are stored in zero page, which we just set.) - $6000 is the game entry point. It never returns, so the other values on the stack are irrelevant. The RWTS at $BD00 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. *BD00L ; set up some places later in the RWTS ; where we need to read from a slot- ; specific data latch BD00- A6 2B LDX $2B BD02- 8A TXA BD03- 09 8C ORA #$8C BD05- 8D 96 BD STA $BD96 BD08- 8D AD BD STA $BDAD BD0B- 8D C3 BD STA $BDC3 BD0E- 8D D7 BD STA $BDD7 BD11- 8D EC BD STA $BDEC ; advance drive head to next track BD14- 20 53 BE JSR $BE53 ; sectors-left-to-read-on-this-track ; counter BD17- A0 0F LDY #$0F BD19- 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. BD1B- 98 TYA BD1C- 18 CLC BD1D- 65 FB ADC $FB BD1F- 99 00 01 STA $0100,Y BD22- 88 DEY BD23- 10 F6 BPL $BD1B ; find the next address prologue and ; store the address field in $2C..$2F, ; like DOS 3.3 BD25- 20 0F BE JSR $BE0F ; check if this sector has been read BD28- A4 2D LDY $2D BD2A- B9 00 01 LDA $0100,Y ; if 0, we've read this sector already, ; so loop back and look for another BD2D- F0 F6 BEQ $BD25 ; 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 BD2F- A8 TAY BD30- 84 FF STY $FF BD32- 8C EA BD STY $BDEA BD35- A5 FE LDA $FE BD37- 8D E9 BD STA $BDE9 BD3A- 38 SEC BD3B- E9 54 SBC #$54 BD3D- 8D D1 BD STA $BDD1 BD40- B0 02 BCS $BD44 BD42- 88 DEY BD43- 38 SEC BD44- 8C D2 BD STY $BDD2 BD47- E9 57 SBC #$57 BD49- 8D AA BD STA $BDAA BD4C- B0 01 BCS $BD4F BD4E- 88 DEY BD4F- 8C AB BD STY $BDAB ; read the sector into memory BD52- 20 6D BD JSR $BD6D ; if that failed, just loop back and ; look for another sector BD55- B0 CE BCS $BD25 ; mark this sector as read BD57- A4 2D LDY $2D BD59- A9 00 LDA #$00 BD5B- 99 00 01 STA $0100,Y BD5E- E6 FB INC $FB ; decrement sectors-left-to-read-on- ; this-track counter BD60- C6 F8 DEC $F8 ; loop until we've read all the sectors ; on this track BD62- 10 C1 BPL $BD25 ; decrement tracks-left-to-read counter ; (set in boot0) BD64- C6 FC DEC $FC ; loop until we've read all the tracks BD66- D0 AC BNE $BD14 ; turn off drive motor and exit BD68- BD 88 C0 LDA $C088,X BD6B- 38 SEC BD6C- 60 RTS Quod erat liberandum. --------------------------------------- A 4am crack No. 299 ------------------EOF------------------