----------------Flip Out--------------- A 4am crack 2015-08-04 -------------------. updated 2017-08-28 |___________________ Name: Flip Out Genre: strategy Year: 1982 Authors: Scott Huskey Publisher: Sirius Software Media: single-sided 5.25-inch floppy OS: custom Previous cracks: Captain Computer ______________________________ { } { "One mustn't look at the } { abyss, because there is at } { the bottom an inexpressible } { charm which attracts us." } { } { Gustave Flaubert } {______________________________} ~ 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 T0C-T1F, T21 copy boots, loads several tracks, displays graphical title screen, then clears screen, displays "BOOT ERROR", and reboots Copy ][+ nibble editor can't make hide nor hair of anything Disk Fixer can't read anything beyond T00,S00 under any combination of parameters Why didn't COPYA work? not a 16-sector disk Why didn't Locksmith FDB work? not a 16-sector disk Why didn't my EDD copy work? I don't know. Probably a nibble check during boot. The original disk displays the hi-res title screen while loading. It is a single-load game; it does not touch the disk once it's fully loaded. Next steps: 1. Trace the boot 2. Capture entire game in memory 3. Build a new disk with a fastloader to replicate the original disk's boot experience ~ Chapter 1 In Which We Start Off Loudly And Build To A Crescendo [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 ]BLOAD BOOT0,A$800 ]CALL -151 *801L ; save boot slot number 0801- A5 2B LDA $2B 0803- AA TAX 0804- 85 FB STA $FB 0806- 4A LSR 0807- 4A LSR 0808- 4A LSR 0809- 4A LSR 080A- 09 C0 ORA #$C0 080C- 8D 00 30 STA $3000 ; zap language card 080F- A0 00 LDY #$00 0811- 84 00 STY $00 0813- A9 D0 LDA #$D0 0815- 85 01 STA $01 0817- A2 30 LDX #$30 0819- AD 81 C0 LDA $C081 081C- AD 81 C0 LDA $C081 081F- B1 00 LDA ($00),Y 0821- 91 00 STA ($00),Y 0823- C8 INY 0824- D0 F9 BNE $081F 0826- E6 01 INC $01 0828- CA DEX 0829- D0 F4 BNE $081F ; initialize globals 082B- A6 FB LDX $FB 082D- 84 F7 STY $F7 082F- A9 04 LDA #$04 0831- 85 F8 STA $F8 0833- 85 FA STA $FA ; load some more sectors from track $00 ; with a 4-4 encoding scheme and a ; prologue of "AD DA DD" 0835- BD 8C C0 LDA $C08C,X 0838- 10 FB BPL $0835 083A- C9 AD CMP #$AD 083C- D0 F7 BNE $0835 083E- BD 8C C0 LDA $C08C,X 0841- 10 FB BPL $083E 0843- C9 DA CMP #$DA 0845- D0 F3 BNE $083A 0847- BD 8C C0 LDA $C08C,X 084A- 10 FB BPL $0847 084C- C9 DD CMP #$DD 084E- D0 EA BNE $083A 0850- A0 00 LDY #$00 0852- 84 F5 STY $F5 0854- BD 8C C0 LDA $C08C,X 0857- 10 FB BPL $0854 0859- 38 SEC 085A- 2A ROL 085B- 85 F6 STA $F6 085D- B0 11 BCS $0870 ; main loop to read 2 nibbles and save ; 1 byte 085F- BD 8C C0 LDA $C08C,X 0862- 10 FB BPL $085F 0864- 2A ROL 0865- 85 F6 STA $F6 0867- C8 INY 0868- D0 06 BNE $0870 ; increment page 086A- E6 F8 INC $F8 ; decrement sector count 086C- C6 FA DEC $FA 086E- F0 0F BEQ $087F 0870- BD 8C C0 LDA $C08C,X 0873- 10 FB BPL $0870 0875- 25 F6 AND $F6 0877- 91 F7 STA ($F7),Y ; calculate a running checksum 0879- 45 F5 EOR $F5 087B- 85 F5 STA $F5 ; loop back for more bytes 087D- B0 E0 BCS $085F ; verify checksum 087F- BD 8C C0 LDA $C08C,X 0882- 10 FB BPL $087F 0884- 25 F6 AND $F6 0886- 45 F5 EOR $F5 0888- D0 A5 BNE $082F ; jump to the code we just loaded 088A- 4C 29 04 JMP $0429 $F8 (initially 4) appears to be the page in memory to put the sector. It's incremented after each read (at $086A). $FA (also initially 4) appears to be the sector count. It's decremented after each read (at $086C). At $088A, it jumps to $0429 to continue the boot. So I need to capture the text page. *9600 Poking around, it appears the game occupies $0C00..$8FFF. I'll save it in chunks. *2000 *C500G ... ]BSAVE OBJ.2000-5FFF,A$2000,L$4000 ]BRUN TRACE2 ... *2000<6000.8FFFM *C500G ... ]BSAVE OBJ.6000-8FFF,A$2000,L$3000 And, just for good measure, let's make sure I got it all: ]CALL -151 *800:FD N 801<800.BEFEM *BLOAD OBJ.0C00-1FFF,A$C00 *BLOAD OBJ.2000-5FFF,A$2000 *BLOAD OBJ.6000-8FFF,A$6000 *8F63G ...works... Almost there. ~ Chapter 6 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. 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 90 LDA #$90 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 $0C00, and RWTS ; write command ($02) *388.397 0388- 01 60 01 00 01 00 FB F7 0390- 00 0C 00 00 02 00 00 60 *BSAVE MAKE,A$300,L$98 *800:0 N 801<800.BEFEM ; clear memory *BLOAD OBJ.0C00-1FFF,A$C00 *BLOAD OBJ.2000-5FFF,A$2000 *BLOAD OBJ.6000-8FFF,A$6000 *300G ; write game to disk Now I have the entire game on tracks $01-$09 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. 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 2E FB FF 0888- BC 62 8F 0C 09 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 $FB2F, then $BD00, then $8F63. - $FE89, $FE93, and $FB2F are in ROM (reset input, output, and textmode) - $BD00 is the RWTS entry point. It loads T01-T09 into memory, starting at $0C00. (These values are stored in zero page, which we just set.) - $8F63 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 $BE7C $BE7C is actually a wrapper around the advance-drive-head routine. The real routine starts at $BE53. It looks like this: *BE7CL ; advance drive head BE7C- 20 53 BE JSR $BE53 ; check current phase (track x2) BE7F- A5 FD LDA $FD BE81- C9 0A CMP #$0A BE83- D0 0C BNE $BE91 ; once we've read enough into memory, ; show the graphical title screen BE85- 2C 54 C0 BIT $C054 BE88- 2C 57 C0 BIT $C057 BE8B- 2C 52 C0 BIT $C052 BE8E- 2C 50 C0 BIT $C050 BE91- 60 RTS This reproduces the behavior of the original disk's loader, which showed the title screen briefly while it continued loading the rest of the game. Continuing at $BD17... ; 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. ~ Changelog 2017-08-28 - typos (thanks Richard S.) 2015-08-04 - initial release --------------------------------------- A 4am crack No. 392 ------------------EOF------------------