---------------Agent USA--------------- A 4am crack 2015-05-11 --------------------------------------- Name: Agent USA Genre: educational Year: 1984 Credits: - Omar Khudari - Tom Synder - Chris Lutes - Peter Reynolds - Leonard Bertoni (Apple version) - Tom Snyder Productions, Inc. Publisher: Scholastic, Inc. Media: single-sided 5.25-inch floppy OS: custom Other versions: Mr. Clean (file crack) ~ 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 track $0B+ copy displays title screen then hangs Copy ][+ nibble editor tracks $00-$0A have modified address and data epilogues (FF FF FF) tracks $01+ have a corrupted address field that claim to be track $00 tracks $0B+ appear unformatted --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 01 START: 1800 LENGTH: 3DFF 3138: FF FF B2 B2 FF FF FF FF VIEW 3140: FF FF FF FF FF FF FF FF 3148: FF FF FF FF FF FF FF D4 3150: D4 FF FF FF FF FC FC FF 3158: FF FF FF FF FF D5 AA 96 <-315D ^^^^^^^^ address prologue 3160: AA AA AA AA AB AE AB AE ^^^^^ ^^^^^ ^^^^^ ^^^^^ V=0 T=0 S=6 chksm 3168: FF FF FF E7 E7 F9 FE FF ^^^^^^^^ address epilogue 3170: FF D5 AA AD E5 EA FC F7 3178: FC BA B4 F9 BE FF FA CE --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- Disk Fixer ["O" -> "Input/Output Control"] set Address Epilogue to "FF FF FF" set Data Epilogue to "FF FF FF" T00 readable T00 -> custom bootloader nothing else readable Why didn't COPYA work? modified epilogue bytes (every track) Why didn't Locksmith FDB work? modified epilogue bytes (every track) Why didn't my EDD copy work? I don't know yet. I can see the raw nibbles on tracks $01+ (including the corrupted address field) but the disk just hangs. The game is a single-load; it does not access the disk once it is running. It should be possible to capture the whole game in memory and recreate the boot experience with a custom bootloader. Next steps: 1. Trace the boot 2. Capture the game 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 ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 ]BLOAD BOOT0,A$800 ]CALL -151 *801L ; set reset vector 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 080A- 8D F3 03 STA $03F3 080D- 49 A5 EOR #$A5 080F- 8D F4 03 STA $03F4 0812- A9 00 LDA #$00 0814- 8D F2 03 STA $03F2 ; hmm 0817- A9 02 LDA #$02 0819- 48 PHA ; machine initialization (memory banks, ; TEXT, HOME, IN#0, PR#0, &c.) 081A- 8D 81 C0 STA $C081 081D- 20 2F FB JSR $FB2F 0820- 8D 52 C0 STA $C052 0823- 20 89 FE JSR $FE89 0826- 20 93 FE JSR $FE93 0829- 20 58 FC JSR $FC58 082C- 8D 51 C0 STA $C051 082F- 8D 54 C0 STA $C054 0832- 8D 52 C0 STA $C052 ; set up ($3E) vector to point to the ; sector read routine in the disk ; controller ROM 0835- A9 5C LDA #$5C 0837- 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 0839- A9 60 LDA #$60 083B- 8D 01 08 STA $0801 ; hmm 083E- A9 72 LDA #$72 0840- 48 PHA OK, we've now pushed $02/$72 on the stack. That's probably important. ; multi-sector read ; Y = start logical sector ($01) ; X = end logical sector ($0E) ; A = start address high byte ($09) 0841- A0 00 LDY #$00 0843- 84 FC STY $FC 0845- C8 INY 0846- A9 09 LDA #$09 0848- A2 0E LDX #$0E ; multi-sector read routine 084A- 20 55 08 JSR $0855 ; call the code we just read 084D- 20 00 09 JSR $0900 ; another sector read, this time just ; sector $0F into $0200 0850- A9 02 LDA #$02 0852- A0 0F LDY #$0F 0854- AA TAX ; falls through to multi-sector read ; entry point (was also called earlier ; from $084A) 0855- 85 27 STA $27 0857- E8 INX 0858- 86 49 STX $49 085A- 84 F9 STY $F9 ; map logical into physical sector 085C- B9 73 08 LDA $0873,Y 085F- 85 3D STA $3D ; read sector via disk controller ROM 0861- 20 6E 08 JSR $086E ; loop until done 0864- A4 F9 LDY $F9 0866- C8 INY 0867- C4 49 CPY $49 0869- 90 EF BCC $085A 086B- A5 27 LDA $27 086D- 60 RTS 086E- A6 2B LDX $2B 0870- 6C 3E 00 JMP ($003E) That's it. Flexible but compact. Just by listening to the original disk boot up, it displays the graphical title screen before it ever moves off track $00. So I'm betting that the routine at $900 is a graphics unpacker, and the rest of the data is the compressed title screen. 32 pages of data compressed into 14. Not too shabby for an 8-bit computer. No idea what's at $0200 though. Let's find out. ~ Chapter 2 In Which We Confront The Limits Of Our Expertise, And Expand Them *9600physical sectors is at ; $0263) or a physical sector 021D- 24 4A BIT $4A 021F- 30 03 BMI $0224 0221- B9 63 02 LDA $0263,Y ; store physical sector in $3D (again, ; used by the disk controller ROM) 0224- 85 3D STA $3D ; read sector by jumping to ($003E), ; 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 0226- 20 00 02 JSR $0200 ; increment sector index 0229- A4 F9 LDY $F9 022B- C8 INY ; are there more sectors to read? 022C- C4 49 CPY $49 ; yes, branch back and repeat 022E- 90 EA BCC $021A ; 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) 0230- A5 27 LDA $27 0232- 60 RTS We called this routine at $029E with A=$41, Y=$01, and X=$05, so that read sectors $01..$05 into $4100..$45FF. Continuing... *2A7L ; move the drive head one phase only, ; to the next HALF track 02A7- 20 36 02 JSR $0236 ; read more sectors ($06..$0A) from ; track 1.5 02AA- A2 0A LDX #$0A 02AC- 20 15 02 JSR $0215 ; advance another half track 02AF- 20 36 02 JSR $0236 ; read more sectors ($0B..$0F) from ; track 2 02B2- A2 0F LDX #$0F 02B4- 20 15 02 JSR $0215 ; fiddle with $4A again 02B7- 46 4A LSR $4A 02B9- 60 RTS So here's the deal with $4A: we initialized it at $0273 by a blind LSR, which clears the high bit. This tells the multi-sector read routine at $0215 to use logical sectors. Then we set the high bit at $029B with SEC + ROR, indicating we want $0215 to read physical sectors. Then we read a few sectors from track 1, a few from track 1.5, and a few from track 2. Then we reset $4A with another LSR, and we're back to using logical sectors. This explains why my EDD bit copy failed. This disk is storing data on half tracks. Worse, it's storing data on *adjacent* half tracks -- a few from track 1, a few from track 1.5, and a few from track 2. Due to limitations of the Disk II drive mechanism, that would be virtually impossible for a generic bit copier to reproduce on a blank floppy disk. Continuing... *278L 0278- A9 50 LDA #$50 027A- 20 05 02 JSR $0205 *205L ; This is a fascinating and compact ; way to read multiple tracks -- just ; call one of these lines ($0205, ; $0208, or $020B) and have them call ; the "read entire track" routine at ; $020E (which itself falls through to ; the "read partial track" routine at ; $0215) and keep falling through until ; it finally hits the RTS at $0232 and ; returns to the caller. 0205- 20 0E 02 JSR $020E 0208- 20 0E 02 JSR $020E 020B- 20 0E 02 JSR $020E 020E- 20 33 02 JSR $0233 0211- A2 0F LDX #$0F 0213- A0 00 LDY #$00 0215- 85 27 STA $27 0217- E8 INX 0218- 86 49 STX $49 021A- 84 F9 STY $F9 021C- 98 TYA 021D- 24 4A BIT $4A 021F- 30 03 BMI $0224 0221- B9 63 02 LDA $0263,Y 0224- 85 3D STA $3D 0226- 20 00 02 JSR $0200 0229- A4 F9 LDY $F9 022B- C8 INY 022C- C4 49 CPY $49 022E- 90 EA BCC $021A 0230- A5 27 LDA $27 0232- 60 RTS So a call to $0205 will read 4 tracks into $5000..$8FFF. The accumulator holds the starting page on entry, and $0215 loads the accumulator with the next page on exit, so you can just chain calls as often as you like to read multiple tracks into consecutive memory. Every part of this code is brilliant. AND it fits in a single sector in low memory. AND it's flexible enough to read from virtually uncopyable disks. *27DL ; read three more tracks (into $9000.. ; $BFFF) 027D- 20 08 02 JSR $0208 ; move drive head one more time (A is ; unused but preserved by this routine) 0280- A9 40 LDA #$40 0282- 20 33 02 JSR $0233 ; push $40 to the stack 0285- 48 PHA ; read one sector (into $4000..$40FF) 0286- A2 00 LDX #$00 0288- 20 13 02 JSR $0213 ; turn off drive motor 028B- A6 2B LDX $2B 028D- BD 88 C0 LDA $C088,X ; push $15 to the stack 0290- A9 15 LDA #$15 0292- 48 PHA ; exit via RTS 0293- 60 RTS We pushed $40/$15 to the stack, so this will "return" to $4016, which is the game's entry point. I can interrupt the boot at $028D and capture the entire game in memory. *9600 *C0E8 *C500G ... ]BSAVE OBJ.2000-7FFF,A$2000,L$6000 ]BRUN TRACE3 ...reboots slot 6... *C0E8 *2000<8000.BFFFM *C500G ... ]BSAVE OBJ.8000-BFFF,A$2000,L$4000 ~ Chapter 4 If You Wish To Play A Game, You Must First Create The Universe [S6,D1=blank disk] [S5,D1=my work disk] ]PR#5 ... The game occupies $2000..$BFFF, but for the sake of writing it to disk once, I will load it at $1000..$AFFF. (This doesn't interfere with DOS in memory because my work disk uses Diversi-DOS 64K, which relocates itself to the language card during boot. The only part of main memory still in use is $BF00..$BFFF.) When I configure the bootloader on the new disk, it will load the game into $2000..$BFFF. ]BLOAD OBJ.2000-7FFF,A$1000 ]BLOAD OBJ.8000-BFFF,A$7000 ]CALL -151 ; page count (decremented) 0300- A9 A0 LDA #$A0 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 0327- C6 FF DEC $FF ; loop until done with all pages 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 $1000, and RWTS ; write command ($02) *388.397 0388- 01 60 01 00 01 00 FB F7 0390- 00 10 00 00 02 00 00 60 *BSAVE MAKE,A$300,L$98 *300G ...write write write... Now I have the entire game on tracks $01-$0A 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 2E FB FF 0888- 08 15 40 20 0A 00 00 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, $FB2F, $0900, and $4016. (Each of these routines exits via RTS and "returns" to the next address (+1) that we pushed on the stack.) - $FE89, $FE93, and $FB2F are in ROM (IN#0, PR#0, and TEXT) - $0900 is the RWTS entry point. It reads tracks $01..$0A into $2000.. $BFFF. (These values are stored in zero page, which we just set.) - $4016 is the game's 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. *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 7C 0A JSR $0A7C ; 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 routine at $0A7C checks the current track number (technically the phase, stored in zero page $FD). After reading track $02 (phase $04), it switches on the hi-res graphics screen. We just loaded $2000..$3FFF from tracks $01 and $02, so this shows the title page as soon as possible. (I did not retain the original compressed data on track 0.) *A7CL ; check phase 0A7C- A5 FD LDA $FD 0A7E- C9 04 CMP #$04 0A80- D0 0C BNE $0A8E ; switch to hi-res grpahics page 1 0A82- 2C 54 C0 BIT $C054 0A85- 2C 57 C0 BIT $C057 0A88- 2C 52 C0 BIT $C052 0A8B- 2C 50 C0 BIT $C050 ; exit via drive head moving routine 0A8E- 4C 53 0A JMP $0A53 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 and no compressed graphics (minimizes memory copies) means the entire boot process takes about three seconds. Quod erat liberandum. --------------------------------------- A 4am crack No. 306 ------------------EOF------------------