---------------Classmate--------------- A 4am crack 2014-09-12 --------------------------------------- "Classmate" is a 1987 educational program for teachers to manage class records. It is programmed by Linda and Sid Broudy and distributed by Davidson and Associates. COPYA fails miserably and immediately. EDD 4 bit copy gives no read errors, but the copy just hangs on boot. Disk Fixer -- a standalone sector editor I just learned about -- can't read anything until I go to Input/Output Control (press "O") and turn off checksums to ignore address and data epilogues. Then it can read track 0, but nothing more. Time for boot tracing with AUTOTRACE. [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 For those of you just tuning in, my work disk uses a custom program that I affectionately call "AUTOTRACE" to automate the process of boot tracing as far as possible. For some disks (like this one, apparently), it just captures track 0, sector 0 (saved in a file called "BOOT0") and stops. For other disks that load in the same way that an unprotected DOS 3.3 disk loads, it captures the next stage of the boot process as well (in a file called "BOOT1"). BOOT1 contains sectors 0-9 on track 0, which are loaded into memory at $B600..$BFFF. This generally contains the RWTS routines which the program uses to read the rest of the disk. If the RWTS is fairly normal as well (and my AUTOTRACE program just spot- checks a few memory locations to guess at its "normalcy"), AUTOTRACE extracts the RWTS routines (generally loaded from track 0, sectors 2-9 into $B800.. $BFFF) and saves *that* into a third file called "RWTS". There's a good chance I'll be able to load that "RWTS" file into a tool called Advanced Demuffin (written in 1983 by The Stack) to convert the disk into a standard disk readable by unprotected DOS 3.3 disks or any other third-party tools. If anything looks fishy or non- standard, AUTOTRACE just stops, and I have to check the files it saved so far to determine why. In this case, it stopped after capturing T00,S00. So I need to look at that sector and figure out why. ]CALL -151 *800<2800.28FFM *801L . . all normal, until... . 084A- 4C C0 B6 JMP $B6F0 A little something extra before the boot1 code. I don't like extra. Extra is bad. In a normal DOS 3.3 disk, the code on T00,S00 is actually loaded twice: once at $0800 and then again at $B600, where it remains in memory until you reboot or do something to intentionally wipe it out. So I can see what's going to be at $B6F0 by looking at $08F0. *8F0L ; odd 08F0- A9 AA LDA #$AA 08F2- 85 31 STA $31 ; odd x2 08F4- A9 AD LDA #$AD 08F6- 85 4E STA $4E ; suspicious (since this code is loaded at $B600, this will overwrite the $AA byte in the LDA instruction above) 08F8- 8D F1 B6 STA $B6F1 ; continue with boot1 08FB- 4C 00 B7 JMP $B700 I've seen this pattern before on other disks -- "Math Blaster" and "Bingo Bugglebee Presents Home Alone", just to name two. There's no particular reason that my AUTOTRACE program shouldn't continue with boot1 tracing. It's safe to patch the code at $084A. A warning would be nice so I know to look at $8F0 later. ]PR#5 ... ]LOAD HELLO 116 IF PEEK (10314) = 76 AND PEEK (10315) = 240 AND PEEK (103 16) = 182 THEN PRINT "/!\ B OOT0 JUMPS TO $B6F0": GOTO 1 30 ]SAVE HELLO ]DELETE BOOT0 ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 /!\ BOOT0 JUMPS TO $B6F0 CAPTURING BOOT1 ...reboots slot 6... ...reboots slot 5... SAVING BOOT1 There we go. Hooray for automation. However, it didn't capture an RWTS, so there must be some more weirdness in boot1. Let's see what it is. ]BLOAD BOOT1,A$2600 ]CALL -151 *2700L 2700- A0 1A LDY #$1A 2702- B9 00 B7 LDA $B700,Y 2705- 49 1B EOR #$1B 2707- 99 00 B7 STA $B700,Y 270A- C8 INY 270B- D0 F5 BNE $2702 270D- EE 04 B7 INC $B704 2710- EE 09 B7 INC $B709 2713- AD 09 B7 LDA $B709 2716- C9 C0 CMP #$C0 2718- D0 E8 BNE $2702 271A- 95 F2 STA $F2,X 271C- AC 95 EC LDY $EC95 271F- AC B2 70 LDY $70B2 2722- 96 E9 STX $E9,Y 2724- 18 CLC 2725- B2 ??? Oh joy. Boot1 is encrypted. Come to think of it, I've seen this pattern before, too. Both "Math Blaster" and "Home Alone" had similar decryption loops at $B700, but the encryption key (the EOR instruction) was different. "Math Blaster" used "EOR #$6C"; "Home Alone" used "EOR #$0A". I can probably automate this step too. ]PR#5 ... ]LIST 200,250 200 REM BOOT1 WAS CAPTURED, NO W SAVE IT 205 PRINT "SAVING BOOT1" 210 PRINT CHR$ (4)"BSAVE BOOT1 ,A$2000,L$A00" 215 PRINT "SAVING BOOT1" 216 PRINT CHR$ (4)"BSAVE BOOT1 ,A$2000,L$A00" 220 RWTS = 0: GOSUB 1200 230 IF RWTS = 0 THEN 400 250 PRINT "SAVING RWTS" I can insert some code after line 210 to check for a simple decryption loop at $B700 and decrypt it. 211 KEY = 0: GOSUB 1300: IF KEY = 0 THEN 220 212 PRINT "/!\ BOOT1 IS ENCRYPT ED": PRINT "DECRYPTING BOOT1 " 213 POKE 38826,KEY: CALL 38820 214 PRINT CHR$ (4)"RENAME BOOT 1,BOOT1 ENCRYPTED" . . . 1300 REM CHECK FOR SIMPLE DEC RYPTION LOOP AT $B700 1301 REM (KEY<>0 ON EXIT IF F OUND) 1310 KEY = 0 1320 IF PEEK (8448) < > 160 THEN RETURN 1321 IF PEEK (8449) < > 26 THEN RETURN 1322 IF PEEK (8450) < > 185 THEN RETURN 1333 IF PEEK (8451) < > 0 THEN RETURN 1334 IF PEEK (8452) < > 183 THEN RETURN 1335 IF PEEK (8453) < > 73 THEN RETURN 1340 KEY = PEEK (8454): RETURN The subroutine at line 1300 checks the first six bytes of the boot1 code (in memory at $2100 at this point) for the sequence "A0 1A B9 00 B7 49". The next byte would be the decryption key (part of the EOR instruction). The actual decryption is part of the AUTOTRACE binary. Line 213 POKEs the decryption key into memory and CALLs the decryption routine at $97A4. 97A4- A0 1A LDY #$1A ; $B700 from disk is at $2100 right now 97A6- B9 00 21 LDA $2100,Y ; decryption key POKEd from line 213 97A9- 49 FF EOR #$FF 97AB- 99 00 21 STA $2100,Y 97AE- C8 INY 97AF- D0 F5 BNE $97A6 97B1- EE A8 97 INC $97A8 97B4- EE AD 97 INC $97AD 97B7- AD AD 97 LDA $97AD 97BA- C9 2A CMP #$2A 97BC- D0 E8 BNE $97A6 97BE- 60 RTS Saving all that up and starting with a fresh work disk, I get this: ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 /!\ BOOT0 JUMPS TO $B6F0 CAPTURING BOOT1 ...reboots slot 6... ...reboots slot 5... SAVING BOOT1 /!\ BOOT1 IS ENCRYPTED DECRYPTING BOOT1 SAVING BOOT1 ]CATALOG C1983 DSR^C#254 280 FREE A 015 HELLO B 003 AUTOTRACE B 024 ADVANCED DEMUFFIN 1.5 T 147 ADVANCED DEMUFFIN 1.5 DOCS B 003 BOOT0 B 012 BOOT1 ENCRYPTED B 012 BOOT1 Kick. Ass. But I still don't have an RWTS file. Let's look at the (automatically decrypted!) boot1 code and see what's going on. ]BLOAD BOOT1,A$2600 ]CALL -151 *FE89G FE93G *B600<2600.2FFFM *B700L ; decryption loop is untouched B700- A0 1A LDY #$1A B702- B9 00 B7 LDA $B700,Y B705- 49 1B EOR #$1B B707- 99 00 B7 STA $B700,Y B70A- C8 INY B70B- D0 F5 BNE $B702 B70D- EE 04 B7 INC $B704 B710- EE 09 B7 INC $B709 B713- AD 09 B7 LDA $B709 B716- C9 C0 CMP #$C0 B718- D0 E8 BNE $B702 ; decrypted code starts here B71A- 8E E9 B7 STX $B7E9 B71D- 8E F7 B7 STX $B7F7 ; unfriendly reset vector B720- A9 6B LDA #$6B B722- 8D F2 03 STA $03F2 B725- A9 B7 LDA #$B7 B727- 8D F3 03 STA $03F3 B72A- 49 A5 EOR #$A5 B72C- 8D F4 03 STA $03F4 B72F- EA NOP ; more RWTS parameters (normal) B730- A9 01 LDA #$01 B732- 8D F8 B7 STA $B7F8 B735- 8D EA B7 STA $B7EA B738- AD E0 B7 LDA $B7E0 B73B- 8D E1 B7 STA $B7E1 B73E- A9 02 LDA #$02 B740- 8D EC B7 STA $B7EC B743- A9 04 LDA #$04 B745- 8D ED B7 STA $B7ED B748- AC E7 B7 LDY $B7E7 B74B- 88 DEY B74C- 8C F1 B7 STY $B7F1 B74F- A9 01 LDA #$01 B751- 8D F4 B7 STA $B7F4 B754- 8A TXA B755- 4A LSR B756- 4A LSR B757- 4A LSR B758- 4A LSR B759- AA TAX B75A- A9 00 LDA #$00 B75C- 9D F8 04 STA $04F8,X B75F- 9D 78 04 STA $0478,X ; multi-sector read routine (normal) B762- 20 93 B7 JSR $B793 ; reset stack (normal) B765- A2 FF LDX #$FF B767- 9A TXS ; slightly odd (usually $9D84 is the ; boot2 entry point, but OK) B768- 4C 82 9D JMP $9D82 That all looks relatively normal. I don't see anything that would explain why my copy is hanging. It's not grinding, and it's not rebooting. If the RWTS was trying to read the disk and failing, the disk drive would be grinding. (You know what that sounds like.) But it's just hanging, like it's in an infinite loop somewhere. That is most likely intentional, like a nibble check that retries infinitely. Or maybe a nibble check that gives up and fails by going into an infinite loop with the drive motor still on. Let's follow the white rabbit, starting at $B793, the entry point for the multi-sector read routine. *B793L ; this is not normal B793- 4C 00 B8 JMP $B800 ; but the rest of the loop looks ; entirely normal B796- AD E4 B7 LDA $B7E4 B799- 20 B5 B7 JSR $B7B5 B79C- AC ED B7 LDY $B7ED B79F- 88 DEY B7A0- 10 07 BPL $B7A9 B7A2- A0 0F LDY #$0F B7A4- EA NOP B7A5- EA NOP B7A6- CE EC B7 DEC $B7EC B7A9- 8C ED B7 STY $B7ED B7AC- CE F1 B7 DEC $B7F1 B7AF- CE E1 B7 DEC $B7E1 B7B2- D0 DF BNE $B793 B7B4- 60 RTS Down the rabbit hole we go... *B800L ; Hmm, the first thing this routine ; does is restore the code that should ; have been at $B793 (but wasn't, ; because it jumped here instead). ; Which tells me that this is designed ; to be run exactly once, during boot, ; the first time anything uses the ; multi-sector read routine at $B793. B800- A9 AC LDA #$AC B802- 8D 93 B7 STA $B793 B805- A9 E5 LDA #$E5 B807- 8D 94 B7 STA $B794 B80A- A9 B7 LDA #$B7 B80C- 8D 95 B7 STA $B795 B80F- A9 07 LDA #$07 B811- 85 4F STA $4F ; oh look, we're turning on the drive ; motor manually B813- AE E9 B7 LDX $B7E9 B816- BD 8D C0 LDA $C08D,X B819- BD 8E C0 LDA $C08E,X B81C- 10 12 BPL $B830 ; do something (below) B81E- 20 3E B8 JSR $B83E B821- 8D 00 02 STA $0200 ; do it again B824- 20 3E B8 JSR $B83E ; got the same result? B827- CD 00 02 CMP $0200 ; apparently "no" is the correct answer B82A- D0 0F BNE $B83B ; try again B82C- C6 4F DEC $4F B82E- D0 F4 BNE $B824 ; give up B830- A9 08 LDA #$08 B832- 8D 7A B7 STA $B77A B835- 8D F4 03 STA $03F4 ; jump to The Badlands B838- 4C 6B B7 JMP $B76B ; success path ($B82A branches here) -- ; continue to real multi-sector read ; routine B83B- 4C 93 B7 JMP $B793 ; main subroutine starts here -- looks ; for the standard address prologue B83E- AE E9 B7 LDX $B7E9 B841- BD 8C C0 LDA $C08C,X B844- 10 FB BPL $B841 B846- C9 D5 CMP #$D5 B848- D0 F7 BNE $B841 B84A- EA NOP B84B- EA NOP B84C- BD 8C C0 LDA $C08C,X B84F- 10 FB BPL $B84C B851- C9 AA CMP #$AA B853- D0 F1 BNE $B846 B855- EA NOP B856- EA NOP B857- BD 8C C0 LDA $C08C,X B85A- 10 FB BPL $B857 B85C- C9 96 CMP #$96 B85E- D0 E1 BNE $B841 B860- 48 PHA B861- 68 PLA ; skips over the first half of the ; address field B862- A0 04 LDY #$04 B864- BD 8C C0 LDA $C08C,X B867- 10 FB BPL $B864 B869- 48 PHA B86A- 68 PLA B86B- 88 DEY B86C- D0 F6 BNE $B864 ; look for track number 0 B86E- BD 8C C0 LDA $C08C,X B871- 10 FB BPL $B86E B873- C9 AA CMP #$AA B875- D0 CA BNE $B841 B877- 48 PHA B878- 68 PLA ; look for sector number 0 B879- BD 8C C0 LDA $C08C,X B87C- 10 FB BPL $B879 B87E- C9 AA CMP #$AA B880- D0 BF BNE $B841 ; skip the rest of the address field, ; then get the value of the raw nibble ; that follows B882- A0 05 LDY #$05 B884- BD 8C C0 LDA $C08C,X B887- 10 FB BPL $B884 B889- 48 PHA B88A- 68 PLA B88B- 88 DEY B88C- D0 F6 BNE $B884 B88E- 60 RTS Aha! The original disk has two address fields for T00,S00. One of them is the start of the actual sector data. The other one is a decoy that has an address field but no data field. The raw nibbles immediately following the two address prologues are different, and this routine checks to ensure that they are different. The routine in the disk controller ROM (usually at $C65C) that looks for track 0 sector 0 will ignore the decoy if it happens to find it before the real one. (Technically, it will look for the data field, not find it in a reasonable time frame, and start over, and eventually it will find the real address field as the disk continues to spin.) This decoy is apparently enough to fool bit copy programs. This is all very interesting -- and it explains why my bit copy would just hang during boot -- but it doesn't get me any closer to understanding this disk's custom RWTS. Let's back up. *B793L B793- 4C 00 B8 JMP $B800 B796- AD E4 B7 LDA $B7E4 B799- 20 B5 B7 JSR $B7B5 Ignoring the JMP for the moment, the multi-sector read routine calls the standard $B7B5 entry point to actually read a single sector. *B7B5L ; this is normal B7B5- 08 PHP B7B6- 78 SEI ; definitely not normal (usually $BD00) B7B7- 20 00 BA JSR $BA00 ; the rest is all normal B7BA- B0 03 BCS $B7BF B7BC- 28 PLP B7BD- 18 CLC B7BE- 60 RTS B7BF- 28 PLP B7C0- 38 SEC B7C1- 60 RTS That explains why I couldn't find the RWTS code I expected in the location I expected. This RWTS is laid out completely differently in memory than the standard DOS 3.3 RWTS. Even the entry point is different ($BA00 instead of $BD00). *BA00L BA00- 85 48 STA $48 BA02- 84 49 STY $49 BA04- A0 02 LDY #$02 BA06- 8C F8 06 STY $06F8 BA09- A0 04 LDY #$04 BA0B- 8C F8 04 STY $04F8 BA0E- A0 01 LDY #$01 BA10- B1 48 LDA ($48),Y BA12- AA TAX BA13- A0 0F LDY #$0F BA15- D1 48 CMP ($48),Y BA17- F0 1B BEQ $BA34 Yup, that looks like an RWTS entry point. Oh, and remember that weird code at $B6F0 that set two zero page locations for no apparent reason? Here's the reason: the RWTS uses them. (I've seen this pattern before, too.) After seconds of furious investigation, I found the RWTS code that looks for the data prologue: *BDE1L BDE1- BD 8C C0 LDA $C08C,X BDE4- 10 FB BPL $BDE1 BDE6- 49 D5 EOR #$D5 BDE8- D0 F4 BNE $BDDE BDEA- BD 8C C0 LDA $C08C,X BDED- 10 FB BPL $BDEA BDEF- C5 31 CMP $31 <-- ! BDF1- D0 F3 BNE $BDE6 BDF3- A0 56 LDY #$56 BDF5- BD 8C C0 LDA $C08C,X BDF8- 10 FB BPL $BDF5 BDFA- C5 4E CMP $4E <-- ! BDFC- D0 E8 BNE $BDE6 And there it is, in living color: this RWTS uses two magic zero page values to find the data prologue while it's reading a sector from disk. Why? Because f--- you, that's why. Because it makes the extracted RWTS useless without initializing the magic zero page location with the right magic number. Automated RWTS extraction programs wouldn't find this. If I load this RWTS into Advanced Demuffin, it will not be able to read the original disk, because the RWTS itself is not what initializes the magic zero page location. I can save this RWTS into a separate file, but I won't be able to use it in Advanced Demuffin without an IOB module. *C500G ... ]BLOAD BOOT1,A$2600 ]BSAVE RWTS,A$2800,L$800 Now then... What's an IOB module? Well, the author of Advanced Demuffin anticipated that he couldn't anticipate everything, so he made the program extensible. Quoting from the Advanced Demuffin softdocs: --v-- An IOB module is an interface for the source RWTS. Advanced Demuffin uses the IOB module to set up the IOB table and jump to RWTS. The IOB module is stored from $1400-$14FB. When Advanced Demuffin loads in a IOB module, it reads the first sector of the file off the track-sector list and stores it at $13FC-$14FB. When Advanced Demuffin wants to read a sector it JSRs to the IOB module with the phase number, sector number, and the page number stored in the A, Y and X registers respectively. Since the source drive always has to be drive one, Advanced Demuffin can make the IOB module very compact. After it gets the page,track and sector Advanced Demuffin sets up the IOB for RWTS using this infor- mation, and JMPs to RWTS. (It jumps instead of JSRing, because it lets the RWTS do the RTS.) Here is a list of the IOB module that is built in to Advanced Demuffin: ; Convert phase # to track # 1400- 4A LSR ; Store track number 1401- 8D 22 0F STA $0F22 ; Store sector number 1404- 8C 23 0F STY $0F23 ; Store page number ; [note: original docs have incorrect ; hex opcode on this line] 1407- 8E 27 0F STX $0F27 140A- A9 01 LDA #$01 ; Store the drive number 140C- 8D 20 0F STA $0F20 ; Store the read code 140F- 8D 2A 0F STA $0F2A ; With high byte of IOB 1412- A9 0F LDA #$0F ; With low byte of IOB 1414- A0 1E LDY #$1E ; Goto RWTS 1416- 4C 00 BD JMP $BD00 --^-- Basically, Advanced Demuffin only knows how to call a custom RWTS if it 1. is loaded at $B800..$BFFF 2. uses a standard RWTS parameter table 3. has an entry point at $BD00 that takes the address of the parameter tables in A and Y 4. doesn't require initialization As it turns out, that covers a *lot* of copy protected disks, but it doesn't cover this one. This disk fails assumption #3 (the entry point is at $BA00, not $BD00) and #4 (the RWTS relies on the values of zero page $31 and $4E, which are initialized outside the RWTS). So, let's make an IOB module. ; Most of this is identical to the ; standard IOB module that comes with ; Advanced Demuffin (explained above). 1400- 4A LSR 1401- 8D 22 0F STA $0F22 1404- 8C 23 0F STY $0F23 1407- 8E 27 0F STX $0F27 140A- A9 01 LDA #$01 140C- 8D 20 0F STA $0F20 140F- 8D 2A 0F STA $0F2A ; initialize the magic zero page values 1412- A9 AA LDA #$AA 1414- 85 31 STA $31 1416- A9 AD LDA #$AD 1418- 85 4E STA $4E ; get the address of the RWTS parameter ; table at $0F1E and call the RWTS at ; its non-standard entry point, $BA00 141A- A9 0F LDA #$0F 141C- A0 1E LDY #$1E 141E- 4C 00 BA JMP $BA00 Wait wait wait... I've made this mistake before. This IOB module won't work. Advanced Demuffin will crash. Learn from your mistakes so you have the opportunity to make interesting new ones. Let's back up. *B793L B793- 4C 00 B8 JMP $B800 B796- AD E4 B7 LDA $B7E4 B799- 20 B5 B7 JSR $B7B5 That "JMP $B800" instruction gets replaced immediately at $B800. B800- A9 AC LDA #$AC B802- 8D 93 B7 STA $B793 B805- A9 E5 LDA #$E5 B807- 8D 94 B7 STA $B794 B80A- A9 B7 LDA #$B7 B80C- 8D 95 B7 STA $B795 So, the routine at $B793 ends up looking like this: B793- AC E5 B7 LDY $B7E5 B796- AD E4 B7 LDA $B7E4 B799- 20 B5 B7 JSR $B7B5 Perfectly ordinary, no? Actually, no. Here's what it looks like on an ordinary (unprotected) DOS 3.3 disk. B793- AD E5 B7 LDA $B7E5 B796- AC E4 B7 LDY $B7E4 B799- 20 B5 B7 JSR $B7B5 Spot the difference. Go ahead, I'll wait. A and Y get passed through to the RWTS entry point, which is usually at $BD00 but on this disk is at $BA00. DOS 3.3 disk: *BD00L BD00- 84 48 STY $48 BD02- 85 49 STA $49 This disk: *BA00L BA00- 85 48 STA $48 BA02- 84 49 STY $49 Now do you see it? On a normal disk, the Y register holds the low byte of the RWTS parameter table address, and the accumulator holds the high byte. But on this disk, those are reversed; the accumulator holds the low byte, and the Y register holds the high byte. Why? Because f--- you, that's why. Of course, the IOB module I created to interface with this RWTS was still putting the low byte in Y and the high byte in A, so the RWTS was reading a completely bogus parameter table and God only knows what happened next. (Thank goodness the original disk was write-protected.) I need to make one little change to my IOB module. 1400- 4A LSR 1401- 8D 22 0F STA $0F22 1404- 8C 23 0F STY $0F23 1407- 8E 27 0F STX $0F27 140A- A9 01 LDA #$01 140C- 8D 20 0F STA $0F20 140F- 8D 2A 0F STA $0F2A 1412- A9 AA LDA #$AA 1414- 85 31 STA $31 1416- A9 AD LDA #$AD 1418- 85 4E STA $4E 141A- A0 0F LDY #$0F ; Y=high 141C- A9 1E LDA #$1E ; A=low 141E- 4C 00 BA JMP $BA00 *BSAVE IOB,A$1400,L$FB Now let's go. *BRUN ADVANCED DEMUFFIN 1.5 [press "5" to switch to slot 5] [press "R" to load a new RWTS module] --> At $B8, load "RWTS" from drive 1 [press "I" to load a new IOB module] --> load "IOB SWAPPED" from drive 1 [press "6" to switch to slot 6] [press "C" to convert disk] --v-- ADVANCED DEMUFFIN 1.5 (C) 1983, 2014 ORIGINAL BY THE STACK UPDATES BY 4AM ======================================= TRK:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0:...RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC1:...RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC2:...RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC3:...RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC4:...RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC5:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC6:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC7:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC8:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC9:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCA:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCB:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCC:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCD:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCE:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCF:..RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ======================================= 16SC $00,$00-$22,$0F BY1.0 S6,D1->S6,D2 --^-- Make no mistake: this is definitely progress. I have converted a little more than two tracks, which means that the RWTS I extracted *can* read (at least part of) the disk, and the IOB module I created *can* call the RWTS correctly. But this combination only works from T00,S00 to T02,S04. That track/sector sounds suspiciously familiar. It's the last sector of DOS, and it's the first sector read by the boot1 code. ; relevant boot1 code B73E- A9 02 LDA #$02 B740- 8D EC B7 STA $B7EC B743- A9 04 LDA #$04 B745- 8D ED B7 STA $B7ED After DOS is loaded, I guess the RWTS is modified to look for a different data epilogue sequence. But remember, the third byte of the data epilogue is stored in zero page $4E (initially set up at $B6F0). So the DOS doesn't even need to modify the RWTS code directly; it just changes zero page $4E. Turning to the Copy ][+ nibble editor, it appears that every sector from T02,S05 to T22,S0F uses "D5 AA B5" as the data epilogue. --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 03 START: 38A3 LENGTH: 015F 3880: B5 9A A6 B9 B6 D5 9A A6 VIEW 3888: FC DE AA EB DB DB DB DB ^^^^^^^^ data epilogue 3890: DB DB DB DB DB D7 AA 97 ^^^^^^^^ address prologue 3898: AA AA AB AB AF AB AE AA 38A0: AF FF FF FF FF FF FF FF <-38A3 ^^^^^^^^ address epilogue 38A8: FF D5 AA B5 CD F3 DF D6 ^^^^^^^^ data prologue 38B0: B4 F3 AE AE DF D6 CD ED 38B8: CD FC AE F3 F7 ED B4 96 38C0: D6 DF ED B9 9D 9D DB A7 --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- I need another IOB module. ]PR#5 ... ]BLOAD IOB,A$1400 ]CALL -151 *1417:B5 *1400L 1400- 4A LSR 1401- 8D 22 0F STA $0F22 1404- 8C 23 0F STY $0F23 1407- 8E 27 0F STX $0F27 140A- A9 01 LDA #$01 140C- 8D 20 0F STA $0F20 140F- 8D 2A 0F STA $0F2A 1412- A9 AA LDA #$AA 1414- 85 31 STA $31 1416- A9 B5 LDA #$B5 ; new 1418- 85 4E STA $4E 141A- A0 0F LDY #$0F 141C- A9 1E LDA #$1E 141E- 4C 00 BA JMP $BA00 *BSAVE IOB 3+,A$1400,L$FB [S6,D1=original disk] [S6,D2=partially demuffin'd disk] [S5,D1=my work disk] *BRUN ADVANCED DEMUFFIN 1.5 [press "5" to switch to slot 5] [press "R" to load a new RWTS module] --> At $B8, load "RWTS" from drive 1 [press "I" to load a new IOB module] --> load "IOB 3+" from drive 1 [press "6" to switch to slot 6] [press "C" to convert disk] [press "Y" to change default values] --v-- ADVANCED DEMUFFIN 1.5 (C) 1983, 2014 ORIGINAL BY THE STACK UPDATES BY 4AM ======================================= INPUT ALL VALUES IN HEX SECTORS PER TRACK? (13/16) 16 START TRACK: $02 ^^ important START SECTOR: $05 ^^ also important END TRACK: $22 END SECTOR: $0F INCREMENT: 1 MAX # OF RETRIES: 0 COPY FROM DRIVE 1 TO DRIVE: 2 ======================================= 16SC $02,$05-$22,$0F BY$01 S6,D1->S6,D2 --^-- And here we go... --v-- ADVANCED DEMUFFIN 1.5 (C) 1983, 2014 ORIGINAL BY THE STACK UPDATES BY 4AM ======================================= TRK: ................................. +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0: ................................ SC1: ................................ SC2: ................................ SC3: ................................ SC4: ................................ SC5: ................................. SC6: ................................. SC7: ................................. SC8: ................................. SC9: ................................. SCA: ................................. SCB: ................................. SCC: ................................. SCD: ................................. SCE: ................................. SCF: ................................. ======================================= 16SC $02,$05-$22,$0F BY$01 S6,D1->S6,D2 --^-- This is the power and the genius of Advanced Demuffin. Every disk must be able to read itself. So, let it read itself, then capture the data and write it out in a standard format. ]PR#5 ... ]CATALOG,S6,D2 C1983 DSR^C#254 250 FREE A 008 CLASS A 069 CLASSMATE1 A 056 CLASSMATE2 A 075 CLASSMATE3 A 035 CLASSMATE4 B 003 CHAIN ]RUN CLASS ERROR #6 FILE NOT FOUND Wait, what? Firing up Disk Fixer and pointing it to my newly demuffin'd copy, I see the problem: all of the files on this disk have control characters in their names. --v-- -------------- DISK EDIT -------------- TRACK $11/SECTOR $0F/VOLUME $FE/BYTE$00 --------------------------------------- $00:>00<11 0E 00 00 00 00 00 @QN@@@@@ $08: 00 00 00 13 0F 02 C3 81 @@@SOBC. ^^ Ctrl-A $10: CC C1 D3 D3 A0 A0 A0 A0 LASS $18: A0 A0 A0 A0 A0 A0 A0 A0 $20: A0 A0 A0 A0 A0 A0 A0 A0 $28: A0 A0 A0 A0 08 00 14 0F H@TO $30: 02 C3 81 CC C1 D3 D3 CD BC.LASSM ^^ Ctrl-A $38: C1 D4 C5 B1 A0 A0 A0 A0 ATE1 $40: A0 A0 A0 A0 A0 A0 A0 A0 $48: A0 A0 A0 A0 A0 A0 A0 45 E $50: 00 15 0F 02 C3 81 CC C1 @UOBC.LA ^^ Ctrl-A $58: D3 D3 CD C1 D4 C5 B2 A0 SSMATE2 $60: A0 A0 A0 A0 A0 A0 A0 A0 $68: A0 A0 A0 A0 A0 A0 A0 A0 $70: A0 A0 38 00 16 0F 02 C3 8@VOBC $78: 81 CC C1 D3 D3 CD C1 D4 .LASSMAT ^^ Ctrl-A --------------------------------------- BUFFER 0/SLOT 6/DRIVE 2/MASK OFF/NORMAL --------------------------------------- COMMAND : _ --^-- OK, one thing at a time. I have a non- bootable disk with a standard disk catalog and what appear to be standard, though awkwardly named, files. So let's put a standard DOS on this puppy. I'm not even going to try to patch the DOS from the original disk. The sooner I can forget about that DOS, the better. Using Copy ][+, I can "copy DOS" from a freshly initialized DOS 3.3 disk onto the demuffin'd copy. This function of Copy ][+ just sector-copies tracks 0-2 from one disk to another, but it's easier than setting that up manually in some other copy program. Copy ][+ --> COPY --> DOS --> from slot 6, drive 2 --> to slot 6, drive 1 [S6,D1=demuffin'd copy] [S6,D2=newly formatted DOS 3.3 disk] ...read read read... ...write write write... Now I need to change the boot program to "CLASS". This feature of Copy ][+ just presents a list interface to choose a file from the catalog, then sector-edits T01,S09 to set the name of the program that DOS runs (instead of "HELLO"). Copy ][+ --> CHANGE BOOT PROGRAM --> on slot 6, drive 1 --> CLASS (The catalog listing doesn't actually show the control character, so it looks like I'm changing the boot program from "CLASS" to "CLASS". But it does make the necessary changes.) Rebooting loads DOS (of course, I just put it there), appears to load the CLASS program successfully... then immediately exits to a BASIC prompt. There is still more copy protection. ]PR#6 ... BREAK ]LIST 0 REM ]LIST (no output) Annoying LIST tricks are annoying. (The comment embeds FP, which executes the DOS command "FP", in case you were wondering how listing a program can remove it from memory.) ]LOAD CLASS ]LIST 1, 10 REM CLASSMATE APPLE VERSION FP 15 REM -- CLASS 20 PRINT CHR$ (21) 30 IN$ = "": ONERR GOTO 260 40 IF PEEK (40324) = 173 THEN NEW 50 HOME : GOSUB 270: GOSUB 290: GOSUB 330: GOSUB 300: GOSUB 330: GOSUB 310: GOSUB 330: GOSUB 320: FOR I = 1 TO 3000: NEXT 60 HOME :P = PEEK ( - 1101): IF P < > 6 THEN GOTO 80 70 VTAB 12: HTAB 1: PRINT "ENTE R ALL PROMPT RESPONSES AND L ETTER": HTAB 12: PRINT "GRAD ES IN ";: INVERSE : PRINT "C APS";: NORMAL : FOR I = 1 TO 5000: NEXT BREAK ] Line 40 is the problem. 40324 is $9D84, which is part of the DOS. On a standard DOS (which this disk now has), this byte is "AD" (173). But on the original disk, that DOS routine actually started two bytes earlier (which is why boot1 jumped to $9D82 instead of $9D84). So the byte at $9D84 was actually 183 instead. Interestingly enough, the code only fails if the byte is identical to the standard DOS 3.3 byte. I suppose there weren't a lot of third party DOSes (DOSen?) available that moved the location of the DOS cold start routine. ]0 ]40 ]SAVE CLASS ]RUN Now it gets a bit further, showing a text-based title screen and asking for today's date. But then it does some more disk activity (presumably running one of the other BASIC programs -- I don't see any evidence of any direct sector reads or embedded assembly language) and exits to a BASIC prompt again. It turns out that every single BASIC program on this disk has a similar PEEK-based copy protection. CLASSMATE1: delete line 35 CLASSMATE2: change line 30 to "M=0" CLASSMATE3: change line 30 to "M=0" CLASSMATE4: delete line 35 Each of those filenames has a character after the "C". I also deleted line 0 from each program, so now you can just LIST them. Quod erat liberandum. --------------------------------------- A 4am crack No. 131 ------------------EOF------------------