---------------Spell It!--------------- A 4am crack 2014-09-17 --------------------------------------- "Spell It!" is a 1984 educational game distributed by Davidson & Associates. The title page credits Richard Eckert and Janice Davidson, Ph.D. Side B is unprotected. However, side A is uncopyable by any automated method. 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, side A] [S5,D1=my work disk] ]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 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, this 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"), there's a good chance I'll be able to use a tool called Advanced Demuffin (written in 1983 by The Stack) to convert the disk from whatever weird format it uses to store its sector data into a standard disk readable by unprotected DOS 3.3 disks or any other third-party tools. That didn't happen in this case, but a lot of other stuff did happen along the way, so I'll start by explaining the parts that worked. As always, let's start with boot0. ]BLOAD BOOT0,A$800 ]CALL -151 *801L . . all normal, until... . 084A- 4C C0 B6 JMP $B6F0 My AUTOTRACE program warned me about this -- 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'm pretty sure I know why boot0 is setting seemingly random zero page locations. (I've seen this before on other disks.) But I won't be able to verify it until I get a bit further down the rabbit hole. The next part of AUTOTRACE's output is exciting(*), because I just added this automation recently, and here I am already reaping the benefits of it. (*)not guaranteed, excitement may vary ]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 My AUTOTRACE program has captured two copies of the boot1 code. One is encrypted; the other is not. ]BLOAD BOOT1 ENCRYPTED,A$2600 ]CALL -151 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *B700L B700- A0 1A LDY #$1A B702- B9 00 B7 LDA $B700,Y B705- 49 FE EOR #$FE 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 B71A- 70 17 BVS $B733 B71C- 49 70 EOR #$70 B71E- 09 49 ORA #$49 B720- 57 ??? B721- 95 73 STA $73,X B723- 0C ??? B724- FD 57 49 SBC $4957,X B727- 73 ??? B728- 0D FD B7 ORA $B7FD The first thing that boot1 does is decrypt the rest of boot1. Everything from $B71A..$BFFF is encrypted with a simple XOR key, given in $B706. I've seen this pattern before (in "Math "Blaster" and "Bingo Bugglebee Presents Home Alone," just to name two), so I added support for it in AUTOTRACE. Here is the code: ]PR#5 ; because I overwrote DOS ... ]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" 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" 215 PRINT "SAVING BOOT1" 216 PRINT CHR$ (4)"BSAVE BOOT1 ,A$2000,L$A00" . . . 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 And there you have it: automatic decryption of encrypted boot1 code. Kick. Ass. But I still don't have an RWTS file. Let's look at the (now 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 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 245 FREE A 076 SPELL IT! HELLO A 074 SPELL IT! A 058 SPELL IT! DEMONSTRATION A 039 SPELL IT! EDITOR T 002 SPELL 1.OBJ A 002 HELLO ]RUN HELLO 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 12 0F 02 D3 9A @@@ROBS. ^^ Ctrl-Z $10: D0 C5 CC CC A0 C9 D4 A1 PELL IT! $18: A0 C8 C5 CC CC CF A0 A0 HELLO $20: A0 A0 A0 A0 A0 A0 A0 A0 $28: A0 A0 A0 A0 4C 00 17 0F L@WO $30: 02 D3 9A D0 C5 CC CC A0 BS.PELL ^^ Ctrl-Z $38: C9 D4 A1 A0 A0 A0 A0 A0 IT! $40: A0 A0 A0 A0 A0 A0 A0 A0 $48: A0 A0 A0 A0 A0 A0 A0 4A J $50: 00 1C 0F 02 D3 9A D0 C5 @\OBS.PE ^^ Ctrl-Z $58: CC CC A0 C9 D4 A1 A0 C4 LL IT! D $60: C5 CD CF CE D3 D4 D2 C1 EMONSTRA $68: D4 C9 CF CE A0 A0 A0 A0 TION $70: A0 A0 3A 00 20 0F 02 D3 :@ OBS $78: 9A D0 C5 CC CC A0 C9 D4 .PELL IT ^^ Ctrl-Z --------------------------------------- BUFFER 0/SLOT 6/DRIVE 1/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 "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" without the control character). 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), runs the HELLO program, prints "LOADING PROGRAM", runs "SPELL IT! HELLO"... then immediately reboots. There is still more copy protection. ]PR#6 ... BREAK ]LOAD SPELL IT! HELLO ]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 POKE 216,0: ONERR GOTO 2000 0 12 CALL - 936 14 VTAB 10 20 IF PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 20000 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 7780 REM TONE SUBROUTINE 7790 POKE 770,32: POKE 771,74: POKE 772,255: POKE 773,173: POKE 774,48: POKE 775,192: POKE 7 76,136: POKE 777,208: POKE 7 78,5 7800 POKE 779,206: POKE 780,1: POKE 781,3: POKE 782,240: POKE 78 3,9: POKE 784,202: POKE 785, 208: POKE 786,245: POKE 787, 174 7810 POKE 788,0: POKE 789,3: POKE 790,76: POKE 791,5: POKE 792 ,3: POKE 793,32: POKE 794,63 : POKE 795,255: POKE 796,96 7820 PRINT CHR$ (4);"OPEN SPEL L 1.OBJ" 7830 PRINT CHR$ (4);"READ SPEL L 1.OBJ" 7840 INPUT YES,NO,MAYBE,XX,YY,Z Z 7850 PRINT CHR$ (4);"CLOSE SPE LL 1.OBJ" 7852 POKE 2162,ZZ 7860 POKE XX,YY: GOTO 10 20000 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 7840 reads a series of values from a text file ("SPELL 1.OBJ", although I'm pretty sure there are some control characters in there somewhere). Looks innocuous, until where you realize that it's using those values to POKE actual memory locations. ]PR#5 ... ]TLIST SPELL 1.OBJ,S6,D1 -936 8131 6084 104 64 66 The first three values go into the variables YES, NO, and MAYBE. (Really.) The last three go into XX, YY, and ZZ, then line 7860 does POKE XX, YY. XX is 104; YY is 64. Hey, poking address 104. That sounds familiar... ]PR#6 ... ]LOAD SPELL IT! HELLO ]POKE 104,64 ]LIST 10 POKE 216,0: ONERR GOTO 6000 0 20 RESTORE 30 KB = - 16384:KC = - 16368 40 FY = 3:FX = 6:PT = 768:DR = 7 69:TN = 770 50 CALL YES: CALL NO: HGR : CALL MAYBE 55 IF PEEK ( - 16384) = 69 + 1 28 THEN PRINT CHR$ (4)"RUN SPELL IT! EDITOR" 60 GOSUB 6200: GOSUB 8990 70 C = 1: GOSUB 8030: GOSUB 3001 0 74 HCOLOR= 0: FOR I = 12 TO 15: HPLOT 14,I TO 266,I: NEXT 80 FOR I = 3 TO 8: VTAB I: HTAB 3: PRINT SPC( 36): NEXT 81 HPLOT 11,12 TO 11,63: HPLOT 13,12 TO 13,63: HPLOT 267,12 TO 267,63 BREAK ] 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 20 20 IF PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 20000 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 900 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.204F 2000- 00 14 20 0A 00 B9 32 31 2008- 36 2C 30 3A A5 AB 32 30 2010- 30 30 30 00 1E 20 0C 00 2018- 8C C9 39 33 36 00 26 20 2020- 0E 00 A2 31 30 00 4A 20 2028- 14 00 AD E2 28 34 30 33 ^^ ^^^^^ ^^^^^^^^ IF PEEK( 4 0 3 2030- 32 34 29 D0 31 37 33 CE ^^^^^^^^ ^^ ^^^^^^^^ ^^ 2 4 ) = 1 7 3 OR 2038- E2 28 34 37 30 39 34 29 ^^^^^ ^^^^^^^^^^^^^^ ^^ PEEK( 4 7 0 9 4 ) 2040- D1 CF 30 C4 32 30 30 30 ^^^^^ ^^ ^^ ^^^^^^^^^^^ < > 0 THEN 2 0 0 0 2048- 30 00 8A 20 84 03 B9 32 ^^ 0 The opcode for a "REM" statement is $B2. Let's try changing the "IF" statement on line 20 to a "REM" statement. *202A:B2 *3D0G ; return to BASIC prompt ]LIST 20 20 REM PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 20000 Success! Line 20 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 Disk Fixer sector editor, I press "F" 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 several matches. The first is on track $13. Looking at the surrounding bytes confirms that it is line of code in the "SPELL IT! HELLO" program that I was just analyzing and hex-editing in memory. But there are three more matches on the disk. Some further analysis shows that the main "SPELL IT!" program contains not one, not two, but three separate checks to verify that the disk was booted from the original protected DOS. ]PR#6 ... ]LOAD SPELL IT! ]LIST . . . 12 IF PEEK (2162) < > 66 OR PEEK (40324) = 173 THEN 16 . . . 16 PRINT CHR$ (4);"PR#6" . . . 284 IF PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 16 . . . 336 A1 = (CH = 1):A2 = (CH = 2): A3 = (CH = 3): IF PEEK (40 324) = 173 THEN GOSUB 16 The fix is the same: find the $AD byte (the opcode for an "IF" statement in Applesoft BASIC) and change it to $B2 (opcode for a "REM" statement). T13,S06,$2B change "AD" to "B2" T17,S0E,$FD change "AD" to "B2" T1A,S0B,$D2 change "AD" to "B2" T1B,S08,$0C change "AD" to "B2" ]PR#6 ... ]LOAD SPELL IT! ]LIST . . . 12 REM PEEK (2162) < > 66 OR PEEK (40324) = 173 THEN 16 . . . 284 REM PEEK (40324) = 173 OR PEEK (47094) < > 0 THEN 16 . . . 336 A1 = (CH = 1):A2 = (CH = 2): A3 = (CH = 3): REM PEEK (40 324) = 173 THEN GOSUB 16 Success! The game loads and runs with no further complaint. After extensive testing, I think I can call this one completed. Quod erat liberandum. --------------------------------------- A 4am crack No. 138 ------------------EOF------------------