--------------Math Blaster------------- A 4am crack 2014-08-29 --------------------------------------- "Math Blaster" is a 1983 educational game programmed by Richard Eckert and Janice Davidson, Ph.D. and distributed by Davidson & Associates. It comes on one double-sided disk and uses both sides. COPYA fails miserably and immediately on side A. EDD 4 bit copy gives no errors, but the copy just hangs early in the boot process. Side B appears unprotected. COPYA gives no read errors, and the copy boots an entire DOS and runs a startup program which says "THIS IS THE MATH BLASTER DATA DISK. PLEASE BOOT YOUR PROGRAM DISK." Issuing a CATALOG command at this point shows a standard DOS 3.3 disk catalog with several text files. Back to side A. Turning to my trusty Copy ][+ sector editor, I press "P" to get to the Sector Editor Patcher, and select "DOS 3.3 PATCHED". This option ignores checksum bytes and epilogue sequences -- as long as the address and data prologue are standard ("D5 AA 96" and "D5 AA AD", respectively), this will allow me to read each sector. And lo and behold, it works! I can read the data from every sector on track 0. But everything beyond that is still a mystery. Time for boot tracing with AUTOTRACE. [S6,D1=original disk, side A] [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 This code is important, but it's not obvious why unless you've seen the technique before. Which I think I have. Let me see if I'm right. First, I'll need to let my AUTOTRACE program capture the rest of boot1. ]BRUN AUTOTRACE1 ...reboots slot 6... ...reboots slot 5... SAVING BOOT1 ]BLOAD BOOT1,A$2600 ]CALL -151 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *B92FL B92F- 6C 6C 6C JMP ($6C6C) B932- 6C 6C 6C JMP ($6C6C) B935- 6C 6C 6C JMP ($6C6C) B938- 6C 6C 6C JMP ($6C6C) B93B- 6C 6C 6C JMP ($6C6C) B93E- 6C 6C 6C JMP ($6C6C) Umm, that was not what I was expecting. At all. That's supposed to be the part of the RWTS code that checks for the address epilogue bytes ("DE AA"). But that's not even real code. (For the record, I was trying to show that the RWTS uses those magic zero page values that are initialized at $B6F0. It probably does still do that, but I can't show it yet because this RWTS is... special.) Let's back up. Boot1 starts at $B700. What's at $B700? *B700L B700- A0 1A LDY #$1A B702- B9 00 B7 LDA $B700,Y B705- 49 6C EOR #$6C 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 Well, that explains it. The first thing boot1 does is decrypt itself (well, everything after the first $1A bytes, which is how long the decryption loop is). Unfortunately, there's no room to insert a break after the decryption loop. Fortunately, I don't have to, because the decryption is simple enough that I can replicate it outside of the original boot process. *C500G ; because I overwrote DOS ... ]RENAME BOOT1,BOOT1 ENCRYPTED ]BLOAD BOOT1 ENCRYPTED,A$2600 ]CALL -151 0300- A0 1A LDY #$1A 0302- B9 00 27 LDA $2700,Y 0305- 49 6C EOR #$6C 0307- 99 00 27 STA $2700,Y 030A- C8 INY 030B- D0 F5 BNE $0302 030D- EE 04 03 INC $0304 0310- EE 09 03 INC $0309 0313- AD 09 03 LDA $0309 0316- C9 30 CMP #$30 0318- D0 E8 BNE $0302 031A- 60 RTS *BSAVE DECRYPT BOOT1,A$300,L$1B *300G *BSAVE BOOT1 DECRYPTED,A$2600,L$A00 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *B92FL B92F- 00 BRK B930- 00 BRK B931- 00 BRK B932- 00 BRK B933- 00 BRK B934- 00 BRK B935- 00 BRK This is not going well. Let's back up again. The decrypted code starts at $B71A. What's at $B71A? *B71AL ; RWTS parameter table setup (normal) 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 OK, and it proves that my manual decryption loop worked. But I still don't know why I'm not seeing the RWTS code I expected in the location I expected. 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 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. Never a dull moment in the land of Apple II copy protection. 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. 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. This calls for an IOB module. 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. *C500G ; because I overwrote DOS ... ]CALL -151 ; 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 Now let's tell Advanced Demuffin to use this custom IOB as well as the RWTS we captured from the original disk. For the longest time, I thought that Advanced Demuffin would only load RWTS files from slot 6, drive 1. That's where the original disk was during boot tracing, and that's where it needs to be during demuffining (which is a real word that I totally just made up), and that means I ended up swapping floppy disks like some kind of 24th century time traveling retronaut. Then I re-read the Advanced Demuffin tech notes (which I have included on almost every work disk I've ever produced, but apparently never read in their entirety). And I discovered that it's trivial to tell Advanced Demuffin to load RWTS files from slot 5. Note to self: read the fine manual. [S6,D1=original disk, side A] [S6,D2=blank disk] [S5,D1=my work disk] ]PR#6 ]BRUN ADVANCED DEMUFFIN 1.1 --> EXIT TO MONITOR *F1F:50 ; use slot 5 *800G ; resume --> LOAD NEW RWTS MODULE At $B8, load "RWTS" from drive 1 --> LOAD NEW IOB MODULE "IOB" from drive 1 --> EXIT TO MONITOR *F1F:60 ; use slot 6 *800G ; resume --> FORMAT TARGET DISK ...grind grind grind... --> CONVERT DISK This disk is 16 sectors, and the default options (copy the entire disk, all tracks, all sectors) don't need to be changed unless something goes horribly wrong. I press RETURN to start the conversion process, and... Advanced Demuffin crashes. Wait, what? Specifically, it crashes the very first time it tries to read a sector from the original disk. That probably means that (a) the RWTS code I extracted from the original disk (and decrypted) is somehow corrupted or incomplete, or (b) my IOB module is calling that RWTS incorrectly Since decryption of executable code is generally an all-or-nothing affair, and I've already listed through much of it and confirmed that it contains real 6502 assembly language code (for example, to find the data prologue), I'm going to go with (b) my IOB module is incorrect. But how? What did I miss? I don't know. I sat on this for several days, stuck right here. Then I took another look at the boot1 code from the original disk. ]PR#5 ... ]BLOAD BOOT1 DECRYPTED,A$2600 ]CALL -151 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *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 a new IOB module. *C500G ... ]BLOAD IOB,A$1400 ]CALL -151 *141A:A0 *141C:A9 *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 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 SWAPPED,A$1400,L$FB And here we go again. [S6,D1=original disk, side A] [S6,D2=blank disk] [S5,D1=my work disk] *C500G ... ]BRUN ADVANCED DEMUFFIN 1.1 --> EXIT TO MONITOR *F1F:50 ; use slot 5 *800G ; resume --> LOAD NEW RWTS MODULE At $B8, load "RWTS" from drive 1 --> LOAD NEW IOB MODULE "IOB SWAPPED" from drive 1 --> EXIT TO MONITOR *F1F:60 ; use slot 6 *800G ; resume --> CONVERT DISK --v-- ADVANCED DEMUFFIN 1.1 - COPYRIGHT 1983 WRITTEN BY THE STACK -CORRUPT COMPUTING =======PRESS ANY KEY TO CONTINUE======= TRK:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC1:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC2:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC3:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC4:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC5:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC6:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC7:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC8:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SC9:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCA:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCB:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCC:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCD:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCE:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR SCF:RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ======================================= 16 SC $00,$00 TO $22,$0F BY $01 TO DRV2 --^-- Well, technically it didn't crash. But it didn't actually work either. Those "R" flags? Read errors. Every track, every sector. Remember when I said the default options don't need to be changed unless something goes horribly wrong? (Seriously, I just said that, like, a minute ago.) This is what "horribly wrong" looks like. The RWTS on this disk can't read... the disk. Seriously, RWTS, you had one job... Let's back up. But to where? Either my IOB is still incorrect (in a new and exciting way that I haven't figured out yet), or my extracted RWTS is incorrect. But they both look fine. I... I am at a loss. ~ [...time passes...] [...time passes...] [...it is pitch black...you are likely to be eaten by a grue...] T H R E E M O N T H S P A S S ...and I am no closer to a solution... ~ OK, I picked this up again. This is now the equivalent of a "cold case" for a detective -- an old, half-finished crack that has gone nowhere. On a lark, and perhaps out of sheer frustration, I searched the internet for "Advanced Demuffin bug". Wherein I came upon this discussion thread on comp.sys.apple2 from none other than Hot Rod (yes, *the* Hot Rod, from the 1980s): https://groups.google.com/forum/#!msg/c omp.sys.apple2/ZhoZd7edtOY/1cZMYIGjjFMJ Quoting the opening message in full: --v-- Greetings, I don't know if anyone still has cause to use Advanced Demuffin much anymore, but back in the day, it was a main tool used to normalize disks (aka 'crack'). Back in 1986, I happened across a bug in it, wrote it down, and never opted to fix it. I had reason to dust it off and use it again this week, and wouldn't you know it - I hit that same darn bug again (which of course I'd forgotten all about). The gist of it is that Advanced Demuffin loads IOBs and RWTS files by reading the first sector of the file into memory at a page offset of #$FC (e.g. $13FC for an IOB, or $B5FC for an RWTS or wherever the page is set to). So if you set the page to load an RWTS as B6, it would read the first sector starting at $B5FC, not actually $B600. It does this because the first four bytes of the first sector of a file contain the address and length of the file, and so then the first bytes of the actual file will then start on the next page boundary (e.g. $1400, $B600). This wouldn't be so bad (although you could clobber four bytes you weren't expecting on the page preceeding what you thought was the start), except that in doing this, it fails to read the LAST four bytes of the file. The routine that transfers the sector data into the target page is at $15C2. It stores data using the Y-reg as offset, starting at Y=0 and incrementing to 0 again. The problem is that the base address is always the $xxFC address (obtained indirectly via $3E.$3F as lo-byte/hi-byte). So when Y = 0 again, the last address stored to is only $xxFB, and $xxFC.$xxFF (the last four bytes) never get stored. So if the RWTS you're loading has critical data in those last four bytes (like say, the read translate table), it will create all sorts of problems trying to convert a disk. This is exactly the issue I hit in 1986, and then again this week. Fixing it would be possible, but cumbersome, as there's no really good patch space inline. One approach would be to relocate the routine that's at $15C2 (e.g. move it out to $1D00) and patch it with an extra loop that just gets those last four bytes. Might make a good 1.2 version. Anyway, I wanted to mention it so at least a reference to the info existed. Anyone end up with a copy of The Stack's source code? ]HR --^-- Hot Rod claims that Advanced Demuffin has a bug: it always fails to load the last 4 bytes of an RWTS file. (Later in the thread, some replies question whether this is always true, or indeed whether this is ever true.) This is normally not a problem, because those bytes are unused. (They sometimes contain patches to DOS 3.3 itself, but they're not used during the normal course of reading sectors from the disk.) But what if the last 4 bytes *were* used by the RWTS? What if an RWTS was arranged... differently? [S5,D1=my work disk] ]PR#5 ... ]BLOAD RWTS,A$2800 ]CALL -151 *FE89G FE93G ; disconnect DOS *B800<2800.2FFFM ; move RWTS into place *BF80.BFFF BF80- 00 00 00 00 00 00 00 00 BF88- 00 00 00 00 00 00 00 00 BF90- 00 00 00 00 00 00 00 01 BF98- 98 99 02 03 9C 04 05 06 BFA0- A0 A1 A2 A3 A4 A5 07 08 BFA8- A8 A9 AA 09 0A 0B 0C 0D BFB0- B0 B1 0E 0F 10 11 12 13 BFB8- B8 14 15 16 17 18 19 1A BFC0- C0 C1 C2 C3 C4 C5 C6 C7 BFC8- C8 C9 CA 1B CC 1C 1D 1E BFD0- D0 D1 D2 1F D4 03 20 21 BFD8- D8 22 23 24 25 26 27 28 BFE0- E0 E1 E2 E3 E4 29 2A 2B BFE8- E8 2C 2D 2E 2F 30 31 32 BFF0- F0 F1 33 34 35 36 37 38 BFF8- F8 39 3A 3B 3C 3D 3E 3F That is a nibble table; it is an integral part of one of the primary functions of any RWTS: converting the nibbles that are actually stored on the disk into the bytes that get stored in memory (and vice versa). It is, in a word, extremely important. That's two words. *C500G ... ]BRUN ADVANCED DEMUFFIN 1.1 --> EXIT TO MONITOR *BFFC:00 00 00 00 ; clear last 4 bytes *F1F:50 ; use slot 5 *800G ; resume --> LOAD NEW RWTS MODULE At $B8, load "RWTS" from drive 1 --> EXIT TO MONITOR *BFFC.BFFF BFFC- 00 00 00 00 Hot Rod was right. There is a bug in Advanced Demuffin. ~ The next chapter of this story centers on Advanced Demuffin itself. I could probably work around this bug, perhaps by making an IOB module that filled in the 4 bytes at $BFFC..$BFFF with the values that this particular RWTS needs. But that's a poor-man's workaround. I want a real solution. I'm going to fix Advanced Demuffin. [S5,D1=my work disk] ]PR#5 ... ]BLOAD ADVANCED DEMUFFIN 1.1 ]CALL -151 For all the times I've used Advanced Demuffin, I've never looked very hard at how it works. I don't even know where to start looking for the RWTS load routine. I don't have the source code. I suppose I'll start at the beginning. *800L 0800- EA NOP 0801- 78 SEI 0802- D8 CLD ; disconnect DOS from output vector 0803- A9 FD LDA #$FD 0805- A0 F0 LDY #$F0 0807- 84 36 STY $36 0809- 85 37 STA $37 ; subroutine sets reset vector to $801 080B- 20 B0 12 JSR $12B0 ; text mode 080E- 20 2F FB JSR $FB2F ; clear screen 0811- 20 58 FC JSR $FC58 ; print header and footer 0814- A2 27 LDX #$27 0816- BD D8 12 LDA $12D8,X 0819- 9D 00 04 STA $0400,X 081C- BD 00 13 LDA $1300,X 081F- 9D 80 04 STA $0480,X 0822- BD 28 13 LDA $1328,X 0825- 9D D0 07 STA $07D0,X 0828- A9 BD LDA #$BD 082A- 9D 00 05 STA $0500,X 082D- 9D 50 07 STA $0750,X 0830- CA DEX 0831- 10 E3 BPL $0816 ; set header and footer margins for ; output vector 0833- A9 03 LDA #$03 0835- 85 22 STA $22 0837- A9 16 LDA #$16 0839- 85 23 STA $23 ; clear rest of screen (excluding and ; footer) 083B- 20 58 FC JSR $FC58 ; print text that follows this ; instruction 083E- 20 63 0D JSR $0D63 0841..08B6 main menu text (high bit 0 except last byte) ; main menu input loop ; X register holds current selection ; (0-4) 08B7- A2 00 LDX #$00 ; subroutine displays inverse bar on ; current selection 08B9- 20 84 0D JSR $0D84 ; subroutine waits for keypress (ESC is ; handled within subroutine, all other ; keys are returned in accumulator) 08BC- 20 51 0D JSR $0D51 ; RETURN key branches to $08DF 08BF- C9 8D CMP #$8D 08C1- F0 1C BEQ $08DF ; <- key branches to $08D5 08C3- C9 88 CMP #$88 08C5- F0 0E BEQ $08D5 ; -> key falls through, all other keys ; branch back to $08B9 08C7- C9 95 CMP #$95 08C9- D0 EE BNE $08B9 ; handle -> keypress ; "deselect" current selection by ; printing normal text over inverse bar 08CB- 20 97 0D JSR $0D97 ; X is index of current selection (0-4) ; so X=X+1 with wrap-around to 0 08CE- E8 INX 08CF- E0 05 CPX #$05 08D1- 90 E6 BCC $08B9 08D3- B0 E2 BCS $08B7 ; handle <- keypress ; "deselect" current selection by ; printing normal text over inverse bar 08D5- 20 97 0D JSR $0D97 ; X is index of current selection (0-4) ; so X=X-1 (with wrap-around to 4) 08D8- CA DEX 08D9- 10 DE BPL $08B9 08DB- A2 04 LDX #$04 08DD- D0 DA BNE $08B9 ; handle RETURN keypress first, clear ; screen (excluding header and footer) 08DF- 20 58 FC JSR $FC58 ; if current selection is not "EXIT TO ; MONITOR", branch to $08F3 08E2- E0 04 CPX #$04 08E4- D0 0D BNE $08F3 ; handle "EXIT TO MONITOR" text mode 08E6- 20 2F FB JSR $FB2F ; clear screen (including header and ; footer, which get reset in $FB2F) 08E9- 20 58 FC JSR $FC58 08EC- 58 CLI ; subroutine sets reset vector to $801 08ED- 20 B0 12 JSR $12B0 ; jump to monitor 08F0- 4C 59 FF JMP $FF59 ; if current selection is not "FORMAT ; TARGET DISK", branch to $08FA 08F3- E0 03 CPX #$03 08F5- D0 03 BNE $08FA ; jump to routine that handles format 08F7- 4C 9B 0F JMP $0F9B ; if current selection is not "LOAD NEW ; IOB MODULE", branch to $0901 08FA- E0 02 CPX #$02 08FC- D0 03 BNE $0901 ; jump to routine that handles loading ; an IOB module 08FE- 4C 00 11 JMP $1100 ; if current selection is not "LOAD NEW ; RWTS MODULE", branch to $0908 0901- E0 01 CPX #$01 0903- D0 03 BNE $0908 ; jump to routine that handles loading ; an RWTS module 0905- 4C 55 10 JMP $1055 (On a side note, it's a nice change of pace to disassemble code that's not intentionally obfuscated.) *1055L ; clear screen 1055- 20 58 FC JSR $FC58 ; print text that follows this ; instruction 1058- 20 63 0D JSR $0D63 105B..109D "PAGE TO LOAD AT..." text (high bit 0 except last byte) ; input subroutine for first digit 109E- 20 C0 0E JSR $0EC0 10A1- B0 FB BCS $109E ; range check 10A3- C9 02 CMP #$02 10A5- 90 F7 BCC $109E 10A7- C9 0C CMP #$0C 10A9- B0 F3 BCS $109E 10AB- 20 F9 0E JSR $0EF9 10AE- 0A ASL 10AF- 0A ASL 10B0- 0A ASL 10B1- 0A ASL ; store partial value in zero page $4C 10B2- 85 4C STA $4C ; input subroutine for second digit 10B4- 20 C0 0E JSR $0EC0 10B7- 90 0A BCC $10C3 ; handle <- keypress 10B9- C9 88 CMP #$88 10BB- D0 F7 BNE $10B4 10BD- 20 ED FD JSR $FDED 10C0- 4C 9E 10 JMP $109E ; store full value in zero page $4C 10C3- 20 F9 0E JSR $0EF9 10C6- 05 4C ORA $4C 10C8- 85 4C STA $4C ; print text that follows this ; instruction 10CA- 20 63 0D JSR $0D63 10CD..10CF three carriage returns (high bit 0 except last byte) 10D0- 20 30 11 JSR $1130 *1130L ; print 1130- 20 63 0D JSR $0D63 1133..1168 "FILE TO LOAD" prompt ; handle character input for filename 1169- 20 51 0D JSR $0D51 116C- C9 88 CMP #$88 116E- F0 1B BEQ $118B 1170- C9 8D CMP #$8D 1172- F0 27 BEQ $119B 1174- C9 A0 CMP #$A0 1176- 90 F1 BCC $1169 1178- E0 00 CPX #$00 117A- D0 04 BNE $1180 117C- C9 C0 CMP #$C0 117E- 90 E9 BCC $1169 1180- 20 ED FD JSR $FDED ; store filename in $0200 1183- 9D 00 02 STA $0200,X 1186- E8 INX 1187- E0 1E CPX #$1E 1189- 90 D9 BCC $1164 ... ; print 11AC- 20 63 0D JSR $0D63 11AF..11B1 ",D_" prompt ; handle input for drive number 11B3- 20 51 0D JSR $0D51 11B6- C9 B1 CMP #$B1 11B8- F0 04 BEQ $11BE 11BA- C9 B2 CMP #$B2 11BC- D0 F5 BNE $11B3 11BE- 20 ED FD JSR $FDED ; munge and store directly in RWTS ; parameter table 11C1- 29 03 AND #$03 11C3- 8D 20 0F STA $0F20 Advanced Demuffin doesn't use the standard DOS 3.3 BLOAD function, on the theory that by the time you need to load an RWTS file, you may not have a working DOS in memory anymore. Also, to avoid disturbing any other memory, Advanced Demuffin loads the RWTS file into its final location immediately. (It has its own copy of a standard RWTS, relocated to $1500.) So it does everything manually: reading the disk catalog to find the file's first sector, then reading that sector to get the list of tracks and sectors, then reading each sector into memory one at a time. ; read T11,S00 into $1F00..$1FFF 11C6- A9 1F LDA #$1F 11C8- 85 4B STA $4B 11CA- A9 11 LDA #$11 11CC- A0 00 LDY #$00 11CE- 20 34 12 JSR $1234 ; find next directory sector (first one ; is usually T11,S0F, then that points ; to T11,S0E, and so on) and read it ; into $1F00..$1FFF 11D1- 20 2B 12 JSR $122B 11D4- B0 2E BCS $1204 ; parse the raw catalog listing to find ; the filename you requested 11D6- A9 0B LDA #$0B 11D8- 85 4A STA $4A 11DA- A0 02 LDY #$02 11DC- B1 4A LDA ($4A),Y 11DE- 29 04 AND #$04 11E0- F0 19 BEQ $11FB 11E2- C8 INY 11E3- B1 4A LDA ($4A),Y 11E5- D9 FD 01 CMP $01FD,Y 11E8- D0 11 BNE $11FB 11EA- C8 INY 11EB- C0 21 CPY #$21 11ED- 90 F4 BCC $11E3 11EF- A0 00 LDY #$00 11F1- B1 4A LDA ($4A),Y 11F3- 48 PHA 11F4- C8 INY 11F5- B1 4A LDA ($4A),Y 11F7- A8 TAY 11F8- 68 PLA 11F8- 68 PLA ; found it 11F9- 10 39 BPL $1234 ; didn't find it 11FB- A5 4A LDA $4A 11FD- 18 CLC 11FE- 69 23 ADC #$23 ; try next sector 1200- F0 CF BEQ $11D1 ; try next file in this sector 1202- D0 D4 BNE $11D8 ; print 1204- 20 63 0D JSR $0D63 1207..1227 "NO SUCH BINARY FILE EXISTS" ; start over 1228- 4C 30 11 JMP $1130 ... ; read track/sector list of the RWTS ; file into $1F00..$1FFF 1234- A2 1F LDX #$1F 1236- 8E 27 0F STX $0F27 1239- A2 00 LDX #$00 123B- 8E 26 0F STX $0F26 123E- 8D 22 0F STA $0F22 1241- 8C 23 0F STY $0F23 1244- A9 01 LDA #$01 1246- 8D 2A 0F STA $0F2A 1249- 20 A9 0D JSR $0DA9 124C- B0 06 BCS $1254 124E- A2 00 LDX #$00 1250- 8E 26 0F STX $0F26 1253- 60 RTS If all goes well, by the time we return from $1130, the page $1F00..$1FFF looks something like this: 1F00- 00 00 00 00 00 00 00 00 1F08- 00 00 00 00 1E 01 1E 00 1F10- 1F 0F 1F 0E 1F 0D 1F 0C 1F18- 1F 0B 1F 0A 1F 09 00 00 (The rest of the page is all zeroes.) That's a track/sector list, stored in the first sector of every DOS 3.3 file. Starting at $1F0C, it contains pairs of [track,sector] in order, followed by a pair of zeroes. So if this RWTS file expects to be loaded at $B800, T1E,S01 contains the code from $B800..$B8FF; T1E,S00 contains $B900..$B9FF; T1F,S0F contains $BA00..$BAFF; and so forth. The subroutine at $1130 was called from $10D0, so let's continue at $10D3. Now we have a loop that reads each of those sectors from disk into the appropriate page in memory. (Remember, the high byte of the starting address is in zero page $4C.) *10D3L ; get high byte of target address 10D3- A5 4C LDA $4C ; store it in RWTS parameter table 10D5- 8D 27 0F STA $0F27 ; minus 1, for reasons that will become ; clear shortly 10D8- CE 27 0F DEC $0F27 ; get a track and sector from the list 10DB- A2 10 LDX #$10 10DD- 86 4A STX $4A 10DF- BD FD 1E LDA $1EFD,X ; Y register has track number 10E2- A8 TAY ; accumulator has sector number 10E3- BD FC 1E LDA $1EFC,X ; branch if out of sectors 10E6- F0 15 BEQ $10FD ; actually read the sector into memory ; 4 bytes earlier, to compensate for ; the fact that every DOS 3.3 binary ; file includes a 4-byte header (the ; starting address and program length) ; ; so, for example, the first sector of ; an RWTS file starting at $B800 would ; actually be read into $B7FC, so that ; the real data ends up being loaded at ; $B800 as expected ; ; this saves a bunch of memory moves 10E8- A2 FC LDX #$FC 10EA- 20 3B 12 JSR $123B ; increment target page 10ED- EE 27 0F INC $0F27 ; check if we've run up against the top ; of writeable memory ($C000 and above ; are not writeable) 10F0- AD 27 0F LDA $0F27 10F3- C9 BF CMP #$BF ; out of space, don't load the rest of ; the file even if there's more to load 10F5- B0 06 BCS $10FD ; increment our index into the ; track/sector list 10F7- A6 4A LDX $4A 10F9- E8 INX 10FA- E8 INX ; this should always branch unless you ; loaded a ridiculously large RWTS file ; for some reason 10FB- D0 E0 BNE $10DD ; all done, jump back to main menu 10FD- 4C 03 08 JMP $0803 Hot Rod was slightly wrong but mostly right. There is a bug in this loop, but it only manifests itself with RWTS files that are loaded into the highest writeable part of main memory (just below $C000). Suppose you have an RWTS file that is loaded at $3800 and is $0800 bytes in length. (I've seen real copy protected disks like this.) This file will take up 10 sectors on disk. S01: track/sector list S02: 4-byte header (starting address and length), followed by $FC bytes of data that should be stored in $3800..$38FB S03: $100 bytes of data that should be stored in $38FC..$39FB S04: $100 bytes of data that should be stored in $39FC..$3AFB ... S09: $100 bytes of data that should be stored in $3EFC..$3FFB S10: 4 bytes of data that should be stored in $3FFC..$3FFF, followed by $FC bytes of unused space Advanced Demuffin will load S01 into $1F00..$1FFF, then load the rest of the sectors (S02-S10) into $37FC..$40FB. Since it reads each sector directly into its final memory location (minus 4 bytes), it will end up overwriting the 4 bytes from $37FC..$37FF, and the $FC bytes from $4000..$40FB. This is generally not a problem, since there's nothing there of any consequence. Now consider the case of an RWTS file that starts at $B800 and is $0800 bytes long. Again, it will take up 10 sectors on disk. S01: track/sector list S02: 4-byte header (starting address and length), followed by $FC bytes of data that should be stored in $B800..$B8FB S03: $100 bytes of data that should be stored in $B8FC..$B9FB S04: $100 bytes of data that should be stored in $B9FC..$BAFB ... S09: $100 bytes of data that should be stored in $BEFC..$BFFB S10: 4 bytes of data that should be stored in $BFFC..$BFFF, followed by $FC bytes of unused space Again, Advanced Demuffin will load S01 (the track/sector list) into $1F00.. $1FFF. Then it will loop through the next 8 sectors (S02-S09) and load them into $B7FC..$BFFB. And then it will stop, because at $10F0 there is a check to ensure that it doesn't ever write to nonwriteable memory. 10F0- AD 27 0F LDA $0F27 10F3- C9 BF CMP #$BF 10F5- B0 06 BCS $10FD That branch is taken to avoid writing a sector's worth of data to $BFFC..$C0FB (which would indeed cause all sorts of problems, since there are lots of soft switches in the $C0xx range). But that means that it never loads sector 10, so the last 4 bytes of the RWTS file never make it into $BFFC..$BFFF. Now, how do I fix it? Well, according to the TECH NOTES softdocs, Advanced Demuffin has some unused space in the $1D00..$1EFB range. That's plenty of space to put a patch or two. Here's what I came up with: *10D3L ; unchanged 10D3- A5 4C LDA $4C 10D5- 8D 27 0F STA $0F27 10D8- CE 27 0F DEC $0F27 10DB- A2 10 LDX #$10 10DD- 86 4A STX $4A 10DF- BD FD 1E LDA $1EFD,X 10E2- A8 TAY 10E3- BD FC 1E LDA $1EFC,X 10E6- F0 15 BEQ $10FD 10E8- A2 FC LDX #$FC 10EA- 20 3B 12 JSR $123B 10ED- EE 27 0F INC $0F27 ; jump to patch 10F0- 4C 00 1D JMP $1D00 ; now unused 10F3- EA NOP 10F4- EA NOP 10F5- EA NOP 10F6- EA NOP ; unchanged 10F7- A6 4A LDX $4A 10F9- E8 INX 10FA- E8 INX 10FB- D0 E0 BNE $10DD 10FD- 4C 03 08 JMP $0803 And here is the actual patch: *1D00L ; check the target memory address (like ; the original did) 1D00- AD 27 0F LDA $0F27 1D03- C9 BF CMP #$BF ; if we're up against nonwriteable ; memory space, branch 1D05- B0 03 BCS $1D0A ; no problem, go back to read loop 1D07- 4C F7 10 JMP $10F7 ; read into $1F00..$1FFF 1D0A- A9 1F LDA #$1F 1D0C- 8D 27 0F STA $0F27 ; get track and sector of the last ; sector of the RWTS file 1D0F- A6 4A LDX $4A 1D11- BD FF 1E LDA $1EFF,X 1D14- A8 TAY 1D15- BD FE 1E LDA $1EFE,X 1D18- F0 0E BEQ $1D28 ; read the last sector (into $1F00.. ; $1FFF) 1D1A- A2 00 LDX #$00 1D1C- 20 3B 12 JSR $123B ; copy the first 4 bytes into place 1D1F- A2 03 LDX #$03 1D21- BD 00 1F LDA $1F00,X 1D24- 9D FC BF STA $BFFC,X 1D27- CA DEX 1D28- 10 F7 BPL $1D21 ; jump back to end of the read loop to ; return to the main menu 1D2A- 4C FD 10 JMP $10FD I just fixed a 31-year-old bug without the original source code. (While I was in there, I took the opportunity to make a few other minor interface modifications. In particular, I added some keyboard support in the main menu to change the slot number without having to exit to the monitor. Now I can just press "5" to load RWTS and IOB files from slot 5, then press "6" to convert the disk in slot 6. I also added direct keyboard shortcuts for each item in the main menu. For example, you can just press "R" to load an RWTS file or "I" to load an IOB file. Version 1.1 made you move one line at a time with the left and right arrow keys and press RETURN. Oh, and I also added support for the up and down arrow keys, because it's the 21st century and why not.) And so, Advanced Demuffin 1.5 was born. ~ When I left off, I was trying to convert side A of "Math Blaster" to a standard format. I had decrypted the boot1 code and extracted the RWTS. I had written an IOB module (actually two IOB modules, the most recent one called "IOB SWAPPED") to act as a liaison between the non-standard RWTS and Advanced Demuffin. The only thing I didn't have was a version of Advanced Demuffin that could load the entire RWTS file into memory. But now I do. [S6,D1=original disk, side A] [S6,D2=blank disk] [S5,D1=my work disk] ]PR#5 ... ]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. Advanced Demuffin 1.1 failed to read this disk at all (because of the 4-byte RWTS bug). Now that I've fixed that, it can use the RWTS that I extracted (and decrypted) to read the disk... but only up 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: 2CCB LENGTH: 015F 2CA8: 9E 9D BE CB 96 9A B7 AC VIEW 2CB0: A7 DE AA EB DB DB DB DB ^^^^^^^^ data epilogue 2CB8: DB DB DB DB DB D7 AA 97 ^^^^^^^^ address prologue 2CC0: AA AA AB AB AE AA AF AB 2CC8: AF FF FF FF FF FF FF FF <-2CCB ^^^^^^^^ address epilogue 2CD0: FF D5 AA B5 A6 E6 FB D3 ^^^^^^^^ data prologue 2CD8: AD EC AB F3 B5 DE E5 AE 2CE0: 97 B9 D9 E5 FE D6 E5 F3 2CE8: BD AC B2 DF EA A6 DB DF --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- It appears I need to make a *third* IOB module. ]PR#5 ... ]BLOAD IOB SWAPPED,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, side A] [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 --^-- Halle-freaking-lujah. Now look at this: ]PR#5 ... ]CATALOG,S6,D2 C1983 DSR^C#254 281 FREE A 058 HELLO A 032 MATH BLASTER DEMONSTRATION T 002 MATH 1.OBJ T 002 MATH 3.OBJ A 040 MATH BLASTER EDITOR A 081 MATH BLASTER And look at this: ]RUN HELLO ERROR #6 FILE NOT FOUND Wait, what? Firing up my trusty Copy ][+ sector editor 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-- SECTOR EDITOR DRIVE 2 00- 00 11 0E 00 00 00 00 00 ........ 08- 00 00 00 12 0F 02 C8 9A ......H. ^^ 10- C5 CC CC CF A0 A0 A0 A0 ELLO 18- A0 A0 A0 A0 A0 A0 A0 A0 20- A0 A0 A0 A0 A0 A0 A0 A0 28- A0 A0 A0 A0 3A 00 16 0F :... 30- 02 CD 9A C1 D4 C8 A0 C2 .M.ATH B ^^ 38- CC C1 D3 D4 C5 D2 A0 C4 LASTER D 40- C5 CD CF CE D3 D4 D2 C1 EMONSTRA 48- D4 C9 CF CE A0 A0 A0 20 TION 50- 00 08 0F 00 CD 9A C1 D4 ....M.AT ^^ 58- C8 A0 B1 AE CF C2 CA A0 H 1.OBJ 60- A0 A0 A0 A0 A0 A0 A0 A0 68- A0 A0 A0 A0 A0 A0 A0 A0 70- A0 A0 02 00 20 0F 00 CD .. ..M 78- 9A C1 D4 C8 A0 B3 AE CF .ATH 3.O ^^ 80- C2 CA A0 A0 A0 A0 A0 A0 BJ TRACK $11 SECTOR $0F DOS 3.3 [?]-HELP SCREEN --^-- 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 "HELLO". 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 --> HELLO (The catalog listing doesn't actually show the control character, so it looks like I'm changing the boot program from "HELLO" to "HELLO". But it does make the necessary changes.) Rebooting loads DOS (of course, I just put it there), appears to load the HELLO program successfully... then immediately reboots. There is still more copy protection. ]PR#6 ... BREAK ]LIST 10 POKE 104,32: RUN 65535 REM COPYRIGHT 1983 65535 REM DAVIDSON & ASSOCIATES According to the framed Beagle Bros. "Peeks, Pokes and Pointers" chart that hangs above my desk and reminds me that technical writing should be wondrous, useful, and fun (but not always in that order), zero page 104 ($68) is the high byte of the starting address of the Applesoft BASIC program in memory. Which means that this HELLO program contains an entirely separate, entirely hidden BASIC program within it. ]POKE 104,32 ]LIST 10 REM 400 IF PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 1000 402 POKE 216,0: ONERR GOTO 100 0 520 K = 768:L = 1000 530 FOR I = K TO L: READ J: POKE I,J: NEXT 595 POKE 765,32 650 DATA 76, 55, 3, 164, 1, 17 3, 48, 192, 230, 2, 208, 5, 230, 3, 208, 5, 96, 234, 76, 21, 3, 136, 240, 5 670 DATA 76, 27, 3, 208, 235, 164, 0, 173, 48, 192, 230, 2 , 208, 5, 230, 3, 208, 5, 96 , 234, 76, 47, 3, 136 690 DATA 240, 209, 76, 53, 3, 208, 235, 173, 255, 2, 10, 1 68, 185, 127, 3, 133, 0, 173 , 253, 2, 74, 240, 4, 70 710 DATA 0, 208, 249, 185, 127 , 3, 56, 229, 0, 133, 1, 200 , 185, 127, 3, 101, 0, 133, 0, 169, 0, 56, 237, 254 730 DATA 2, 133, 3, 169, 0, 13 3, 2, 165, 1, 208, 152, 234, 234, 76, 112, 3, 230, 2, 20 8, 5, 230, 3, 208, 5 750 DATA 96, 234, 76, 125, 3, 208, 236, 0, 0, 246, 246, 23 2, 232, 219, 219, 207, 207, 195, 195, 184, 184, 174, 174 , 164 770 DATA 164, 155, 155, 146, 1 46, 138, 138, 130, 130, 123, 123, 116, 116, 109, 110, 10 3, 104, 97, 98, 92, 92, 87, 87, 82 790 DATA 82, 77, 78, 73, 73, 6 9, 69, 65, 65, 61, 62, 58, 5 8, 54, 55, 51, 52, 48, 49, 4 6, 46, 43, 44, 41 810 DATA 41, 38, 39, 36, 37, 3 4, 35, 32, 33, 30, 31, 29, 2 9, 27, 28, 26, 26, 24, 25, 2 3, 23, 21, 22, 20 830 DATA 21, 19, 20, 18, 18, 1 7, 17, 16, 16, 15, 16, 14, 1 5, 255, 255, 255, 0 900 POKE 2049,104: POKE 2050,16 8: POKE 2051,104: POKE 2052, 166: POKE 2053,223: POKE 205 4,154 910 POKE 2055,72: POKE 2056,152 : POKE 2057,72: POKE 2058,96 911 PRINT CHR$ (4);"OPEN MATH 1.OBJ": PRINT CHR$ (4);"REA D MATH 1.OBJ": INPUT YES,NO, MAYBE,YY,ZZ: PRINT CHR$ (4) ;"CLOSE MATH 1.OBJ" 915 POKE YY,ZZ: GOTO 10 1000 PRINT CHR$ (4)"PR#6" But wait... there's more. I mean, there has to be more. Other than creating a little assembly language routine at 768 ($300), this program doesn't actually *do* anything. It doesn't even call the assembly language routine it creates. It pokes and pokes and... GOTO 10? How does that do, well, anything? Line 911 reads a series of values from a text file ("MATH 1.OBJ", although I'm pretty sure there are some control characters in there somewhere). Looks innocuous, until line 915 where you realize that it's using those values to POKE something. Using Copy ][+'s "view file as text" function, here are the entire contents of "MATH 1.OBJ": --v-- 8131 -936 6084 104 64 --^-- The first three values go into the variables YES, NO, and MAYBE. (Really.) The last two go into YY and ZZ, and that's what gets POKE'd in line 915. Hey, poking address 104. That sounds familiar... ]POKE 104,64 ]LIST 10 CALL YES: CALL NO: HGR : CALL MAYBE 20 VTAB 22: HTAB 11 30 PRINT "2( LOADING PROGRAM )" 40 P = PEEK ( - 16384) 50 IF P = 196 THEN PRINT CHR$ (4);"RUN MATH BLASTER DEMONS TRATION" 60 IF P = 197 THEN PRINT CHR$ (4);"RUN MATH BLASTER EDITOR " 70 PRINT CHR$ (4);"RUN MATH BL ASTER" Un-freaking-believable. This BASIC program changes the starting memory address of the currently running BASIC program and re-runs itself. Twice. Apple-ception! Anyway, back to the... I don't even know what to call it. Back to the second program-within-a-program, I guess. ]POKE 104,32 ]LIST 400 400 IF PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 1000 This is the problem. 40324 is $9D84, which (reaching waaay back to the beginning of this journey when I decrypted the boot1 code) is *not* the entry point to the boot2 code. On a standard DOS 3.3 disk, it is, but on this disk, the entry point is at $9D82 instead. So this line of BASIC is spot-checking the DOS in memory to ensure that we booted from the original non-standard DOS. (Hint: we didn't, because I just replaced that DOS with a standard DOS 3.3.) It also checks 47094 ($B7F6), which is part of the RWTS parameter table. On a standard DOS 3.3 disk, this location would be the actual volume number found the last time the RWTS successfully read a sector. Apparently the original disk's RWTS (which, again, I just replaced with a standard DOS 3.3 RWTS) always sets it to 0 instead. Let's see if I can skip past it... ]RUN 402 Success! The program loads and runs all the way up to the main menu. But how can I patch this program? It's not even the real program; it's the second-level program-within-a-program. There's a program above it and another program below it, all self-contained in the same "A" type file. If I delete the line, all of that will be ruined. I'm going to have to hack the Applesoft opcodes from the monitor. ]PR#6 ... ]POKE 104,32 ]CALL-151 *2000.203F 2000- 00 07 20 0A 00 B2 00 2A 2008- 20 90 01 AD E2 28 34 30 ^^ ^^^^^ ^^^^^ IF PEEK( 4 0 2010- 33 32 34 29 D0 31 37 33 ^^^^^^^^^^^ ^^ ^^^^^^^^ 3 2 4 ) = 1 7 3 2018- CE E2 28 34 37 30 39 34 ^^ ^^^^^ ^^^^^^^^^^^^^^ OR PEEK( 4 7 0 9 4 2020- 29 D1 CF 30 C4 31 30 30 ^^ ^^^^^ ^^ ^^ ^^^^^^^^ ) < > 0 THEN 1 0 0 2028- 30 00 3C 20 92 01 B9 32 ^^ 0 2030- 31 36 2C 30 3A A5 AB 31 2038- 30 30 30 00 4D 20 08 02 Looking at address $2005, it appears that the opcode for a "REM" statement is $B2. Let's try changing the "IF" statement to a "REM" statement. *200B:B2 *3D0G ; return to BASIC prompt ]LIST 400 400 REM PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 10 00 Success! Line 400 is now a comment and shouldn't do any harm. (Listing the rest of the code confirms that this hasn't disturbed the delicate balance of the three programs in memory.) ]RUN Success! It runs without complaint. Now to make this patch permanent. Turning to my trusty Copy ][+ sector editor (version 5.5, the last version that can "follow" files), I press "F" to follow, select "HELLO" from the disk catalog listing, "S" to scan and "H" for hex. Searching for "34 30 33 32 34" (the string "40324" as it's represented in hex within an Applesoft program), I find it on T13,S06. T13,S06,$0C change "AD" to "B2" Success! The disk boots and loads with no complaint. That is, until -- and I am not making this up -- I select a game and try to play it. Then it reboots. There is still more copy protection. ]PR#6 The HELLO program eventually (in the third-level program-within-a-program) runs the BASIC program "MATH BLASTER", which appears to be the entire game. So let's start there. ]LOAD MATH BLASTER ]LIST . . . 404 FOR I = 17000 TO 33000 STEP 20: POKE I, RND (1) * 253: NEXT : PRINT CHR$ (13); CHR$ (4) ;"PR#6" Well that last line certainly looks suspicious. A bit of searching through this 81-sector Applesoft program (AND BOY THAT WAS FUN, LET ME TELL YOU) led me to these two lines, which look both suspicious and familiar: ]LIST 360 360 IF PEEK (40324) = 173 THEN 404 ]LIST 400 400 IF PEEK (47094) < > 0 OR PEEK (40324) = 173 THEN 402 It's the same protection as the HELLO program! It looks at memory location $9D84 as a spot check to make sure we booted from the original disk. (Hint: we didn't.) (Here's a fun fact: I'm almost certain that line 400 has a bug. It should branch to line 404, not 402. Line 404 is the failure path; it destroys random bits of memory and reboots. Line 402 is, incidentally, the success path, and skipping line 401 has no ill effects. So this check doesn't do anything.) At any rate, I should be able to neutralize these checks the same way I did in the HELLO program, by changing the "IF" to a "REM". Firing up my trusty Copy ][+ (5.5) sector editor, I press "F" to follow a file, select "MATH BLASTER" from the catalog, then "S" to scan and "H" for hex. Searching for "34 30 33 32 34" ("40324"), I find it on T03,S06. --v-- SECTOR EDITOR DISK A 00- AD 50 52 24 D1 CF 22 46 -PR$QO"F 08- 52 41 43 54 49 4F 4E 53 RACTIONS 10- 22 CD 43 48 D1 CF 34 C4 "MCHQO4D 18- BA C3 31 32 29 00 33 50 :C12)@3P 20- 68 01 AD E2 28 34 30 33 (A-b(403 ^^ ^^^^^ ^^^^^^^^ IF PEEK( 4 0 3 28- 32 34 29 D0 31 37 33 C4 24)P173D ^^^^^ ^^ ^^ ^^^^^^^^ ^^ 2 4 ) = 1 7 3 THEN 30- 34 30 34 00 39 50 69 01 404@9P)A ^^^^^^^^ 4 0 4 38- B1 00 90 50 6A 01 4A D0 1@.P*AJP 40- 32 32 3A 4B D0 32 36 36 22:KP266 48- 3A 92 34 3A 81 4C D0 31 :.4:.LP1 50- 36 34 C1 31 37 38 3A B0 64A178:0 58- 33 31 38 3A 82 3A A2 32 318:.:"2 60- 32 3A 96 35 3A BA 43 48 2:.5::CH 68- 3B 22 2E 20 13 48 4F 52 ;". SHOR 70- 49 5A 4F 4E 54 41 4C 2C IZONTAL, 78- 20 13 56 45 52 54 49 43 SVERTIC 80- 41 4C 20 4F 52 20 13 4D AL OR SM FILE: MATH BLASTER TRACK $03, SECTOR $6 DOS 3.3 [?]-HELP SCREEN --^-- To neuter the check on line 360: T03,S06,$22 change "AD" to "B2" To neuter the check on line 400 (even though it doesn't appear to function properly): T03,S00,$73 change "AD" to "B2" I searched the entire disk for other instances of "34 30 33 32 34" and found two, one on T0B,S00 and one on T1F,S04. According to the Copy ][+ track/sector map, neither sector is actually in use by any file. This disk doesn't use any raw sector reads, so I suspect these are just remnants of deleted files or previous versions that were still present on the final master disk. After extensive testing, I can find no evidence of further copy protection. Quod erat liberandum. --------------------------------------- A 4am crack No. 121 ------------------EOF------------------