---------------Ultima II--------------- A 4am crack 2022-06-07 --------------------------------------- Name: Ultima II Version: rev. 3 (*) Genre: RPG Year: 1982 Credits: Kenneth W. Arnold, Richard Garriott, Keith Zabalaoui, Helen Walker, Owen Garriott, Howard Makler, Mary Taylor Rollo Publisher: On-Line Systems Platform: Apple ][+ or later (48K) Media: 5.25-inch disk Sides: 3 OS: DOS 3.3 Previous cracks: none (of this version) (*) I have found evidence of at least three releases. The second one makes unspecified changes in the ULTIMA II.OBJ file. This third one adds a COPYIT file which runs when you first create a character, which prompts you to make a copy of the (unprotected) player master disk. ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but program master boots to DOS, fills the screen with inverse "R" characters, and reboots Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Passport copies but applies no patches copy has same behavior has others Copy ][+ nibble editor no obvious shenanigans no track $23 Disk Fixer T00-T02 appear to be standard DOS 3.3 except that commands like LOAD and SAVE have been changed to L?AD and S?VE. Also, on T01,S0F, the sector number of the directory VTOC is changed from 0 to 1. T11 appears as a corrupted disk catalog because of the aforementioned VTOC change. Copying T11,S01 to T11,S00 allows third-party tools to perform a CATALOG and load files T01,S09 -> startup program is "HELLO" Why didn't COPYA work? Probably a runtime protection check looking for... something? Between the sectors I guess? Why didn't Locksmith FDB work? ditto Why didn't my EDD copy work? An excellent question... that I can't yet answer. Next steps: 1. Find the protection check 2. Disable it 3. Declare victory (*) (*) go to the home gym ~ Chapter 1 Unlucky In Cards Since my copy goes down a different code path than the original, I'm guessing there is a runtime protection check somewhere. Disks do not simply fill the screen with repeating garbage and reboot unless someone tells them to. Firing up my trusty sector editor, I look for obvious shenanigans but have no luck. Outside of DOS, there are no instances of "LDA $C089, X" to turn the drive motor on. In fact, there are no other instances of "89 C0" at all. Nor "E9 C0" (to hit the slot 6 motor without a slot index). Nor even "8C C0" (to hit the data latch). The disk puts the directory VTOC on T11,S01 and fills T11,S00 with garbage. Copying sector 1 over sector 0 allows me to access the files. [S6,D1=non-working copy] [S5,D1=work disk] ]PR#5 ]CATALOG,S6 C1983 DSR^C#254 073 FREE *B 033 HELLO *B 007 II *B 013 FL *B 004 WII *B 012 UL *B 012 STD SUBS.OBJ *B 003 UPDATE.OBJ *B 008 CREATE *B 003 PLAYER *B 006 TABLES.903C.OBJ *B 010 SHAPES *B 010 MONSTERS! *B 009 DNGDRAW.OBJ *B 059 ULTIMA II.OBJ *B 033 PIC.OUT *B 033 PIC.TWN *B 033 PIC.DNG *B 033 PIC.SPA *B 033 PIC.MIN *B 033 PIC.CAS *B 033 PIC.RUS B 004 COPYIT ]BLOAD HELLO ]PAD A$6000,L$1420 ]CALL -151 *6000L 6000- 20 58 FC JSR $FC58 6003- 20 80 72 JSR $7280 6006- 04 ??? 6007- CE CF CD DEC $CDCF 600A- CF ??? 600B- CE A0 C9 DEC $C9A0 600E- AC CF AC LDY $ACCF 6011- C3 ??? 6012- 8D 00 This looks like garbage but it's just embedded ASCII in the code, probably being read by the subroutine at $7280. *400<6006.6012M DNOMON I,O,CM The "D" is inverse, so it's really a Ctrl-D. The subroutine would manipulate the stack pointer to get access to the "embedded" command, then print the characters through COUT, which would trigger a DOS command and execute it as if you had typed it at the prompt yourself. *7280L 7280- 20 E0 72 JSR $72E0 7283- 4C F8 66 JMP $66F8 Hmm, that is not what I expected, but maybe there are more layers and the sub-sub-routine is what prints the embedded command through COUT. *72E0L 72E0- AD F3 03 LDA $03F3 72E3- 8D F4 03 STA $03F4 72E6- 4C F7 72 JMP $72F7 Clobbering the reset vector like this will make reset reboot, which is fine but still has nothing to do with this embedded command. *72F7L 72F7- CE FA 72 DEC $72FA 72FA- EF ??? 72FB- FA ??? 72FC- 72 ??? More embedded ASCII? No! This is a self-decrypting routine. The first instruction at $72F7 is changing the second instruction at $72FA. You can get away with this on an Apple II because there is absolutely no pipeline cache. The opcode at $72FA won't be read until the previous instruction completes. *72FA:EE *72FAL 72FA- EE FA 72 INC $72FA This, however, will have no effect on the instruction while it's executing. (Of course it will change the memory location in case the program counter ends up there again in the future.) So we've decremented and incremented the same memory location. Fantastic. ~ Chapter 2 Are You Watching Closely? Continuing from the next instruction at $72FD... 72FD- AD 1F 73 LDA $731F 7300- 49 8A EOR #$8A 7302- D0 01 BNE $7305 7304- 20 8D 1F JSR $1F8D 7307- 73 ??? *731F 731F- 60 The branch at $7302 is always taken... into the middle of the next instruction listed at $7304. That means the JSR is fake and will never be called, because execution continues at $7305. *7305L 7305- 8D 1F 73 STA $731F 7308- 18 CLC 7309- D0 01 BNE $730C 730B- 4C A0 29 JMP $29A0 Again with the branch-into-the-middle- of-the-next-instruction obfuscation. That JMP target is irrelevant, because the JMP is never executed. *730CL 730C- A0 29 LDY #$29 730E- 98 TYA 730F- 90 01 BCC $7312 7311- 20 59 F7 JSR $F759 Again. *7312L 7312- 59 F7 72 EOR $72F7,Y 7315- 99 F7 72 STA $72F7,Y 7318- C8 INY 7319- D0 F3 BNE $730E 731B- 88 DEY 731C- 30 01 BMI $731F 731E- 4C 60 E1 JMP $E160 Again. *731FL 731F- 60 RTS OK, I'm going to put NOP instructions at each of the branch-over-opcode locations, so I can see the entire listing at once and figure out what's going on. *7304:EA *730B:EA *7311:EA *731E:EA *72FDL 72FD- AD 1F 73 LDA $731F 7300- 49 8A EOR #$8A 7302- D0 01 BNE $7305 7304- EA NOP ; note: this is modifying code later in ; this routine 7305- 8D 1F 73 STA $731F 7308- 18 CLC 7309- D0 01 BNE $730C 730B- EA NOP 730C- A0 29 LDY #$29 730E- 98 TYA 730F- 90 01 BCC $7312 7311- EA NOP ; decrypt the code at $72F7+$29, which ; is $7320 7312- 59 F7 72 EOR $72F7,Y 7315- 99 F7 72 STA $72F7,Y 7318- C8 INY 7319- D0 F3 BNE $730E 731B- 88 DEY 731C- 30 01 BMI $731F 731E- EA NOP ; this was modified earlier 731F- 60 RTS $60 XOR $8A = $EA, so by the time we reach this instruction, it's been changed to a NOP and execution will continue to... the decrypted code! Clever. This decryption routine is self- contained. If I run it from $7308 (after the RTS-to-NOP modification), I can see the decrypted code at $7320. *7308G (returns to monitor) And here we go. ~ Chapter 3 These Are A Few Of My Favorite Things I am, like, 99% sure that we have found the actual copy protection routine. Otherwise why go to all this trouble to hide it? *7320L 7320- C8 INY ; Y was $FF at the end of the loop that ; decrypted this code, so now it's $00 7321- 8C F4 B7 STY $B7F4 7324- 8C EC B7 STY $B7EC ; seek to track 0 7327- A9 B7 LDA #$B7 7329- A0 E8 LDY #$E8 732B- 20 00 BD JSR $BD00 ; turn on drive motor (aha! this is why ; I couldn't find it before, because it ; was encrypted) 732E- BD 89 C0 LDA $C089,X ; death counter maybe? 7331- A9 05 LDA #$05 7333- 8D 00 BB STA $BB00 7336- 20 87 73 JSR $7387 *7387L 7387- A9 1C LDA #$1C 7389- 8D 02 BB STA $BB02 ; reset data latch 738C- BD 8E C0 LDA $C08E,X ; find $D5 nibble 738F- BD 8C C0 LDA $C08C,X 7392- 10 FB BPL $738F 7394- C9 D5 CMP #$D5 7396- EA NOP 7397- EA NOP 7398- F0 0F BEQ $73A9 ; $BB01/$BB02 is a failsafe counter for ; finding the nibble. (Note that $BB01 ; is uninitialized, but it's the low ; byte of the 2-byte word so it doesn't ; matter much.) 739A- CE 01 BB DEC $BB01 739D- D0 F0 BNE $738F 739F- CE 02 BB DEC $BB02 73A2- D0 EB BNE $738F ; if we can't even find $D5, pop the ; stack (because we JSR'd here) and ; jump to what I assume is The Badlands 73A4- 68 PLA 73A5- 68 PLA 73A6- 4C 5A 73 JMP $735A ; execution continues here (from $7398) ; once we find the $D5 nibble 73A9- BD 8C C0 LDA $C08C,X 73AC- 10 FB BPL $73A9 ; now looking for $AA 73AE- C9 AA CMP #$AA 73B0- D0 E2 BNE $7394 73B2- 48 PHA 73B3- 68 PLA 73B4- BD 8C C0 LDA $C08C,X 73B7- 10 FB BPL $73B4 ; then $96 73B9- C9 96 CMP #$96 73BB- D0 F1 BNE $73AE 73BD- A0 05 LDY #$05 73BF- 20 EC 73 JSR $73EC *73ECL ; a compact little subroutine that will ; skip over a given number of nibbles ; (Y is set in the caller) 73EC- BD 8C C0 LDA $C08C,X 73EF- 10 FB BPL $73EC ; just burning cycles so we don't ; accidentally read the same nibble ; twice 73F1- 48 PHA 73F2- 68 PLA 73F3- 88 DEY 73F4- D0 F6 BNE $73EC ; note that the last nibble read is ; still in A when we return 73F6- 60 RTS Continuing from $73C2... ; the 5th nibble needs to be $AA (this ; will be part of the sector number in ; the address field) 73C2- C9 AA CMP #$AA 73C4- D0 C9 BNE $738F 73C6- BD 8C C0 LDA $C08C,X 73C9- 10 FB BPL $73C6 ; the 6th nibble also needs to be $AA ; (this will be the other half of the ; sector number in the 4-and-4-encoded ; address field, so we're looking for ; sector 0) 73CB- C9 AA CMP #$AA 73CD- D0 C0 BNE $738F 73CF- 48 PHA 73D0- 68 PLA 73D1- BD 8C C0 LDA $C08C,X 73D4- 10 FB BPL $73D1 ; unconditionally (without counting) ; look for the next $D5 nibble, which ; should be the start of the data ; prologue for sector 0 73D6- C9 D5 CMP #$D5 73D8- D0 F7 BNE $73D1 73DA- EA NOP 73DB- BD 8C C0 LDA $C08C,X 73DE- 10 FB BPL $73DB ; $AA nibble, also part of the data ; prologue 73E0- C9 AA CMP #$AA 73E2- D0 F2 BNE $73D6 73E4- EA NOP ; skip $100 nibbles 73E5- A0 00 LDY #$00 73E7- 20 EC 73 JSR $73EC ; skip $5B more nibbles 73EA- A0 5B LDY #$5B ; note: this falls through to the same ; subroutine entry point we called ; earlier, which is nice 73EC- BD 8C C0 LDA $C08C,X 73EF- 10 FB BPL $73EC 73F1- 48 PHA 73F2- 68 PLA 73F3- 88 DEY 73F4- D0 F6 BNE $73EC 73F6- 60 RTS Routines that fall through to other routines that they previously called as subroutines are my favorite thing about assembly language. (My least favorite thing is everything else.) So far, we've seeked to track 0, found sector 0, and skipped basically all of it. Continuing from $7339... ; this will always branch, because the ; last thing that happened was that Y ; was decremented to 0, which is still ; technically positive as far as the ; 6502 is concerned 7339- 10 01 BPL $733C 733B- 20 C8 C0 JSR $C0C8 !@#$% Grr. *733CL ; burn some number of cycles ; (Y starts at 0 because that's where ; it ended in the previous subroutine) 733C- C8 INY 733D- C0 4B CPY #$4B 733F- 90 FB BCC $733C 7341- F0 01 BEQ $7344 7343- 4C BD 8C JMP $8CBD I'm so tired. *7344L ; on first glance this looks like a ; regular LDA/BPL loop to read a nibble ; EXECPT it's branching forwards, not ; backwards 7344- BD 8C C0 LDA $C08C,X 7347- 10 0A BPL $7353 ; now comparing the RAW DATA LATCH READ ; at $7344 7349- C9 C9 CMP #$C9 734B- D0 0D BNE $735A ; success path falls through here -- ; turn off drive motor and exit via ; $72F7 (more on this in a moment) 734D- BD 88 C0 LDA $C088,X 7350- 4C F7 72 JMP $72F7 ; execution continues here (from $7347) ; if the raw data latch read is NOT the ; expected $C9 value 7353- EA NOP 7354- EA NOP ; apparently we get one more chance to ; find the right nibble, because after ; burning some cycles with the NOPs, if ; the data latch has gone high (meaning ; a full nibble value is available), we ; branch back to the CMP to see if it ; now matches the expected $C9 nibble 7355- BD 8C C0 LDA $C08C,X 7358- 30 EF BMI $7349 ; decrement Death Counter and try again 735A- CE 00 BB DEC $BB00 735D- D0 D7 BNE $7336 ; out of chances -- copy the final code ; to the bottom of the stack page and ; jump there (we ain't comin' back) 735F- A2 1F LDX #$1F 7361- 9A TXS 7362- BD 6E 73 LDA $736E,X 7365- 9D 00 01 STA $0100,X 7368- CA DEX 7369- 10 F7 BPL $7362 736B- 4C 00 01 JMP $0100 ; (executes from $100) ; wipe main memory and reboot 736E- A9 12 LDA #$12 7370- 9D 00 02 STA $0200,X 7373- E8 INX 7374- D0 FA BNE $7370 7376- EE 04 01 INC $0104 7379- AC 04 01 LDY $0104 737C- C0 C0 CPY #$C0 737E- F0 F6 BEQ $7376 7380- C0 01 CPY #$01 7382- D0 EC BNE $7370 7384- 6C FC FF JMP ($FFFC) So that's it. At one of two exact times after sector 0, there needs to be a $C9 nibble. If we find it, we exit through $72F7; otherwise we wipe memory and reboot. Wait, why are we exiting through $72F7? Isn't that where we started? Surely we are not stuck in an infinite loop. Indeed we are not. $72F7 will (again) decrement and re-increment the opcode at $72FA, then do this again: 72FD- AD 1F 73 LDA $731F 7300- 49 8A EOR #$8A 7302- D0 01 BNE $7305 7304- EA NOP 7305- 8D 1F 73 STA $731F Previously, that changes the opcode at $731F from "RTS" to "NOP", so execution fell through to the protection code at $7320. But now it does the opposite: it changes the "NOP" back to "RTS"! And since the rest of the decryption was also just XOR, it will end up re- encrypting the protection code in memory, keeping it relatively safe from prying eyes. (I am the prying eyes.) Then it hits $731F again, which is now an "RTS", so it gracefully exits to the caller. Very very smooth. ~ Chapter 4 Now You See It, Now You Don't I understand WHAT the protection check is doing, but I don't understand WHY. A sector copy of this original disk will not copy the nibbles between the sectors, which explains why COPYA and similar fast copiers failed. But what is so special about the nibbles after track 0, sector 0? Why can't a bit copier like EDD copy it? Using the Copy ][+ nibble editor, we can look at the raw nibbles on track 0, including the crucial $C9 nibble. Here is track 0, starting near the end of the data field of sector 0. (I've converted the inverse characters into "+" signs to indicate where Copy ][+ found one or more timing bits after a nibble.) --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. ---------------------------------------- TRACK: 00 START: 2029 LENGTH: 188E 2188: 96 96 96 96 96 96 96 96 VIEW 2190: 96 96 96 96 96 96 96 EA 2198: EC 9A DE AA EB EA+FF+FF+ 21A0: FF+FF+BA+FF+FF+FF+FF+FF+ 21A8: FF+FF+FF+FE+C9+FF+FF+FF+ <-21AC 21B0: FF+FF+FF+AC+FF+99+FF+99+ 21B8: E4 E4 E4+FF+A4 FF+9C FF+ 21C0: FF+FF+FF+FF+92 92 92 FF+ 21C8: 96 FF+FF+FF+9D F2+FF+FF+ --^-- The exact nibble offsets are not important (they'll change if we re-read the disk), but this is what we have at offset $219A: DE AA EB ; data epilogue EA FF FF FF FF BA FF FF FF FF FF FF FF FF FE C9 ... Seems normal enough. But let's read the same track again... --v-- 26F0: 96 96 96 96 96 96 96 96 VIEW 26F8: 96 96 96 96 96 EA EC 9A 2700: DE AA EB EA+D9+FF+E5 E5+ 2708: FF+FF+FF+FF+FF+9F E7 F9 2710: FC C9 C9 C9+FF+FF+FF+D6 <-2712 2718: DB+FF+A4+FF+FF+FF+AD 94 2720: FF+FF+A9 A9 CB+FF+FF+FF+ 2728: FF+FF+FF+EA FF+FF+FF+FF+ 2730: FF+BA+FF+FF+FF+FF+FF+FF+ --^-- Again, the exact nibble offsets are not important. But look at the "data" after sector 0 (starting at offset $2700): DE AA EB ; data epilogue EA D9 FF E5 E5 FF FF FF FF FF 9F E7 F9 FC C9 C9 C9 In fact, this region of the original disk will look different every time we read it. How? I'm glad you asked. There's only one thing you can put on a disk that will change every time you read it: nothing. And by "nothing," I mean "a long sequence of zero bits." And that's what is on the original disk before (and, as it turns out, after) the $C9 nibble: nothing. A bit of background. When we say a "zero bit," we really mean "the lack of a magnetic state change." The Disk II drive isn't digital; it's analog. If it doesn't see a state change in a certain period of time, it calls that a "0". If it does see a change, it calls that a "1". But the drive can only tolerate a lack of state changes for so long -- about as long as it takes for two bits to go by. Fun fact(*): this is why disks use nibbles as an intermediate on-disk format in the first place. No valid nibble contains more than two zero bits consecutively, when written from most- significant to least-significant bit. (*) not guaranteed, actual fun may vary So what happens when a drive doesn't see a state change after the equivalent of two consecutive zero bits? The drive thinks the disk is weak, and the MC3470 chip inside the drive starts increasing the amplification to try to compensate, looking for a valid signal. But there is no signal. There is no data. There is just a yawning abyss of nothingness. Eventually, the drive gets desperate and amplifies so much that it starts returning random bits based on ambient noise from the disk motor and the magnetism of the Earth. Seriously. It's trivial to write zero bits to a disk; just write a #$00 nibble to write 8 zero bits -- like any other 8-bit nibble. You can write whatever you want to a disk; it doesn't need to be what DOS would consider a "valid" nibble. But when you read that nibble back, the drive can't handle 8 zero bits in a row, so it will actually return some random bits. Which is why no one does that. Returning random bits doesn't sound very useful for a storage device, but it's exactly what the developer wanted, and that's exactly what this copy protection scheme depends on. Here's why: Bit copiers can't duplicate a long sequence of zero bits. Why? Because that's not what they see. What they see is some random bits -- the real zero bits interspersed with phantom "1" bits. So that's what they write to the target disk. Whatever randomness they get when they read the original disk will essentially get "frozen" onto the copy. But wait, it gets worse. ~ Chapter 5 In Which We Jump Out Of The System, Then Jump Back In To understand exactly what is on the original disk, and why this copy protection works, we need to jump outside the system and use modern tools that can see the disk at a different level. With an Applesauce hardware controller, a modified drive, and the associated Applesauce.app software, we can get a flux-level read of exactly what is on the original disk. Starting immediately after the data epilogue of sector 0: 11101010 ; $EA nibble 00000000 ; long sequence of 0 bits ... ; (102 "bits" long) 1111111100 ; $FF/10 (sync nibble) 1111111100 ; $FF/10 (sync nibble) 111111100 ; $FE/9 (sync nibble) 11001001 ; $C9 nibble 00000000... ; long sequence of 0 bits Those 102 bits after the $EA nibble can randomly resolve into anything, which will then be interpreted as nibbles. The sync nibbles might resynchronize the stream, so that when it reads the data latch at the crucial moment, the $C9 nibble is there. Or they might not. There aren't enough sync nibbles to guarantee resynchronization. (See p. 3-8 of "Beneath Apple DOS" for a more detailed explanation of why you need at least four $FF/10 nibbles to resynchronize in all cases.) With the element of randomness, it's possible that those 102 zero bits could resolve to a bitstream where the nibbles are still out of phase at the crucial moment when it checks for the $C9 nibble. In that case, it will decrement the Death Counter in $BB00 and start over. It has 5 chances to get it right. Could this actually happen? I have the original disk and real hardware, and I know how to get the protection code decrypted in memory, so let's find out. ; return on success (instead of exiting ; through $72F7 to re-encrypt) *7350:60 ; return on failure (instead of jumping ; to The Badlands) *735F:60 And now a driver that calls this patched protection code in memory: *300:A0 FF 20 20 73 AE 00 BB FE 41 03 CE 40 03 D0 F0 60 *300L 0300- A0 FF LDY #$FF 0302- 20 20 73 JSR $7320 0305- AE 00 BB LDX $BB00 0308- FE 41 03 INC $0341,X 030B- CE 40 03 DEC $0340 030E- D0 F0 BNE $0300 0310- 60 RTS *340:0 0 0 0 0 0 0 [S6,D1=original disk] *300G ...read read read... *341.346 0341- 00 02 08 0C 29 C1 That's a nice distribution. It always succeeded eventually -- about 90% of the time on the first or second try. But this test clearly shows that the Death Counter is a necessary component of this copy protection. Two times out of 256, it only succeeded on the fifth and final try! I ran the same test a few more times and got a similar distribution. But on the fourth run, $0341 was non-zero, meaning that the death counter had decremented to 0 and the protection had failed. About one time in a thousand, this protection check will fail on an original disk. ~ Chapter 6 May The Odds Be Ever In Your Favor So what happens on a bit copy? The short answer is... it depends. It might work! It's random! Maybe it will interpret the random bitstream as a sequence of nibbles that is just the right length AND just happens to synchronize to the $C9 nibble, in which case the copy will pass the protection check. The long answer is... the deck is stacked against you. In the general case, bit copiers are trying to do the impossible, and they succeed a surprising amount of the time. On a disk like this, most parts of each track are highly structured. There are sectors. There are address fields and data fields, each with prologues and epilogues and internal checksums. By design, you can verify that you read each of these things properly. But the bits between the sectors are the "wild west," with little to no structure to fall back on. Compounding their troubles, bit copiers can not tell the difference between 1 and 2 zero bits after a nibble. The disk is just too fast and the CPU is too slow; there's barely enough time to determine that there's a single zero bit (and even that routine has limitations that can be exploited). Sync nibbles ($FF/10) are generally OK because they can assume that those are being used as sync nibbles. But this is an assumption, not anything they can verify in real time, and after 4 sync nibbles in a row, even advanced bit copiers like EDD will start writing out $FF/9 instead, unless you tell them otherwise. (Adventure International uses this assumption against copiers by having a long field of $FF/10 nibbles and noticing that they get rewritten as $FF/9 nibbles. See 4am crack no. 1563 "The Kingdom of Facts" for details.) Anyway, bit copiers have a fundamental problem: they can't tell exactly what's on the disk, because the disk is too fast and the CPU is too slow. So they compensate, guess, and reconstruct. They build the most probable bitstream based on the nibble stream at any given point on the track. But they do not, and can not, "duplicate" the bitstream. Every copy is a reconstruction. When you read complete garbage that changes every time it comes around, then try to reconstruct that into some sort of reasonable bitstream that makes sense as a nibble stream, you are going to fail because there aren't really any nibbles. There is just the yawning abyss. Using the same Applesauce software, I imaged my non-working copy and examined the problematic region after track 0, sector 0. Keep in mind that each copy would look slightly different, due to how it reads the random garbage. But even a single copy is instructive. 111010100 ; $EA/9 110000000 ; $C0/9 101110010 ; $B9/9 100111000 ; $9C/9 110100100 ; $D2/9 100000100 ; $82/9 10100111 ; $A7 10100111 ; $A7 101001010 ; $A5/9 100000000 ; $80/9 101010000 ; $A8/9 10111111 ; $BF 11001111 ; $CF 11110011 ; $F3 11111001 ; $F9 10010010 ; $92 100001100 ; $86/9 111010010 ; $E9 This is EXTREMELY interesting. At first glance, it looks nothing like the original disk, because some of those 102 0 bits turned into 1 bits. Fine, we knew that would happen. But more importantly, there's no $C9 nibble. Or is there? If you take the $BF nibble and ignore the first two bits, you get this: 111111 11001111 11110011 11111001 10010010 Here, let me rewrite that for you: 1111111100 ; $FF/10 1111111100 ; $FF/10 111111100 ; $FE/9 110010010 ; $C9/9 So there is a $C9 nibble, but it's out of phase, and the sync field isn't long enough to resync the data latch to see it. But more importantly, IT'S IN THE WRONG PLACE. Count the bits. Between the $EA nibble and the first $FF/10 nibble, the original disk has 102 bits. This copy only has 91 bits. By the time we try to read the crucial $C9 nibble, we're long past it. Finally, because the rest of the abyss has been turned into "real" nibbles, re-reading the track will not change the result. The bitstream is too short, the crucial nibble is out of phase, there's no longer any meaningful randomness, and you're all out of luck. The original disk might fail 0.1% of the time, but this copy will fail 100% of the time. ~ Chapter 7 Back To The Crack Searching the disk for the bytes "20 E0 72" (JSR $72E0, the entry point of the protection routine), I find one match on track $03. ; change "JSR" to "BIT" T03,S0C,$84: 20 -> 2C The code immediately following jumps to $66F8, which (finally!) prints the embedded ASCII command to COUT and returns to the caller. There do not appear to be any other side effects or delayed anti-tamper checks, although I did notice that $7280 is called several more times. But every caller routes through $7280, never $72E0, so this one-byte patch is sufficient to bypass all the protection checks. ~ Epilogue Out of curiosity, I found a scan of an old EDD program list, which gives brief descriptions of programs that require non-default parameters. Here is what they suggest to make a protected backup: --v-- drive speed critical recopy t0 until boots t0 parm normal or bitcopy and/or autonc or manualnc --^-- "recopy t0 until boots" Now you know why. Quod erat liberandum. --------------------------------------- A 4am crack No. 2847 ------------------EOF------------------