----Computer Laboratory for Calculus--- A 4am crack 2014-09-26 -------------------. updated 2015-09-06 |___________________ "Computer Laboratory for Calculus" is a 1985 educational program by Chris W. Avery and Frank Soler and distributed by The Math Lab. COPYA fails miserably and immediately. Locksmith Fast Disk Backup fares no better. EDD 4 bit copy gives no read errors, but the copy it creates crashes after loading DOS. Turning to my trusty Disk Fixer sector editor, I go to "Input/Output Control" (press "O") and set CHECKSUM ENABLED = NO. 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 every track. Given the (relatively) weak structural protection, I used to turn to the DOS 3.3 master disk, patch the RWTS to ignore checksums and epilogue bytes (changing $B942 from "SEC" to "CLC"), and run COPYA. Then, one fine day, and completely by accident, I came across an original disk with a bad sector. I suppose this shouldn't surprise me. These floppies are decades old by now; it's amazing any of them work at all. The point is, I shouldn't be using tools that ignore potentially serious read errors. So no more COPYA+B942:18 patch. From now on, it's Super Demuffin or Advanced Demuffin to convert disks to a standard format. Let's see if AUTOTRACE can capture the RWTS from the original disk. If not, I'll have to resort to manual investigation. [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 ... CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 CAPTURING BOOT1 ...reboots slot 6... ...reboots slot 5... SAVING BOOT1 SAVING RWTS 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. In this case, 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". If anything looks fishy or non- standard, AUTOTRACE just stops, and I have to check the files it saved so far to determine why. But in this case, it ran all the way through, automatically capturing BOOT0, BOOT1, and RWTS files. Now I use plug that RWTS file into Advanced Demuffin and try to convert the data on this disk to a standard format. [S6,D1=original disk] [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 "6" to switch to slot 6] [press "C" to 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. --v-- ADVANCED DEMUFFIN 1.5 (C) 1983, 2014 ORIGINAL BY THE STACK UPDATES BY 4AM =======PRESS ANY KEY TO CONTINUE======= TRK:................................... +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0:................................... SC1:................................... SC2:................................... SC3:................................... SC4:................................... SC5:................................... SC6:................................... SC7:................................... SC8:................................... SC9:................................... SCA:................................... SCB:................................... SCC:................................... SCD:................................... SCE:................................... SCF:................................... ======================================= 16SC $00,$00-$22,$0F BY1.0 S6,D1->S6,D2 --^-- The disk's own RWTS gave no read errors on any track. 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 003 FREE A 008 HELLO *B 002 RELOCATOR A 035 PLOTTER A 029 SEQUENCE A 007 FLEXLOAD *B 005 ASCII.70 B 005 ASCII.56 A 033 PLOT 2 A 027 TRIPLE A 011 MENU A 023 NUM INT B 002 AMPERTEXT B 004 EMUDISK A 032 DE A 034 IMPLICIT A 030 LIMIT B 005 ASCII.40 A 030 POLAR A 032 SURFACE A 021 R SUMS *B 010 FLEX TYPE A 022 DOUBLE A 034 PLOT 3D A 015 GRAPH A 006 ACK A 026 STEEP T 002 NAMECODE B 003 XMGPRT There are two problems with this copy: 1. Depending on how the original disk was written, my copy may or may not be able to read itself. I may need to patch the disk's RWTS to deal with the fact that the disk is now in a standard format. 2. Even if it can read itself, it won't run, because it's executing a copy protection routine during boot to check if the disk is original. (Hint: it's not.) Booting the demuffin'd copy confirms that #1 is not a problem. The disk boots and reads itself perfectly, right up until it crashes at $910A. ~ Slight tangent: have you ever wondered why some RWTS code *doesn't* need to be changed? This original disk couldn't be read by a standard RWTS (because COPYA tried and failed). But once I convert the disk to a standard format, why can the modified RWTS still read a disk in a standard format? This is a common technique for programs that use two disks (a program disk and a data disk), or programs that let you create your own data disks. Typically, the program disk is read-only, but the data disk needs to be writable. Depending on the type of data, users may also expect the data disk to be unprotected, especially if they're creating files that are usable by themselves or by other programs. Thus, "liberal" RWTS code was invented. ]PR#5 ... ]BLOAD RWTS,A$2800 ]CALL -151 *FE89G FE93G ; disconnect DOS *B800<2800.2FFFM ; move RWTS into place *B925L 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- 4C DF BC JMP $BCDF <-- ! B932- 10 FB BPL $B92F B934- C9 DE CMP #$DE B936- D0 0A BNE $B942 B938- EA NOP B939- BD 8C C0 LDA $C08C,X B93C- 10 FB BPL $B939 B93E- C9 AA CMP #$AA B940- F0 5C BEQ $B99E B942- 38 SEC B943- 60 RTS Odd. The usual memory locations ($B935, $B93F) for the data epilogue bytes are unchanged. But the code never gets that far, because at $B92F it jumps to $BCDF to do... something. *BCDFL BCDF- BD 8C C0 LDA $C08C,X BCE2- 10 FB BPL $BCDF BCE4- C9 AB CMP #$AB BCE6- F0 06 BEQ $BCEE BCE8- C9 DE CMP #$DE BCEA- F0 02 BEQ $BCEE BCEC- 38 SEC BCED- 60 RTS BCEE- BD 8C C0 LDA $C08C,X BCF1- 10 FB BPL $BCEE BCF3- C9 FF CMP #$FF BCF5- F0 04 BEQ $BCFB BCF7- C9 AA CMP #$AA BCF9- D0 F1 BNE $BCEC BCFB- 18 CLC BCFC- 60 RTS This code will actually accept two different bytes for the first epilogue, $AB or $DE. And it will also accept two for the second byte, $FF or $AA. Technically this gives you a total of four possible epilogue sequences that this RWTS would read as valid, although I believe this program only uses two of them ("AB FF" for the original program disk, and "DE AA", which is standard, for user-created data disks). Since my demuffin'd copy uses "DE AA", the RWTS can read it without complaint. There are more sophisticated methods that accept a broader or narrower range of bytes without taking up additional space in the RWTS, but that's how this particular disk accomplishes it. ~ Anyway, so the demuffin'd copy can read itself (yay), but it still crashes on boot (boo). In fact, it crashes in exactly the same way as my failed bit copy did, which tells me that the crash is intentional and part of a copy protection routine. Copy ][+ says the boot program is "XMGPRT", a binary file that loads at $9000. So let's start there. ]BLOAD XMGPRT,S6,D1 ]CALL -151 *9000L ; get address of RWTS parameter table 9000- 20 E3 03 JSR $03E3 9003- 85 FB STA $FB 9005- 84 FA STY $FA ; hmm 9007- A9 C5 LDA #$C5 9009- 48 PHA ; copy RWTS parameters 900A- A9 00 LDA #$00 900C- 85 FC STA $FC 900E- A2 03 LDX #$03 9010- BC 4C 90 LDY $904C,X 9013- 91 FA STA ($FA),Y 9015- CA DEX 9016- 10 F8 BPL $9010 9018- 8A TXA ; hmm 9019- 48 PHA The X register will be $FF after the loop, so this means we've pushed $C5FF to the stack. At this point, a bare RTS will "return" to $C5FF+1, i.e. reboot. 901A- 20 3C 90 JSR $903C *903CL ; call the RWTS (most likely just to ; move the drive head to the proper ; position for an impending nibble ; check) 903C- 20 E3 03 JSR $03E3 903F- 20 D9 03 JSR $03D9 9042- A9 00 LDA #$00 9044- 85 48 STA $48 ; if that fails, off to The Badlands 9046- B0 65 BCS $90AD 9048- 60 RTS Caller was $901A, so resuming at $901D: *901DL 901D- A0 01 LDY #$01 901F- B1 FA LDA ($FA),Y 9021- AA TAX 9022- 20 6B 90 JSR $906B *906BL ; turning on the drive motor manually ; is always suspicious 906B- BD 89 C0 LDA $C089,X ; set up a Death Counter 906E- A9 56 LDA #$56 9070- 85 FD STA $FD 9072- A9 08 LDA #$08 9074- C6 FC DEC $FC 9076- D0 04 BNE $907C ; If the Death Counter hits zero, that ; would be bad. Which part of "Death ; Counter" sounded good to you, anyway? 9078- C6 FD DEC $FD 907A- F0 7E BEQ $90FA ; look for a specific nibble ($FB) 907C- BC 8C C0 LDY $C08C,X 907F- 10 FB BPL $907C 9081- C0 FB CPY #$FB 9083- D0 ED BNE $9072 9085- F0 00 BEQ $9087 ; 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) 9087- EA NOP 9088- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 9089- 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) 908C- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 908E- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 908F- B0 0B BCS $909C ; next nibble needs to be $FF 9091- BC 8C C0 LDY $C08C,X 9094- 10 FB BPL $9091 ; ...otherwise we start over 9096- C0 FF CPY #$FF 9098- D0 D8 BNE $9072 ; loop back to get next nibble 909A- F0 EB BEQ $9087 ; execution continues here (from $908F) ; get another nibble 909C- BC 8C C0 LDY $C08C,X 909F- 10 FB BPL $909C ; stash it in zero page 90A1- 84 FC STY $FC ; if the accumulator is anything but ; %00001010, start over 90A3- C9 0A CMP #$0A 90A5- D0 CB BNE $9072 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. It doesn't find that on the bit copy, so it knows it's not running on an original disk. Continuing the code listing... ; get a nibble 90A7- BD 8C C0 LDA $C08C,X 90AA- 10 FB BPL $90A7 ; more bit twiddling 90AC- 38 SEC 90AD- 2A ROL ; AND it with the previously stashed ; nibble 90AE- 25 FC AND $FC 90B0- 49 FF EOR #$FF ; branch to failure path 90B2- D0 46 BNE $90FA ; success path falls through to here -- ; turn off the drive motor and ; (eventually) return to the caller 90B4- DD 88 C0 CMP $C088,X . . . ; failure path (a.k.a. "The Badlands", ; from which there is no return) 90FA- A8 TAY 90FB- DD 88 C0 CMP $C088,X ; manually pop the return address of ; the immediate caller (which leaves ; the manually pushed $C5FF address on ; the top of the stack to "return" to) 90FE- 68 PLA 90FF- 68 PLA ; destroy all trace of this program in ; memory 9100- 99 00 90 STA $9000,Y 9103- C8 INY 9104- C0 00 CPY #$00 9106- D0 F8 BNE $9100 9108- 00 BRK 9109- 00 BRK Literally, that's it. I'm pretty sure they meant to have an "RTS" here (which would reboot), but they had an off-by-1 bug in saving this file and the "RTS" instruction went missing. Meanwhile, the success path returns to the real caller and continues execution at $9025: *9025L ; remove the dummy address that we ; manually pushed to the stack earlier ; (which would reboot if "returned" to) 9025- 68 PLA 9026- 68 PLA ; umm, I'm no expert, but this looks ; like an infinite loop 9027- A0 00 LDY #$00 9029- 84 FE STY $FE 902B- B9 50 90 LDA $9050,Y 902E- 09 80 ORA #$80 9030- 20 39 90 JSR $9039 9033- A4 FE LDY $FE 9035- C8 INY 9036- 4C 29 90 JMP $9029 9039- 6C 36 00 JMP ($0036) Zero page $36 is the DOS output vector, so this prints stuff. In an infinite loop. And this is the *success* path. Never a dull moment in the land of Apple II copy protection. Whatever it's printing, it starts at $9050. This is what's at $9050: *9050.9070 9050- 04 52 55 4E 20 48 45 4C 9058- 4C 4F 20 20 20 20 20 20 9060- 20 20 20 20 20 20 20 20 9068- 20 0D 00 BD 89 C0 A9 56 9070- 85 *FC58G N 400<9050.906AM DRUN HELLOM@ Aha! It's not an infinite loop after all. Well, it is, technically, but it won't actually run forever. It's not easy to show in plain text, but the initial "D" and the final "M@" are in inverse. So that string starts with Ctrl-D and ends with Ctrl-M and a null byte. Since DOS is already loaded, printing this through the DOS vector will execute that command, as if you typed it from a prompt yourself. Since that program never returns to the caller, it will break out of the seemingly "infinite" loop. Since there are no side effects to the copy protection routine, I can change this file to simply skip over it and go straight to running HELLO. *9000:4C 27 90 *BSAVE XMGPRT,A$9000,L$0108 Quod erat liberandum. ~ Changelog 2015-09-06 - Vastly improved explanation of the actual protection routine. Thanks to qkumba for pointing out that my original explanation was inaccurate. 2014-09-26 - initial release --------------------------------------- A 4am crack No. 148 ------------------EOF------------------