----------Talking Text Writer---------- A 4am crack 2017-06-23 --------------------------------------- Name: Talking Text Writer Version: 1987-06-01 (most recent file datestamp in ProDOS disk catalog) Genre: educational Year: 1986 Credits: Teresa J. Rosegrant, Russell A. Cooper Publisher: Scholastic, Inc. Platform: Apple //e or later (64K) Media: 4 single-sided 5.25-inch disks OS: ProDOS Previous cracks: none Similar cracks: #684 Math Shop v1986-10-27 I have 4 disks, labeled 1. "reader" 2. "writer" 3. "file maker" 4. "sample files" I'll start with disk 1. ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy loads ProDOS then reboots Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing immediately suspicious Disk Fixer T00 -> looks like ProDOS bootloader and ProDOS disk catalog Why didn't any of my copies work? Probably a nibble check in the startup program Next steps: 1. Search for the error condition (maybe we'll get lucky!) 2. Disable protection check that leads to the error condition 3. Declare victory (*) go to the gym ~ Chapter 1 Jump, Jump For My Love [S6,D1=non-working copy] Turning to my trusty Disk Fixer sector editor, I search my non-working copy for "4C 00 C6" ("JMP $C600", to reboot from slot 6). --v-- ------------- DISK SEARCH ------------- $04/$05-$37 $1C/$09-$E8 --^-- Two matches. T04,S05 seems like a dead end. T1C,S09, on the other hand, contains a veritable plethora of red flags. A few of them that stand out immediately: - a JSR to a subroutine, then a conditional branch over a JMP which immediately reboots (so, this code is very intolerant of errors -- and very unfriendly!) - another subroutine with a reference to "$C089,X" (a common way to turn on the drive motor manually) - that subroutine also contains several instances of "$C08C,X" (to read the data latch of the drive) It appears this code is loaded into memory at $3000. Here it is through the eyes of a sector editor: --v-- T1C,S09 ----------- DISASSEMBLY MODE ---------- ; set up some parameters 0000:A9 40 LDA #$40 0002:8D 88 30 STA $3088 0005:A9 00 LDA #$00 0007:8D 87 30 STA $3087 000A:8D 89 30 STA $3089 000D:8D 8A 30 STA $308A ; get slot number x16 (standard ProDOS ; location) 0010:AD 30 BF LDA $BF30 0013:8D 86 30 STA $3086 0016:20 69 30 JSR $3069 . . . ; call ProDOS MLI 0069:20 00 BF JSR $BF00 ; MLI command=#$80 (raw block read) 006C:80 ; address of parameter table (set up ; earlier to point to block #$0000 into ; $4000) 006D:85 30 ; immediately return regardless of ; success or failure 006F:60 RTS Continuing from $3019... ; Z flag set -> branch over next line 0019:F0 03 BEQ $001E ; if Z flag is clear, there was some ; error with the ProDOS MLI call, so we ; end up at $30E8, which reboots slot 6 001B:4C E8 30 JMP $30E8 . . . 00E8:4C 00 C6 JMP $C600 That's the behavior I saw in my non- working copy. Continuing from $301E... ; get slot number x16 again 001E:AE 30 BF LDX $BF30 ; call a subroutine (will investigate) 0021:20 9B 30 JSR $309B ; and store result (!) 0024:85 1A STA $1A . . . ; turn on drive motor manually 009B:BD 89 C0 LDA $C089,X ; some sort of Death Counter 009E:A9 56 LDA #$56 00A0:85 1C STA $1C 00A2:A9 08 LDA #$08 00A4:C6 1B DEC $1B 00A6:D0 04 BNE $00AC 00A8:C6 1C DEC $1C ; If the Death Counter hits zero, that ; would be bad. Which part of "Death ; Counter" sounded good to you, anyway? ; (Specifically, this jumps to the ; immediate reboot we saw earlier.) 00AA:F0 3C BEQ $00E8 ; look for an #$FB nibble 00AC:BC 8C C0 LDY $C08C,X 00AF:10 FB BPL $00AC 00B1:C0 FB CPY #$FB 00B3:D0 ED BNE $00A2 00B5:F0 00 BEQ $00B7 ; kill a few cycles (not pointless, ; because the disk spins independently ; of the CPU, so all of these low-level ; disk reads are highly time-sensitive) 00B7:EA NOP 00B8:EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 00B9:BC 8C C0 LDY $C08C,X ; do a compare to set or clear the ; carry bit (among other things, but ; it's the carry bit we care about) 00BC:C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 00BE:2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 00BF:B0 0B BCS $00CC ; next nibble needs to be #$FF 00C1:BC 8C C0 LDY $C08C,X 00C4:10 FB BPL $00C1 00C6:C0 FF CPY #$FF ; ...otherwise we start over 00C8:D0 D8 BNE $00A2 ; loop back to get next nibble 00CA:F0 EB BEQ $00B7 ; execution continues here (from $30BF) ; and we get another (full) nibble 00CC:BC 8C C0 LDY $C08C,X 00CF:10 FB BPL $00CC ; stash it in zero page 00D1:84 1B STY $1B ; if the accumulator is anything but ; %00001010, start over 00D3:C9 0A CMP #$0A 00D5:D0 CB BNE $00A2 ; get one more nibble 00D7:BD 8C C0 LDA $C08C,X 00DA:10 FB BPL $00D7 ; AND it with the previously stashed ; nibble, to get a single 4-and-4 ; encoded byte value 00DC:38 SEC 00DD:2A ROL 00DE:25 1B AND $1B 00E0:49 FF EOR #$FF ; branch on failure 00E2:F0 07 BEQ $00EB ; turn off drive motor manually 00E4:DD 88 C0 CMP $C088,X ; and exit gracefully to the caller 00E7:60 RTS I got lost several times trying to follow this routine. I think the easiest way to explain it is to show the difference between the original disk and my non-working copy. ~ Chapter 2 It's Time To Get Visual Here is the original disk, as seen by the Copy II+ nibble editor. Nibbles with extra "0" bits (timing bits) after them are displayed in inverse on an original machine, marked here with a "+" after the nibble. --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 00 START: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF+FF FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- It's easy to understand why a simple sector copy failed. The sequence that this code is looking for starts at offset $1C93, which is between the end of one sector and the beginning of the next. (The data epilogue is at $1C8C; the next address prologue is at $1CA2.) Sector copiers discard everything between those delimiters and rebuild the track with a default pattern of sync bytes. That pattern doesn't include an $FB nibble, so the nibble check fails. But the EDD bit copy also failed. Here is the original disk's pattern at offset $1C93: - #$FB + timing bit - #$FF - #$FF + timing bit - #$FF - #$FF + timing bit And here is what the same part of the track looks like on my failed EDD copy: --v-- 1C70: 9F EB E5 FC D7 D7 D7 EE 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF FF+FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --^-- A subtle difference! The sequence at offset $1C93 now looks like this: - #$FB + timing bit - #$FF - #$FF - #$FF + timing bit - #$FF + timing bit This code is looking for #$FF nibbles with an alternating pattern of timing bit, no timing bit, timing bit, no timing bit. It doesn't find that on the bit copy, so it knows it's not running on an original disk. ~ Chapter 3 That's What Friends Are For This protection check has side effects; it sets the accumulator to the value of some bit math performed on two nibbles read from disk. I don't know what those values are, so I don't know what the accumulator is when the protection routine returns to the caller, so I don't know what value ends up in zero page $1A. I also don't know if it matters or not, but I'm going to err on the side of caution and assume that it does. I have seen this protection check before, but not this variation that stores the final value of the accumulator. So... how to capture it? I could boot trace the entire disk, but that's... boring. I could sector-edit the code on the original disk, but NEVER DO THIS DON'T EVEN ASK WHY JUST NO. Instead, I'm going to take advantage of the fact that I have two disk drives, and that this protection check takes a slot (and drive) on entry, and I'm going to hack a copy. [S6,D1=original disk] [S5,D1=non-working copy] Editing the NON-WORKING COPY with the Disk Fixer sector editor, I can make two small changes to this sector: ; Instead of getting the current slot+ ; drive from ProDOS, hard-code it to ; slot 6, drive 1. Do this before each ; JSR (the raw block read and the ; protection check itself) T1C,S09,$10: AD30BF -> EAA960 T1C,S09,$1E: AE30BF -> EAA260 ]PR#5 ...reboots slot 5... ...briefly accesses slot 6... ...continues booting from slot 5... Hooray! We have a bootable copy... as long as the original disk is in slot 6. One more edit (TO THE COPY OF COURSE), to print out the accumulator after the protection check passes: T1C,S09,$24: 851AA00BB95C -> 20DAFD4C69FF ]PR#5 ...reboots slot 5... ...briefly accesses slot 6... 03 Excellent. The value of the accumulator on exit is #$03. Starting over with a fresh (but non- working) copy of the original disk, I can make a small edit to the protection routine so it sets the accumulator to #$03 and exits unconditionally. [S6,D1=fresh copy of disk 1] Disk 1: T1C,S09,$9A: BD89C0 -> A90360 ]PR#6 ...works... Disk 2 ("writer") has an identical protection routine on track $1D. Disk 2: T1D,S03,$9A: BD89C0 -> A90360 Disks 3 and 4 are unprotected. Quod erat liberandum. --------------------------------------- A 4am crack No. 1270 ------------------EOF------------------