--------------Spellicopter------------- A 4am crack 2014-04-03 --------------------------------------- Spellicopter is an 1983 educational game distributed by DesignWare, Inc. The original disk uses a standard 6-2 nibble encoding and standard address and data prologue and epilogue sequences. COPYA copies the disk without complaint, but the copy doesn't boot. There is no VTOC on track $11 (so no CATALOG). Listening to the original disk boot, there doesn't seem to be a standard DOS loaded from tracks 2, 1, then 0. It's time for boot tracing. But I'm tired of doing manual boot tracing. So I've upped my game. [S6D1=Spellicopter original disk] [S5D1=my work disk] Many copy protected disks start off the same way: a boot0 routine in track 0, sector 0 reuses part of the disk controller ROM routine at $C65C to read in an RWTS from sectors 1-9 into $B700..$BFFF, then jumps to $B700 to load the rest of the disk. To speed up some of the more repetitive elements of boot tracing (and eliminate stupid mistakes and typos on my part), I've started writing some automation scripts to capture the boot0 routine to a file on my work disk, check whether the boot0 routine follows the standard pattern, and if so, capture the boot1 code into a separate file. AUTOTRACE0 starts with a copy of the disk controller ROM routine, moved down to $9600, then adds the following code of my own: ; turn off slot 6 disk motor 96F8- AD E8 C0 LDA $C0E8 ; copy boot0 to a safe location where ; it won't get overwritten upon reboot 96FB- A0 00 LDY #$00 96FD- B9 00 08 LDA $0800,Y 9700- 99 00 28 STA $2800,Y 9703- C8 INY 9704- D0 F7 BNE $96FD ; set a marker for my HELLO program to ; recognize that $2800..$28FF contains ; the boot0 routine that it should save ; to disk 9706- A9 80 LDA #$80 9708- 8D 00 01 STA $0100 970B- A9 32 LDA #$32 970D- 8D 01 01 STA $0101 ; reboot to my work disk in slot 5 9710- 4C 00 C5 JMP $C500 *BSAVE AUTOTRACE0,A$9600,L$113 Then, the HELLO program on my work disk checks for the specific byte signature in $0100..$0101. If it finds "80 32", it assumes that $2800..$28FF is a copy of the boot0 routine, and it saves it to the file BOOT0. To automate the next step of boot tracing, I wrote AUTOTRACE1. It also starts with a relocated copy of the disk controller ROM routine, then adds the following: ; set up a callback to a routine that I ; control after the disk loads its RWTS ; into memory 96F8- A9 4C LDA #$4C 96FA- 8D 4A 08 STA $084A 96FD- A9 16 LDA #$16 96FF- 8D 4B 08 STA $084B 9702- A9 97 LDA #$97 9704- 8D 4C 08 STA $084C ; find the starting address and length ; of the boot1 routine (stored in a ; standard location in boot0) and set ; up a copy loop to run later 9707- AD FE 08 LDA $08FE 970A- 8D 1C 97 STA $971C 970D- AD FF 08 LDA $08FF 9710- 8D 17 97 STA $9717 ; let it boot 9713- 4C 01 08 JMP $0801 ; callback starts here ; copy boot1 to safe location that ; won't get overwritten on reboot 9716- A2 0A LDX #$0A 9718- A0 00 LDY #$00 971A- B9 00 B6 LDA $B600,Y 971D- 99 00 20 STA $2000,Y 9720- C8 INY 9721- D0 F7 BNE $971A 9723- EE 1C 97 INC $971C 9726- EE 1F 97 INC $971F 9729- CA DEX 972A- 10 EE BPL $971A ; set a marker for my HELLO program to ; recognize that $2000..$29FF contains ; a boot1 routine that it should save ; to disk 972C- A9 81 LDA #$81 972E- 8D 00 01 STA $0100 9731- 49 A5 EOR #$A5 9733- 8D 01 01 STA $0101 ; turn off slot 6 disk motor 9736- AD E8 C0 LDA $C0E8 ; reboot into my work disk 9739- 4C 00 C5 JMP $C500 *BSAVE AUTOTRACE1,A$9600,L$13C The whole thing is still pretty rough. For example, AUTOTRACE1 checks the starting address and length of the boot1 routine and copies the entire thing into $2000.., but the HELLO program currently just assumes that it's the standard address and length. (It could get this from the BOOT0 file that it saved earlier.) The upshot of all this is that I can set up an original disk in S6D1 and my work disk in S5D1, boot my work disk, and it will automatically trace and save the boot0 and boot1 routines from the original disk (rebooting several times along the way -- slot 6, then slot 5, then 6, then 5). It's pretty magical to watch, when it works properly. I'm sure I'll need to refine it as I find more edge cases (oh God, so many edge cases), but it works flawlessly for Spellicopter. This is how the captured BOOT1 file starts out: ; normal initialization stuff here, ; setting up the RWTS parameter table ; at $B7E8 B700- 8E E9 B7 STX $B7E9 B703- 8E F7 B7 STX $B7F7 B706- A9 01 LDA #$01 B708- 8D F8 B7 STA $B7F8 B70B- 8D EA B7 STA $B7EA B70E- AD E0 B7 LDA $B7E0 B711- 8D E1 B7 STA $B7E1 B714- A9 00 LDA #$00 B716- 8D EC B7 STA $B7EC B719- AD E2 B7 LDA $B7E2 B71C- 8D ED B7 STA $B7ED B71F- AD E3 B7 LDA $B7E3 B722- 8D F1 B7 STA $B7F1 B725- A9 01 LDA #$01 B727- 8D F4 B7 STA $B7F4 B72A- 8A TXA B72B- 4A LSR B72C- 4A LSR B72D- 4A LSR B72E- 4A LSR B72E- 4A LSR B72F- AA TAX B730- A9 00 LDA #$00 B732- 9D F8 04 STA $04F8,X B735- 9D 78 04 STA $0478,X ; hey now, what's all this then? B738- 20 00 BB JSR $BB00 Interesting. It sets up an RWTS parameter table, but instead of calling the usual multi-sector read routine at $B793, it calls $BB00. I know from experience that there shouldn't be any code in the $BB00 page. The standard DOS 3.3 RWTS uses this page as a temporary buffer for converting nibbles to bytes, which means it gets stomped on with every disk read. So any code that's there during boot is non- standard and disk-specific, and therefore suspicious. BB00- 86 1B STX $1B ; a neat trick to get the high byte of ; the return address from the stack ; without actually pulling it off the ; stack and pushing it back. BB02- BA TSX BB03- BD 02 01 LDA $0102,X ; $B7 --> zero page $03 BB06- 85 03 STA $03 BB08- 18 CLC BB09- 69 04 ADC #$04 ; $BB --> zero page $05 BB0B- 85 05 STA $05 BB0D- A0 00 LDY #$00 BB0F- 84 02 STY $02 BB11- A0 40 LDY #$40 BB13- 84 04 STY $04 BB15- A0 39 LDY #$39 BB17- A9 93 LDA #$93 ; $93 --> $B739 BB19- 91 02 STA ($02),Y BB1B- C8 INY BB1C- A5 03 LDA $03 ; $B7 --> $B73A BB1E- 91 02 STA ($02),Y ; $BB40..$BBBF --> $0800..$087F BB20- A0 7F LDY #$7F BB22- B1 04 LDA ($04),Y BB24- 99 00 08 STA $0800,Y BB27- 88 DEY BB28- 10 F8 BPL $BB22 ; restore X register and jump to ; relocated code BB2A- A6 1B LDX $1B BB2C- 4C 00 08 JMP $0800 Oh, that's clever. This subroutine erases any evidence that it was ever called, by changing the JSR instruction that called it from JSR $BB00 to JSR $B793. Then it relocates some other code to $0800 and jumps there. This is not innocent code. Innocent code does not cover its tracks to make it seem like it was never called. This code does not have my best interests at heart. Let's see what ends up at $0800. *BB2C:4C 59 FF *B738G 0800- A9 00 LDA #$00 0802- A0 EC LDY #$EC ; ($02) points to $B700 at this point, ; so this looks like it's setting up ; the RWTS parameter table again. ; $B7EC holds the track number (0). 0804- 91 02 STA ($02),Y 0806- C8 INY ; $B7ED holds the sector number (0). 0807- 91 02 STA ($02),Y 0809- A9 09 LDA #$09 080B- A0 F1 LDY #$F1 ; $B7F1 holds the destination address, ; so it looks like we're going to read ; a bunch of sectors from track 0 ; into $0900 and up. 080D- 91 02 STA ($02),Y 080F- A9 08 LDA #$08 0811- A0 E1 LDY #$E1 ; $B7E1 holds the number of sectors ; to read (8). 0813- 91 02 STA ($02),Y ; set up a JMP instruction at $01 (WTF) 0815- A9 4C LDA #$4C 0817- 85 01 STA $01 0819- A9 93 LDA #$93 081B- 85 02 STA $02 ; call $B793 to read multiple sectors 081D- 20 01 00 JSR $0001 Despite the odd indirection, this part isn't suspicious at all. It's using the standard multi-read routine at $B793 to read the rest of track 0 into memory. But look what comes next: 0820- A0 00 LDY #$00 0822- 84 02 STY $02 0824- A5 1E LDA $1E 0826- 85 08 STA $08 0828- 0A ASL 0829- 90 01 BCC $082C 082B- C8 INY 082C- 48 PHA 082D- 68 PLA 082E- D0 F8 BNE $0828 0830- C0 02 CPY #$02 0832- 69 FF ADC #$FF 0834- 85 1F STA $1F Wait a minute. Wait wait wait wait wait. The standard DOS 3.3 RWTS doesn't use zero page $1E, so at this point it should be uninitialized and essentially random. But this loop seems to be doing some bit math on it (specifically, counting how many bits are 0 and storing that in the Y register), so either something is setting it or I'm being punked. With my trusty Copy ][+ sector editor, I found a few instances of $1E in track 0. The only one that looked like actual RWTS code was T00,S0F,$3F. After some manual cross-referencing, it appears that this sector is loaded at $B900 by the RWTS loader bootstrap in sector 0. Here is the relevant code: B925- BC 8C C0 LDY $C08C,X B928- 10 FB BPL $B925 B92A- D9 00 BA CMP $BA00,Y B92D- D0 13 BNE $B942 B92F- BD 8C C0 LDA $C08C,X B932- 10 FB BPL $B92F B934- B8 CLV B935- BD 8C C0 LDA $C08C,X B938- 10 FB BPL $B935 B93A- B8 CLV B93B- 1E 8C C0 ASL $C08C,X B93E- 26 1E ROL $1E B940- 50 5C BVC $B99E B942- 38 SEC B943- 60 RTS Normally, this code would be checking for the nibble sequence "DE AA", the standard epilogue after the data field. But instead, Spellicopter accepts any two nibbles, then does some bit math to twiddle zero page $1E based on the timing bits that follow. Since that relocated routine at $0800 reads 8 sectors, zero page $1E gets twiddled 8 times, giving it a fully defined value despite never being initialized. Obviously the value in zero page $1E is important. Let's capture it. ]PR#5 ]CALL -151 *9600 * This is repeatable; the original disk always ends up with $F7 in zero page $1E. If my theory is correct (that this is the crux of the copy protection), then running the same capture utility on my non-working copy should yield a different result. [S6D1=Spellicopter non-working copy] [S5D1=my work disk] ]PR#5 ]BRUN CAPTURE 1E ... 00 * This is also repeatable. Copies (even the bit copy I made with EDD 4) end up with $00 in zero page $1E. Now I'm wondering if I can simply fake this data by ignoring the value of $1E and hard-coding the expected value. I suppose there's only one way to find out. Using my trusty Copy ][+ sector editor, I found the $BB00 routine in sector 3 of track $00. I made one small change after the disk reading but before the bit counting: to ignore the value in zero page $1E and always uses $F7 instead. T00,S03,$64 change "A5 1E" to "A9 F7" Success! The game boots and runs without complaint. There doesn't seem to be any further copy protection. Quod erat liberandum. --------------------------------------- A 4am crack 2014-04-03 ------------------EOF------------------