--------------Think Quick!------------- A 4am crack 2015-11-16 --------------------------------------- Name: Think Quick! Version: 1.2 Genre: educational Year: 1987 Authors Leslie Grimm and Sid Weber Publisher: The Learning Company Media: one double-sided 5.25-inch disk, one single-sided 5.25-inch disk OS: RDOS.SYSTEM (ProDOS-compatible) Previous cracks: none of this version Both sides of disk 1 are bootable. Disk 2 is labeled "storage disk" and is not. I'll start with disk 1, side A. ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy boots to title screen then prints a graphical error message "Oops! Problem with disk or drive. Press a key to start the program over." Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer T00 -> bootloader looks custom but I see a ProDOS disk catalog Why didn't any of my copies work? Considering that there isn't actually any problem with my disk or drive, I'm guessing that the "Oops!" message is the result of a runtime protection check. Next steps: 1. Boot from a separate ProDOS disk 2. Trace the first .SYSTEM file 3. ??? ~ Chapter 1 In Which We Stumble Around In The Dark [S7,D1=my ProDOS hard drive] [S6,D1=original disk 1A] ]PR#7 ... ]CAT,S6,D1 /IPIS1 NAME TYPE BLOCKS MODIFIED RDOS.SYSTEM SYS 14 22-APR-87 STARTUP BIN 15 25-AUG-87 CONTROLLER BIN 1 4-MAY-87 UNPINK BIN 7 23-JUN-87 ADV..110.OBJ0 BIN 19 4-MAY-87 SLUGS BIN 7 4-MAY-87 SAVE BIN 7 14-MAY-87 DRAGON.PIX BIN 7 22-APR-87 MENU BIN 12 26-MAY-87 MENU.PAC BIN 16 4-MAY-87 VARS BIN 4 8-MAY-87 HIGH.SCORES BIN 1 4-MAY-87 IPIS0A BIN 13 27-AUG-89 IPIS0B BIN 6 27-AUG-89 IPIS0.WORLD BIN 23 7-MAY-87 IPIS1A BIN 13 25-AUG-87 IPIS1B BIN 10 25-AUG-87 IPIS1.WORLD BIN 19 4-MAY-87 IPIS2A BIN 13 25-AUG-87 IPIS2B BIN 11 25-AUG-87 IPIS2.WORLD BIN 18 4-MAY-87 IPIS4A BIN 7 14-MAY-87 IPIS4B BIN 10 14-MAY-87 IPIS4.WORLD BIN 3 4-MAY-87 END.GAME BIN 17 11-MAY-87 BLOCKS FREE: 0 BLOCKS USED: 280 RDOS.SYSTEM? That's interesting. It appears to be ProDOS-compatible (at least as far as the directory structure is concerned). I'm guessing RDOS.SYSTEM gets loaded first, then loads STARTUP. But I'm just guessing based on the directory order and filenames. ]PREFIX /IPIS1 ]CATALOG ...shows that "STARTUP" loads at $6000 ]BLOAD STARTUP ]CALL -151 *6000L ; munge reset vector to reboot 6000- A9 00 LDA #$00 6002- 8D F3 03 STA $03F3 6005- 8D F4 03 STA $03F4 6008- D8 CLD ; read ROM, write RAM bank 2 6009- 2C 81 C0 BIT $C081 600C- 2C 81 C0 BIT $C081 ; set up an even less friendly reset ; vector 600F- A2 05 LDX #$05 6011- BD 98 63 LDA $6398,X 6014- 9D EC 03 STA $03EC,X 6017- CA DEX 6018- 10 F7 BPL $6011 601A- AD FE FF LDA $FFFE 601D- 8D FE FF STA $FFFE 6020- AD FF FF LDA $FFFF 6023- 8D FF FF STA $FFFF 6026- A9 EC LDA #$EC 6028- 8D FC FF STA $FFFC 602B- A9 03 LDA #$03 602D- 8D FD FF STA $FFFD ; read ROM, no write 6030- 2C 82 C0 BIT $C082 ; check whether we're running on a IIgs 6033- 38 SEC 6034- 20 1F FE JSR $FE1F ; nope 6037- B0 18 BCS $6051 ; yup 6039- 58 CLI ; set IIgs screen color white-on-black 603A- A9 F0 LDA #$F0 603C- 8D 22 C0 STA $C022 ; set IIgs border color black 603F- AD 34 C0 LDA $C034 6042- 29 F0 AND #$F0 6044- 09 05 ORA #$05 6046- 8D 34 C0 STA $C034 ; set IIgs speed to slow (1 MHz) 6049- AD 36 C0 LDA $C036 604C- 29 7F AND #$7F 604E- 8D 36 C0 STA $C036 ; subroutine copies $20 bytes from the ; file space to the upper echelons of ; main memory, $BFE0 (not shown) 6051- 20 4C 63 JSR $634C ; read/write RAM bank 2 6054- 2C 83 C0 BIT $C083 6057- 2C 83 C0 BIT $C083 605A- 20 BC FB JSR $FBBC 605D- [03 03 63 58] 6061- B0 F7 BCS $605A I don't know what $FBBC is, but I'm assuming it's part of RDOS. The four bytes after the JSR appear to be data, not opcodes. If $03 is a command, $6303 could be a parameter table. Not sure what $58 is. *6303. 6303- 07 63 E0 BD 0A 6308- 43 4F 4E 54 52 4F 4C 4C 6310- 45 52 16 63 00 D0 04 53 6318- 41 56 45 1F 63 00 00 04 Well, ($6303) is a pointer to $6307, which is a length-prefixed filename ("CONTROLLER"), which was listed in the directory listing. The CATALOG command listed $BDE0 as the default starting address of CONTROLLER, and that matches the two-byte value at $6305. So I'm guessing this is a file load routine. (I think the parameter table ends with the end of the string, because the same pattern repeats at $6312. ($6312) -> $6316, which is the string "SAVE", which was another filename. And $D000 is its default load address.) I'm also going to assume that the API call at $FBBC sets the carry on error, because $6061 branches back to $605A if the carry is set. Continuing from $6063... ; load the file "UNPINK" at $7C00 6063- 20 BC FB JSR $FBBC 6066- [03 41 63 58] ; branch on success 606A- 90 06 BCC $6072 ; display some sort of error message? ; (not tested) ;606C- 20 FA BF JSR $BFFA ;606F- 4C 63 60 JMP $6063 ; execution continues here (from $606A) 6072- A9 65 LDA #$65 6074- A0 00 LDY #$00 6076- A2 20 LDX #$20 6078- 20 00 7C JSR $7C00 607B- A9 70 LDA #$70 607D- A0 D9 LDY #$D9 607F- A2 20 LDX #$20 6081- 20 00 7C JSR $7C00 Those calls are both going to "UNPINK", whatever that is. It turns out it's self-contained. *BLOAD UNPINK ; at $7C00 *300<6072.6083M ; copy above code *312:60 ; add an "RTS" *F3E2G ; HGR *C052 ; full screen hi-res *300G ; execute above code It displays the title screen, nicely wiping from left to right. ; clear the keyboard strobe 6084- 2C 10 C0 BIT $C010 ; copy hi-res screen 1 to 2 6087- A9 20 LDA #$20 6089- 8D 96 60 STA $6096 608C- 0A ASL 608D- 8D 99 60 STA $6099 6090- A0 00 LDY #$00 6092- A2 1F LDX #$1F 6094- B9 00 20 LDA $2000,Y 6097- 99 00 40 STA $4000,Y 609A- C8 INY 609B- D0 F7 BNE $6094 609D- EE 96 60 INC $6096 60A0- EE 99 60 INC $6099 60A3- CA DEX 60A4- 10 EE BPL $6094 60A6- 2C 50 C0 BIT $C050 60A9- 2C 52 C0 BIT $C052 60AC- 2C 54 C0 BIT $C054 60AF- 2C 57 C0 BIT $C057 ; subroutine checks for a mouse card ; (not shown) 60B2- 20 0F 7C JSR $7C0F ; initializes the mouse card if the ; previous routine found one (also ; not shown) 60B5- 78 SEI 60B6- A9 09 LDA #$09 60B8- 20 12 7C JSR $7C12 60BB- 58 CLI ; back to read/write RAM bank 2 60BC- 2C 83 C0 BIT $C083 60BF- 2C 83 C0 BIT $C083 ; load file "ADV..110.OBJ0" 60C2- 20 BC FB JSR $FBBC 60C5- [03 E7 62 58] ; branch on success 60C9- 90 06 BCC $60D1 ; display error message on failure ;60CB- 20 FA BF JSR $BFFA ;60CE- 4C C2 60 JMP $60C2 ; execution continues here (from $60C9) 60D1- 20 9E 63 JSR $639E *639EL ; save flags and disable interrupts 639E- 08 PHP 639F- 78 SEI 63A0- A9 00 LDA #$00 63A2- 85 46 STA $46 63A4- 85 47 STA $47 63A6- 85 44 STA $44 63A8- A9 E0 LDA #$E0 63AA- 85 45 STA $45 ; don't really know what this does, but ; it seems really important that it ; succeeds (otherwise we start over) 63AC- 20 3A F4 JSR $F43A 63AF- B0 ED BCS $639E ; check which drive we're in (1 or 2) 63B1- A5 43 LDA $43 63B3- 30 07 BMI $63BC ; access appropriate soft switch 63B5- AA TAX 63B6- DD 8A C0 CMP $C08A,X 63B9- 4C C2 63 JMP $63C2 63BC- 29 7F AND #$7F 63BE- AA TAX 63BF- DD 8B C0 CMP $C08B,X ; will come back to this in a minute 63C2- 20 90 64 JSR $6490 ; save the result from routine at $6490 63C5- 48 PHA ; do this 63C6- 20 CC 63 JSR $63CC ; restore the result (from $6490) 63C9- 68 PLA ; restore flags 63CA- 28 PLP 63CB- 60 RTS ~ Chapter 2 In Which We Know We're Getting Close Here's why I know I'm getting close to the copy protection check. Take a look at this routine at $63CC: *63CCL ; wipe previously called subroutine 63CC- A0 00 LDY #$00 63CE- 98 TYA 63CF- 99 90 64 STA $6490,Y 63D2- C8 INY 63D3- C0 4E CPY #$4E 63D5- D0 F8 BNE $63CF 63D7- 60 RTS Whatever $6490 is doing, we're only calling it once, and we don't want any evil hackers with memory capture cards to see it later. *6490L ; manually turn on the drive motor 6490- BD 89 C0 LDA $C089,X ; set up Death Counters 6493- A9 56 LDA #$56 6495- 85 08 STA $08 6497- A9 08 LDA #$08 6499- C6 07 DEC $07 649B- D0 04 BNE $64A1 ; if Death Counter hits 0, jump forward ; (more on this in a second) 649D- C6 08 DEC $08 649F- F0 3D BEQ $64DE ; look for a specific nibble ($FB) 64A1- BC 8C C0 LDY $C08C,X 64A4- 10 FB BPL $64A1 64A6- C0 FB CPY #$FB 64A8- D0 ED BNE $6497 64AA- F0 00 BEQ $64AC ; 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) 64AC- EA NOP 64AD- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 64AE- 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) 64B1- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 64B3- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 64B4- B0 0C BCS $64C2 ; next nibble needs to be $FF 64B6- BC 8C C0 LDY $C08C,X 64B9- 10 FB BPL $64B6 ; ...otherwise we start over 64BB- C0 FF CPY #$FF 64BD- D0 D8 BNE $6497 ; jump back to get next nibble 64BF- 4C AC 64 JMP $64AC ; execution continues here (from $64B4) ; get another nibble 64C2- BC 8C C0 LDY $C08C,X 64C5- 10 FB BPL $64C2 ; stash it in zero page 64C7- 84 07 STY $07 ; if the accumulator is anything but ; %00001010, start over 64C9- C9 0A CMP #$0A 64CB- D0 CA BNE $6497 I got lost several times trying to follow this routine. I think the easiest way to explain it is to show the difference between the original disk and my non-working copy. 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-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: START: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 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 TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- 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-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: START: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 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 TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- 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. The accumulator holds the pattern of whether each sync byte had a timing bit after it. It's set one bit at a time, rotated into place from the carry bit that was set by the "CPY #$08" that happened after getting the value of the data latch (LDY $C08C,X) that happened after doing just enough NOPs that the value of the data latch will depend on the presence of a timing bit after the previous nibble. Which is brilliant. Anyway, if the value of the accumulator (i.e. the pattern of timing bits) is wrong, the program knows it's not running on an original disk. Continuing the code listing... ; get a nibble 64CD- BD 8C C0 LDA $C08C,X 64D0- 10 FB BPL $64CD ; more bit twiddling 64D2- 38 SEC 64D3- 2A ROL ; AND it with the previously stashed ; nibble 64D4- 25 07 AND $07 64D6- 49 FF EOR #$FF 64D8- F0 04 BEQ $64DE ; turn off the drive motor and exit via ; RTS 64DA- DD 88 C0 CMP $C088,X 64DD- 60 RTS ; failure path is here (from either ; $649F if the pattern of timing bits ; is wrong, or $64D8 if the final ; nibble checksum is wrong) 64DE- 4C D8 63 JMP $63D8 (The routine at $63D8 is what showed me the delightful "Oops" message earlier.) ~ Chapter 3 In Which We Triumphantly Bypass The Copy Protection, Only To Snatch Defeat From The Jaws Of Victory There don't appear to be any side effects to this protection check. If it succeeds, we just return gracefully (then wipe it from memory, so it's not like it's going to be called again). If it fails, we go down another path (to $63D8) and never return. So let's put an "RTS" at the beginning of it and see what happens. [Disk Fixer] ["O" for INPUT/OUTPUT CONTROL] set "DOS TYPE" = "PRODOS" ["D"irectory] select "STARTUP" right arrow x3 T03,S00,$90 change BD to 60 ]PR#6 ...boots to title screen, then prints the same "Oops!" message (but later) There is another protection check. It must be in another file. Working under the theory that companies like to re-use protection checks, I'm going to try to short-circuit the arduous tracing process by searching for the bytes at the beginning of the first protection check. [Disk Fixer] ["F"ind] ["H"ex] "BD 89 C0 A9 56" Aha! It finds one match, on T0B,S05. (Copy II+ tells me this is part of the MENU file, which makes sense. It was supposed to show the main menu instead of erroring out.) T0B,S05,$01 change BD to 60 ]PR#6 ...works... Side B has identical protection. Quod erat liberandum. --------------------------------------- A 4am crack No. 495 ------------------EOF------------------