--------------Starblaster-------------- A 4am crack 2015-08-21 --------------------------------------- Name: Starblaster Genre: arcade Year: 1982 Authors: Mark Kriegsman, Geoffrey Engelstein (Piccadilly Software) Publisher: Softsmith Media: single-sided 5.25-inch floppy OS: custom Previous cracks: Mr. Xerox (file crack) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error, but it gets a participation medal just for showing up Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) read errors on tracks $03 and $09-$22 copy displays Softsmith logo, then game title screen, then hangs Copy ][+ nibble editor T00 -> standard prologues, modified epilogues (FF FF FF) T01-T08 -> corrupted address fields, all claim to be track $00 T02-T03 -> not full tracks? looks like they have some standard-ish sectors, but not 16 per track (also corrupted address fields) T09+ unformatted --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 01 START: 23FC LENGTH: 015F ^^ 23D8: FF FF FF FF FF FF FF FF VIEW 23E0: FF FF FF FF FF FF FF FF 23E8: FF FF FF FF FF FF FF FF 23F0: FF FF FF FF FF FF FF FF 23F8: FF FF FF FF FF D5 AA 96 <-23FD ^^^^^^^^ address prologue 2400: AA AA AA AA AA AA AA AA ^^^^^ ^^^^^ ^^^^^ ^^^^^ V000 T00 S00 chksm 2408: FF FF FF FE FF FF FF FF ^^^^^^^^ address epilogue 2410: FF D5 AA AD B4 BA 97 9E ^^^^^^^^ data prologue 2418: B5 B4 B4 B5 B5 BD B9 BE --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- The disk is lying to me. The address field claims to be track $00, but it's really track $01. Bad disk! Stop lying! Disk Fixer ["O" -> "Input/Output Control"] set Address Epilogue to "FF FF FF" set Data Epilogue to "FF FF FF" T00 readable T01..T03 unreadable (no option to ignore the corrupted address field) T04..T22 readable T11 looks like DOS 3.3 catalog Copy ][+ sector editor ["P" -> "Sector Editor Patcher"] set type to "CUSTOM" set Address Epilogue to "FF FF" set Data Epilogue to "FF FF FF" T00 readable ["P" -> "Sector Editor Patcher"] set CHECK TRACK to "NO" T01 readable! only parts of T02 and T03 readable: T02: S03,04,05,06,07,0A,0B,0C,0D,0E T03: S01,02,04,08,09,0C,0F T04-T08 readable Why didn't COPYA work? so many reasons Why didn't my EDD copy work? I don't know. I'm guessing there's some data between tracks $02 and $03. Next steps: 1. Trace the boot 2. Capture everything in memory 3. Write it to a standard disk and build my own bootloader to load it ~ Chapter 1 In Which We Are Still Impressed By What An 8-Bit Computer Can Do [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 ; munge boot slot (x16) into $Cx format 0801- 8A TXA 0802- 4A LSR 0803- 4A LSR 0804- 4A LSR 0805- 4A LSR 0806- 09 C0 ORA #$C0 0808- 85 3F STA $3F ; hmm 080A- A9 04 LDA #$04 080C- 48 PHA ; machine initialization (memory banks, ; TEXT, IN#0, PR#0, HOME, &c.) 080D- 2C 81 C0 BIT $C081 0810- 20 2F FB JSR $FB2F 0813- 2C 52 C0 BIT $C052 0816- 20 89 FE JSR $FE89 0819- 20 93 FE JSR $FE93 081C- 20 58 FC JSR $FC58 081F- 2C 51 C0 BIT $C051 0822- 2C 54 C0 BIT $C054 0825- 2C 52 C0 BIT $C052 ; set up ($3E) vector to point to the ; sector read routine in the disk ; controller ROM 0828- A9 5C LDA #$5C 082A- 85 3E STA $3E ; the disk controller ROM always exits ; via $0801, so set that to an RTS so ; we can JSR and not have to set up a ; loop 082C- A9 60 LDA #$60 082E- 8D 01 08 STA $0801 ; hmm 0831- A9 7E LDA #$7E 0833- 48 PHA OK, we've now pushed $04/$7E on the stack. That's probably important. 0834- A0 00 LDY #$00 0836- 84 FC STY $FC ; multi-sector read ; Y = start logical sector ($01) ; X = end logical sector ($09) ; A = start address high byte ($50) 0838- C8 INY 0839- A9 50 LDA #$50 083B- A2 09 LDX #$09 So, reading into $5000..$58FF. ; call multi-sector read routine 083D- 20 52 08 JSR $0852 ; call the code we just read 0840- 20 00 50 JSR $5000 Just based on how the original disk boots, this is certainly the Softsmith animated logo routine. 0843- 2C 50 C0 BIT $C050 ; another sector read, 5 more sectors ; ($0A..$0E) into $0900..$0DFF 0846- A0 0A LDY #$0A 0848- A9 09 LDA #$09 084A- A2 0E LDX #$0E 084C- 20 52 08 JSR $0852 ; another sector read, this time just ; one sector, into $0400 (X is already ; less than Y on entry, so loop will ; exit after one read) 084F- A9 04 LDA #$04 0851- AA TAX ; falls through to multi-sector read ; entry point (was also called earlier ; from $083D and $084C) 0852- 85 27 STA $27 0854- E8 INX 0855- 86 49 STX $49 0857- 84 F9 STY $F9 ; map logical into physical sector and ; store it in zero page where the disk ; controller ROM will look for it 0859- B9 70 08 LDA $0870,Y 085C- 85 3D STA $3D ; read sector via disk controller ROM 085E- 20 6B 08 JSR $086B ; loop until done 0861- A4 F9 LDY $F9 0863- C8 INY 0864- C4 49 CPY $49 0866- 90 EF BCC $0857 0868- A5 27 LDA $27 086A- 60 RTS 086B- A6 2B LDX $2B 086D- 6C 3E 00 JMP ($003E) 0870- [00 03 05 07 09 0B 0D 0F] [02 04 06 08 0A 0C 0E 01] That's it. Flexible but compact. We manually pushed $04/$7E on the stack (at $080C and $0833), so once we fall through to the sector read routine and it hits the RTS at $086A, it will "return" to $047E + 1 = $047F. Let's interrupt the boot before it gets there. ~ Chapter 2 In Which Things Get Brilliantly Weird *9600physical sectors is at ; $046F) or a physical sector 241D- 24 4A BIT $4A 241F- 30 03 BMI $2424 2421- B9 6F 04 LDA $046F,Y ; store physical sector in $3D (again, ; used by the disk controller ROM) 2424- 85 3D STA $3D ; read sector by jumping to ($3E), ; which points to $Cx5C (e.g. $C65C if ; booting from slot 6) and exit via ; $0801, which is an RTS by now, so ; this just continues to the next line 2426- 20 00 04 JSR $0400 ; increment sector index 2429- A4 F9 LDY $F9 ; are there more sectors to read? 242B- C8 INY 242C- C4 49 CPY $49 ; yes, branch back and repeat 242E- 90 EA BCC $241A ; no, exit with last page (+1) in A ; (disk controller ROM increments this ; after storing sector data, so on exit ; this will be the first page that was ; NOT filled with data in this loop) 2430- A5 27 LDA $27 2432- 60 RTS Here's the really beautiful part: this code never updates zero page $41. The sector read routine at $C65C compares the track in the address field to zp$41 and loops forever until it matches. $C600 initializes zp$41 to 0, and this code never updates zp$41, even after advancing the drive head to the next track. But it is still able to read tracks $01+ because the address fields on these tracks are corrupted and all claim to be track 0. That is brilliantly weird. ~ Chapter 3 Every Byte Is Sacred, Every Byte Is Great, If A Byte Gets Wasted, Woz Gets Quite Irate Continuing from $048B... 248B- 20 00 09 JSR $0900 My intuition tells me that this is the routine that displays the game title screen, which is shown while the rest of the game is loaded. This code is immediately overridden (see below), so I'll need to interrupt the boot here to capture it. *9600 *2900<900.1FFFM *C500G ... ]BSAVE OBJ.0900-1FFF,A$2900,L$1700 ]BRUN TRACE3 ... *C500G ... ]BSAVE OBJ.2000-5FFF,A$2000,L$4000 ]BRUN TRACE3 ... *2000<6000.91FFM *C500G ... ]BSAVE OBJ.6000-91FF,A$2000,L$3200 And, just for good measure, let's make sure I got it all: ]CALL -151 *800:FD N 801<800.BEFEM *BLOAD OBJ.0900-1FFF,A$2800 *BLOAD OBJ.2000-5FFF,A$3F00 *BLOAD OBJ.6000-91FF,A$7F00 *9FDG ...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. The original disk loads in three distinct stages: 1. Softsmith logo (decompressed + animated) 2. Game title screen (decompressed + shown) 3. Game code My crack will behave similarly. But for the purposes of creating the disk from scratch, I can put everything in memory and write it out to disk in one shot. Here's the disk layout: track | load address | source -------+--------------+---------------- $01 | $5000..$58FF | BOOT1 5000-59FF $02 | $0900..$12FF | BOOT1 0900-12FF $03 | $0900..$18FF | OBJ.0900-1FFF $04 | $1900..$28FF | OBJ.2000-5FFF $05 | $2900..$38FF | . $06 | $3900..$48FF | . $07 | $4900..$58FF | . $08 | $5900..$68FF | OBJ.6000-91FF $09 | $6900..$78FF | . $0A | $7900..$88FF | . $0B | $8900..$91FF | . Now to load it all in memory: *800:0 N 801<800.BEFEM *BLOAD BOOT1 5000-58FF,A$800 *BLOAD BOOT1 0900-12FF,A$1800 *BLOAD OBJ.0900-1FFF,A$2800 *BLOAD OBJ.2000-5FFF,A$3F00 *BLOAD OBJ.6000-91FF,A$7F00 Note that it's aligned in memory so it can be written to consecutive tracks. (e.g. The routine at $5000 is only $0A pages, but the next 5 pages are blank.) Now to write it all to disk: [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 *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. 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 1D LDX #$1D 081D- BD 72 08 LDA $0872,X 0820- 95 E2 STA $E2,X 0822- 48 PHA 0823- CA DEX 0824- D0 F7 BNE $081D 0826- 60 RTS *873.88F 0870- 88 FE 92 FE 2E 0878- FB FF BC FF 4F 7B BE FF 0880- BC FF 08 84 BE FF BC 8D 0888- BE FC 09 50 01 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 twice and jumps to that address + 1. Thus: - $FE89 is IN#0 - $FE93 is PR#0 - $FB2F is TEXT - $BD00 is the RWTS entry point (see below). It advances the drive head, then loads a number of tracks (given in zero page $FC) into memory at the address given in zero page $FB. $088C was stored in zp$FB, $088B was stored in zp$FC, so this will load track 1 into $5000. - $5000 displays the Softsmith logo. - $BE7C sets zp$FB=$09 and zp$FC=$01. - $BD00 loads track $02 into $0900. - $0900 displays the game title screen. - $BE85 sets zp$FB=$09 and zp$FC=$0A. - $BD00 loads tracks $03-$0B into $0900..$98FF. - $BE8E turns off the drive motor. - $09FD 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 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 Quod erat liberandum. --------------------------------------- A 4am crack No. 410 ------------------EOF------------------