--------------Math Rabbit-------------- A 4am crack 2014-04-11 -------------------. updated 2015-09-04 |___________________ Math Rabbit is a 1986 educational game programmed by Aaron Weiss and distributed by The Learning Company. The original disk copies with COPYA, but the copy does not work. It boots to the game title, but when you select a level, it displays a graphical message "Fatal Disk Error" and hangs. Using my trusty Copy ][+ sector editor, I scanned for the ASCII string "Fatal" but found nothing. However, knowing that many Apple II programs use 7-bit ASCII, I scanned for the hex sequence "46 61 74 61 6C" ("Fatal" without the high bit set). And behold! I found the error message on track $0A, sector $0B. Based on some of the absolute JMP and JSR instructions, I'm guessing this sector is loaded at $4000. Cleaning up the listing a bit, the entire sector looks like this: 4000- A9 00 LDA #$00 4002- 85 46 STA $46 4004- 85 47 STA $47 4006- 85 44 STA $44 4008- A9 E0 LDA #$E0 400A- 85 45 STA $45 400C- 20 3A B8 JSR $B83A 400F- B0 EF BCS $4000 ; engage either drive 1 or 2 4011- A5 43 LDA $43 4013- 30 07 BMI $401C 4015- AA TAX 4016- DD 8A C0 CMP $C08A,X 4019- 4C 22 40 JMP $4022 401C- 29 7F AND #$7F 401E- AA TAX 401F- DD 8B C0 CMP $C08B,X ; call nibble check (doesn't appear to ; care about return code) 4022- 20 33 40 JSR $4033 ; zero out nibble check code in memory ; (presumably to foil NMI cards) 4025- 48 PHA 4026- A0 00 LDY #$00 4028- 98 TYA 4029- 99 33 40 STA $4033,Y 402C- C8 INY 402D- C0 79 CPY #$79 402F- D0 F8 BNE $4029 4031- 68 PLA 4032- 60 RTS ; entry point for nibble check ; turn on drive motor 4033- BD 89 C0 LDA $C089,X ; set up death counters 4036- A9 56 LDA #$56 4038- 85 08 STA $08 403A- A9 08 LDA #$08 403C- C6 07 DEC $07 403E- D0 04 BNE $4044 4040- C6 08 DEC $08 ; if death counter hits 0, give up and ; branch to The Badlands 4042- F0 3C BEQ $4080 ; look for $FB nibble 4044- BC 8C C0 LDY $C08C,X 4047- 10 FB BPL $4044 4049- C0 FB CPY #$FB 404B- D0 ED BNE $403A 404D- F0 00 BEQ $404F ; kill a few cycles (not pointless, ; because the disk spins independently ; of the CPU, so all of these low-level ; disk reads are highly time-sensitive) 404F- EA NOP 4050- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 4051- BC 8C C0 LDY $C08C,X ; do a compare to set or clear the ; carry bit (among other things, but ; it's the carry bit we care about) 4054- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 4056- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 4057- B0 0B BCS $4064 ; next nibble needs to be $FF 4059- BC 8C C0 LDY $C08C,X 405C- 10 FB BPL $4059 405E- C0 FF CPY #$FF ; ...otherwise we start over 4060- D0 D8 BNE $403A ; loop back to get next nibble 4062- F0 EB BEQ $404F ; execution continues here (from $4057) ; get another nibble 4064- BC 8C C0 LDY $C08C,X 4067- 10 FB BPL $4064 ; stash it in zero page 4069- 84 07 STY $07 ; if the accumulator is anything but ; %00001010, start over 406B- C9 0A CMP #$0A 406D- D0 CB BNE $403A ; get a nibble 406F- BD 8C C0 LDA $C08C,X 4072- 10 FB BPL $406F ; more bit twiddling 4074- 38 SEC 4075- 2A ROL ; AND it with the previously stashed ; nibble 4076- 25 07 AND $07 ; check 4078- 49 FF EOR #$FF ; fail? jump to The Badlands 407A- F0 04 BEQ $4080 Here is the original disk, as seen by the Copy II+ nibble editor. Nibbles with extra "0" bits (timing bits) after them are displayed in inverse on an original machine, marked here with a "+" after the nibble. --v-- 1C70: 9F EB E5 FC D7 D7 D7 EE 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF+FF FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --^-- It's easy to understand why a simple sector copy failed. The sequence that this code is looking for starts at offset $1C93, which is between the end of one sector and the beginning of the next. (The data epilogue is at $1C8C; the next address prologue is at $1CA2.) Sector copiers discard everything between those delimiters and rebuild the track with a default pattern of sync bytes. That pattern doesn't include an $FB nibble, so the nibble check fails. But the EDD bit copy also failed. Here is the original disk's pattern at offset $1C93: - $FB + timing bit - $FF - $FF + timing bit - $FF - $FF + timing bit And here is what the same part of the track looks like on my failed EDD copy: --v-- 1C70: 9F EB E5 FC D7 D7 D7 EE 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF FF+FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --^-- A subtle difference! The sequence at offset $1C93 now looks like this: - $FB + timing bit - $FF - $FF - $FF + timing bit - $FF + timing bit This code is looking for $FF bytes with an alternating pattern of timing bit, no timing bit, timing bit, no timing bit, timing bit. It doesn't find that on the bit copy, so it knows it's not running on an original disk. Continuing the code listing... ; success path falls through to here -- ; just turn off the drive and exit ; gracefully 407C- DD 88 C0 CMP $C088,X 407F- 60 RTS ; The Badlands (from which there is ; no return) ; ; turn off drive motor 4080- DD 88 C0 CMP $C088,X 4083- A9 52 LDA #$52 4085- 8D A8 65 STA $65A8 4088- A9 04 LDA #$04 408A- 8D A9 65 STA $65A9 408D- A9 2E LDA #$2E 408F- 8D AA 65 STA $65AA 4092- A9 20 LDA #$20 4094- 8D AB 65 STA $65AB ; display "Fatal Disk Error" and hang 4097- 20 AC 65 JSR $65AC 409A- 38 SEC 409B- 6E 12 60 ROR $6012 409E- 20 17 60 JSR $6017 40A1- 00 BRK So, the nibble check starts at $4033. If it fails, it displays the error message that I saw and never returns. The success path (at $407C) doesn't cause any side effects; it just turns off the disk motor and returns gracefully. It doesn't even clear the carry flag. Therefore, I predict that changing the first instruction of the nibble check subroutine from "LDA" to "RTS" will successfully bypass this copy protection. T0A,S0B,$33 change "BD" to "60" Success! The game lets me select a level from the title screen and continues to run without complaint. Quod erat liberandum. ~ Changelog 2015-09-04 - Vastly improved explanation of the actual protection routine. Thanks to qkumba for pointing out that my original explanation was inaccurate. 2014-04-11 - initial release --------------------------------------- A 4am crack No. 14 ------------------EOF------------------