------------Finance Manager------------ A 4am crack 2015-11-27 --------------------------------------- Name: Finance Manager Version: 1.0 Genre: productivity Year: 1984 Author: Interactive Software Publisher: Human Engineered Software Media: three 5.25-inch floppy disks OS: DOS 3.3 with custom bootloader Previous cracks: none Similar cracks: #476 Microzine 2 #464 Microzine 4 #409 Microzine 5 #332 Microzine 3 Disk 1 is bootable but protected. Disks 2 and 3 are unprotected but unbotable. Life is like that sometimes. ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error Locksmith Fast Disk Backup unable to read T00-T0A T0B-T22 copy OK obviously the copy does not boot EDD 4 bit copy (no sync, no count) no read errors, but copy hangs on the text title screen Copy ][+ nibble editor T00 -> standard prologues, modified epilogues (FF FF EB) T01-T02 -> corrupted address fields that claim to be track $00 T03-T04 -> not full tracks? looks like they have some standard-ish sectors, but not 16 per track (also corrupted address fields) T05-T0A -> more corrupted address fields T0B+ standard --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 01 START: 2735 LENGTH: 185A ^^ 2710: FF FF FF FF FF FF FF FF VIEW 2718: FF FF FF FF FF FF FF FF 2720: FF FF FF FF FF FF FF FF 2728: FF FF FF FF FF FF FF FF 2730: FF FF FF FF FF D5 AA 96 <-2735 ^^^^^^^^ address prologue 2738: AA AA AA AA AA AA AA AA ^^^^^ ^^^^^ ^^^^^ ^^^^^ V000 T00 S00 chksm 2740: FF FF FF FF FC FF FF FF ^^^^^^^^ address epilogue 2748: FF D5 AA AD F2 FA D7 D7 ^^^^^^^^ data prologue 2750: A6 BE FE F7 FB EC 97 B9 --------------------------------------- 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 T0B-T22 readable T11 looks like DOS 3.3 catalog ["O" -> "Input/Output Control"] set Address Epilogue to "FF FF EB" set Data Epilogue to "FF FF EB" T00 readable T01-T0A still unreadable (no option to ignore corrupted address fields) Copy ][+ track/sector map T00-T0A are marked as used, but no files correspond to those tracks T11 is marked as used, as expected --v-- TRACK 1 2 0123456789ABCDEF0123456789ABCDEF012 S0 ...........EHHIII.BCDEFIHIIIIIJJJJ E1 ...........EHHIII.BCDEFIHIIIIIJJJJ C2 ...........EHHIII.BCDEFIHIIIIIJJJJ T3 ...........DHHIII.BCDEFIHIIIIIJJJJ O4 ...........DFHIII.BCDEFIHIIIIIJJJJ R5 ...........DEHIII.BCDEFGHIIIIIJJJJJ 6 ...........DEHIII.BCDEFGHIIIIIJJJJJ 7 ...........DEHIII.BCDEFGHIIIIIJJJJJ 8 ...........CEHIII.BCDEFGHIIIIIJJJJJ 9 ...........CEHIII.BCDEFGHIIIIIJJJJJ A ...........CEHIII.BCDEFGHIIIIIIJJJJ B ...........CEHIII.BCDEFGHIIIIIIJJJJ C ...........CEHIII.BCDEFGHIIIIIIJJJJ D ...........CEHHII.BCDEFGHIIIIIIJJJJ E ...........CEHHII.ACDEFGHIIIIIIJJJJ F ...........BEHHII.ACDEFGHIIIIIIJJJJ --^-- Copy ][+ sector editor ["P" -> "Sector Editor Patcher"] set type to "CUSTOM" set Address Epilogue to "FF FF" set Data Epilogue to "FF FF EB" set CHECK TRACK to "NO" T01-T02 readable T05-T0A readable only parts of T03 and T04 readable: T03: S03,04,05,06,07,0A,0B,0C,0D,0E T04: S01,02,04,08,09,0C,0F Why didn't COPYA work? modified epilogues and corrupted address fields Why didn't Locksmith FDB work? modified epilogues and corrupted address fields Why didn't my EDD copy work? I've seen similar disks, where the first N tracks have intentionally corrupted address fields. (N varies from disk to disk.) There's a custom loader that loads the data from those corrupted tracks, then transfers control to a standard RWTS for the rest of the program. Somewhere in the corrupted tracks, it will load data from consecutive half tracks (which EDD did not copy). That's just an educated guess; I could be surprised. Next steps: 1. Trace the boot 2. ??? ~ Chapter 1 In Which Everything Is Awesome [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 ]CALL -151 *800<2800.28FFM *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, IN#0, PR#0, HOME, &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". ; Then we can treat it like any other ; subroutine: JSR to it and continue. 0839- A9 60 LDA #$60 083B- 8D 01 08 STA $0801 ; double 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 ($04) ; A = start address high byte ($9D) 0841- A0 00 LDY #$00 0843- 84 FC STY $FC 0845- C8 INY 0846- A9 9D LDA #$9D 0848- A2 04 LDX #$04 084A- 20 55 08 JSR $0855 ; not shown, but it turns out this is ; what displays the text title screen 084D- 20 00 A0 JSR $A000 ; another sector read, this time just ; one sector, into $0200 (X is already ; less than Y on entry, so loop will ; exit after one read) 0850- A9 02 LDA #$02 0852- AA TAX 0853- A0 05 LDY #$05 ; 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 and ; store it in zero page where the disk ; controller ROM will look for it 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 ; subroutine to read a sector via ($3E) ; which points to $Cx5C, which exits ; via $0801, which is now an "RTS" ; (HOW F---ING ELEGANT IS THAT, RIGHT?) 086E- A6 2B LDX $2B 0870- 6C 3E 00 JMP ($003E) ; logical to physical sector mapping *873.882 0873- 00 03 05 07 09 0878- 0B 0D 0F 02 04 06 08 0A 0880- 0C 0E 01 That's it. Flexible but compact. So where does this routine go next? Ah! We manually pushed $02/$72 to the stack earlier, so once we fall through to the multi-sector read routine (at $0850) and hit the RTS (at $086D), it will "return" to $0272 + 1 = $0273. Let's interrupt the boot before it gets there. ~ Chapter 2 In Which Things Get Brilliantly Weird *9600physical sectors is at ; $0263) or a physical sector 021D- 24 4A BIT $4A 021F- 30 03 BMI $0224 ; store physical sector in $3D (again, ; used by the disk controller ROM) 0221- B9 63 02 LDA $0263,Y 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 ; after reading a sector 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 To sum up: These two lines of code... || 02C4- A9 A0 LDA #$A0 || || 02C6- 4C 0B 02 JMP $020B || advanced the drive head from track $00 to track $01, read the entire track into $A000..$AFFF (despite the fact that every sector's address field was corrupted and claimed to be track $00), then fall through from $020B to $020E and do it all over again, but moving to track $02 and reading into $B000..$BFFF instead. Beautiful. ~ Chapter 3 Every Byte Is Sacred, Every Byte Is Great, If A Byte Gets Wasted, Woz Gets Quite Irate Continuing from $0278... 0278- 20 9E 02 JSR $029E *29EL ; advance the drive head a whole track 029E- 20 33 02 JSR $0233 [now on track 3] ; zero page fiddling (see below) 02A1- A9 00 LDA #$00 02A3- 85 41 STA $41 02A5- 38 SEC 02A6- 66 4A ROR $4A ; Read several sectors (but not the ; entire track) ; A = start address high byte ($08) ; Y = start sector ($01) ; X = end sector ($05) 02A8- A9 08 LDA #$08 02AA- A0 01 LDY #$01 02AC- A2 05 LDX #$05 02AE- 20 15 02 JSR $0215 ; move the drive head one phase only, ; to the next HALF track 02B1- 20 36 02 JSR $0236 [now on track 3.5] ; read more sectors ($06..$0A) 02B4- A2 0A LDX #$0A 02B6- 20 15 02 JSR $0215 ; advance another half track 02B9- 20 36 02 JSR $0236 [now on track 4] ; read more sectors ($0B..$0F) 02BC- A2 0F LDX #$0F 02BE- 20 15 02 JSR $0215 ; fiddle with $4A again 02C1- 46 4A LSR $4A 02C3- 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 $02A5 with SEC + ROR, indicating we want $0215 to read physical sectors. Then we read a few sectors from track 3, a few from track 3.5, and a few from track 4. 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 3, a few from track 3.5, and a few from track 4. 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. 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. Continuing from $027B... *27BL ; more multi-track reads, this time ; starting at $1700 and reading 6 ; tracks ($0208 calls $020E and falls ; through to $020B, which calls $020E ; and falls through to $020E, so each ; of these JSRs reads 3 consecutive ; tracks) 027B- A9 17 LDA #$17 027D- 20 08 02 JSR $0208 0280- 20 08 02 JSR $0208 ; now put slot number (x16) into an ; RWTS parameter table 0283- A6 2B LDX $2B 0285- 8E E9 B7 STX $B7E9 ; turn off drive motor 0288- BD 88 C0 LDA $C088,X ; set up DOS globals (tracking where ; the drive head is, so the disk ; doesn't grind when we switch over to ; the new RWTS) 028B- 20 8E BE JSR $BE8E 028E- A5 FC LDA $FC 0290- 99 78 04 STA $0478,Y 0293- 4A LSR 0294- 8D 78 04 STA $0478 ; push $B7/$01 on the stack 0297- A9 B7 LDA #$B7 0299- 48 PHA 029A- A9 01 LDA #$01 029C- 48 PHA ; and exit 029D- 60 RTS Execution continues at $B702 (because we just pushed $B7/$01 on the stack). ~ Chapter 4 In Which Everything Is Terribly Awesome I can interrupt the boot to capture the code loaded at $A000..$BFFF, $0800.. $16FF, and $1700..$76FF. *9600 *C500G ... ]BSAVE BOOT2 1700-76FF,A$1700,L$6000 Whew. ~ Chapter 5 In Which Everything Is Simple If You Look At It The Right Way Let's write everything back to disk, but all normal and stuff. No half tracks, no spirals, no tricks, no traps. Just, you know, sectors on tracks on a disk. Locksmith FDB copied tracks $0B-$22. Using my trusty Disk Fixer sector editor, I turned off checksums and copied the 16 sectors from track $00 to that same disk, one at a time. Now I need to copy the data I captured from tracks $01-$0A, which is saved on my work disk in three separate files. Here's the plan: tk | range ---+-------------- 01 | $A000..$AFFF 02 | $B000..$BFFF 03 | $0800..$16FF (1 sector unused) 04 | $1700..$26FF 05 | $2700..$36FF 06 | $3700..$46FF 07 | $4700..$56FF 08 | $5700..$66FF 09 | $6700..$76FF 0A | [unused] There's one track left over because the original disk used that spiral tracking to store 15 sectors between tracks 3, 3.5, and 4. I just put all of that on track 3, so everything else shifts down one track and track $0A is unused. Like musical chairs, but not really. ]PR#5 ... ]CALL -151 *300L ; 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 to the interleave order that ; this disk expects 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 ; sector interleave table *340.34F 0340- 00 06 05 04 03 02 01 0F 0348- 0E 0D 0C 0B 0A 09 08 07 ; 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 *800:0 N 801<800.BEFEM ; zap mem *BLOAD BOOT2 A000-BFFF,A$1000 ; chunk 1 *BLOAD BOOT2 0800-16FF,A$3000 ; chunk 2 *BLOAD BOOT2 1700-76FF,A$4000 ; chunk 3 [S6,D1=partial copy (T00, T0B-T22)] [S5,D1=my work disk] *300G ...write write write... Now I need to modify the bootloader at $0273 slightly. 1. Modify the routine at $029E to read all of track $03 instead of the crazy spiral stuff. 2. Modify the routine at $0233 (that advances the drive head) so it updates zero page $41 with the current track. The sector read routine at $C65C compares the track listed in the address field to zero page $41 and loops forever until it matches. $C600 initializes $41 to 0, and the original disk never updates $41, but everything works because the address fields are corrupted and all claim to be track 0. So part of it will be simpler, because we'll no longer be spiraling between tracks. But part of it will actually be more complicated because the address fields are no longer corrupted and we need to track the track number. Weird. Patch #1 (read track $03, no spiral) T00,S02,$9E change "20 33 02 A9 00" to "A9 08 4C 0E 02" Patch #2 (subroutine to increment $41) T00,S02,$A3 change "85 41 38 66 4A" to "E6 41 4C 36 02" Patch #3 (call patch #2 on track inc) T00,S02,$34 change "36" to "A3" --v-- ----------- DISASSEMBLY MODE ---------- 0033:20 A3 02 JSR $02A3 ; #3 009E:A9 08 LDA #$08 ; #1 00A0:4C 0E 02 JMP $020E 00A3:E6 41 INC $41 ; #2 00A5:4C 36 02 JMP $0236 --^-- ]PR#6 ...works... Disks 2 and 3 are unprotected. Quod erat liberandum. --------------------------------------- A 4am crack No. 507 ------------------EOF------------------