--------The Print Shop Companion------- A 4am crack 2017-06-17 --------------------------------------- Name: The Print Shop Companion Version: 1.2 Genre: graphics Year: 1985 Credits: Roland Gustafsson Publisher: Broderbund Software Platform: Apple ][+ or later (64K) Media: single-sided 5.25-inch floppy OS: custom Previous cracks: none (of this version) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA read error on last pass Locksmith Fast Disk Backup unable to read track $22 copy displays title screen then hangs on main menu EDD 4 bit copy (no sync, no count) read error on track $22 copy displays title screen then hangs on main menu Copy ][+ nibble editor track $22 is quite unusual, with repeated sequences of $D4 $D5 $DE $D4 and other nibbles, with and without timing bits --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 22 START: 1800 LENGTH: 3DFF 1828: 9A 9A BB DA 97 BE BD D5+ VIEW 1830: FF+CB+A6+AE F6 A5 D7 DF 1838: AD+FF+A5+D4+D5 DE D4 BB 1840: FA F5 AA+FF+BD A9 AB BD 1848: A9 EF EF D6 FF+FF+FF+D4 1850: D5 DE D4 BA FE F5 AA AA+ 1858: FF+D4 D5 DE D4 BA FF F5 1860: AA AA AA AA+D4+D5 DE D4 1868: BB FE F5 AA AA AA FF+A9 --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- Disk Fixer track 0 has a custom bootloader track $11 has a DOS 3.3-style disk catalog that lists "FONT." and "BORD." files (presumably fonts and borders loadable from the program) no way to read track $22 (no sectors) Why didn't COPYA work? non-standard structure on track $22 Why didn't Locksmith FDB / EDD work? presumably there is some runtime protection check that triggers after displaying the main menu, which checks the structure of track $22 Next steps: 1. find runtime protection check 2. disable it 3. declare victory (*) (*) go to the gym ~ Chapter 1 In Which We Will Not Be Going To The Gym Any Time Soon Since my copy goes down a different code path than the original, I'm guessing there is a runtime protection check somewhere. One thing that all protection checks have in common is they need to turn on the drive motor by accessing a specific address in the $C0xx range. For slot 6, it's $C0E9, but to allow disks to boot from any slot, developers usually use code like this: LDX LDA $C089,X There's nothing that says where the slot number has to be, although the disk controller ROM routine uses zero page $2B and lots of disks just reuse that. There's also nothing that says you have to use the X-register as the index, or that you must use the accumulator as the load register. But most RWTS code does, out of convention I suppose (or possibly fear of messing up such low-level code in subtle ways). Also, since developers don't actually want people finding their protection- related code, they may try to encrypt it or obfuscate it to prevent people from finding it. But eventually, the code must exist and the code must run, and it must run on my machine, and I have the final say on what my machine does or does not do. But sometimes you get lucky. Turning to my trusty Disk Fixer sector editor, I search the non-working copy for "BD 89 C0", which is the opcode sequence for "LDA $C089,X". [Disk Fixer] ["F"ind] ["H"ex] ["BD 89 C0"] --v-- ------------ DISK SEARCH -------------- $03/$06-$26 $03/$0D-$4F $0A/$0D-$9B $0D/$0B-$D8 PRESS [RETURN] --^-- The first match on track 3 might be my jackpot. The protection routine appears to start at offset $19: --v-- T03,S06 ----------- DISASSEMBLY MODE ---------- ; clear page of memory at $BB00 0019:A0 00 LDY #$00 001B:A9 FF LDA #$FF 001D:99 00 BB STA $BB00,Y 0020:C8 INY 0021:D0 FA BNE $001D ; turn on drive motor manually (always ; suspicious) 0023:AE F8 05 LDX $05F8 0026:BD 89 C0 LDA $C089,X ; look for that nibble sequence I saw ; in the nibble editor: $D4 $D5 $DE $D4 0029:BD 8C C0 LDA $C08C,X 002C:10 FB BPL $0029 002E:C9 D4 CMP #$D4 0030:D0 F1 BNE $0023 ; This subroutine just does the LDA/BPL ; loop to get the next nibble. But it ; tells me that this code is probably ; executing from $B6xx in memory. 0032:20 E5 B6 JSR $B6E5 0035:C9 D5 CMP #$D5 0037:D0 F5 BNE $002E 0039:20 E5 B6 JSR $B6E5 003C:C9 DE CMP #$DE 003E:D0 F5 BNE $0035 0040:20 E5 B6 JSR $B6E5 0043:C9 D4 CMP #$D4 0045:D0 F5 BNE $003C 0047:EA NOP ; get a 4-and-4 encoded value from the ; next two nibbles 0048:BD 8C C0 LDA $C08C,X 004B:10 FB BPL $0048 004D:2A ROL 004E:85 26 STA $26 0050:BD 8C C0 LDA $C08C,X 0053:10 FB BPL $0050 0055:25 26 AND $26 ; transfer value into Y register 0057:A8 TAY ; continue below 0058:4C B5 B6 JMP $B6B5 . . . ; now check for an epilogue of sorts, ; the two-nibble sequence $F5 $AA 00B5:20 E5 B6 JSR $B6E5 00B8:C9 F5 CMP #$F5 00BA:D0 20 BNE $00DC 00BC:20 E5 B6 JSR $B6E5 00BF:C9 AA CMP #$AA 00C1:D0 19 BNE $00DC ; Using the 4-and-4 encoded value as ; a lookup into the page we initialized ; earlier, see if we've seen this value ; already. (We initialized all 256 ; addresses with #$FF.) 00C3:B9 00 BB LDA $BB00,Y ; if we've found this value already, ; skip ahead 00C6:10 14 BPL $00DC ; otherwise mark this value as "found" 00C8:A9 00 LDA #$00 00CA:99 00 BB STA $BB00,Y ; loop through the array of "found" ; values and count how many unique ; values we've seen 00CD:AA TAX 00CE:A8 TAY 00CF:B9 00 BB LDA $BB00,Y 00D2:30 01 BMI $00D5 00D4:E8 INX 00D5:C8 INY 00D6:D0 F7 BNE $00CF ; have we seen #$A0 unique values yet? 00D8:E0 A0 CPX #$A0 ; yes, we're done 00DA:B0 03 BCS $00DF ; no, loop back and read some more 00DC:4C 23 B6 JMP $B623 ; turn off drive motor and exit 00DF:AE F8 05 LDX $05F8 00E2:BD 88 C0 LDA $C088,X 00E5:BD 8C C0 LDA $C08C,X 00E8:10 FB BPL $00E5 00EA:60 RTS --^-- Well that certainly explains why my Locksmith Fast Disk Backup copy hung -- it's looking for a nibble sequence ($D4 $D5 $DE $D4) that doesn't exist because it didn't read anything from track $22. But why can't EDD copy this? What's so special about track $22? ~ Chapter 2 In Which We See Differences But No Clarity On my non-working bit copy (created by EDD 4), I can edit T03,S06 to insert some useful code before we start the protection check. That sector is loaded at $B600, and the code I want looks like this: [S6,D1=non-working copy] B619- 20 EB B6 JSR $B6EB <-- B61C- EA NOP B61D- 99 00 BB STA $BB00,Y B620- C8 INY B621- D0 FA BNE $B61D ... ; all new code here -- ; set reset vector to jump to monitor B6EB- A9 69 LDA #$69 B6ED- 8D F2 03 STA $03F2 B6F0- A9 FF LDA #$FF B6F2- 8D F3 03 STA $03F3 B6F5- 49 A5 EOR #$A5 B6F7- 8D F4 03 STA $03F4 ; reproduce original code from $B619 B6FA- A0 00 LDY #$00 B6FC- A9 FF LDA #$FF B6FE- 60 RTS Now I can press and jump to the monitor while the protection check is running. *C600G ...protection check runs and hangs... Now I'm in the monitor and have unfettered access to all of memory. Let's look at the buffer at $BB00 and see which encoded values are being found and which aren't. *BB00.BBFF BB00- 00 00 00 00 FF FF 00 FF BB08- 00 FF FF FF 00 00 00 00 BB10- 00 00 00 FF FF 00 00 00 BB18- FF 00 FF 00 FF FF 00 FF BB20- 00 00 00 FF 00 00 FF 00 BB28- FF FF 00 00 FF FF 00 00 BB30- FF 00 00 00 00 FF 00 FF BB38- 00 FF 00 FF 00 00 00 00 BB40- FF 00 00 00 FF 00 00 00 BB48- FF 00 00 FF FF FF 00 00 BB50- FF 00 FF 00 FF FF 00 FF BB58- 00 00 00 FF 00 FF 00 00 BB60- FF FF 00 00 FF FF 00 FF BB68- FF FF FF FF FF FF 00 00 BB70- FF 00 00 FF FF 00 00 00 BB78- FF FF 00 FF FF FF 00 FF BB80- FF FF FF FF FF 00 00 FF BB88- FF FF 00 00 FF FF 00 FF BB90- FF 00 FF 00 FF FF FF FF BB98- 00 FF 00 FF FF 00 00 FF BBA0- FF 00 00 00 FF FF 00 FF BBA8- FF FF 00 FF FF 00 00 00 BBB0- FF 00 00 FF FF 00 FF FF BBB8- FF 00 00 FF FF FF 00 00 BBC0- FF 00 00 00 FF FF 00 FF BBC8- FF FF FF FF 00 00 00 00 BBD0- FF FF 00 00 FF 00 00 FF BBD8- FF 00 00 FF FF FF FF FF BBE0- FF FF 00 FF FF 00 00 FF BBE8- FF FF 00 00 00 FF 00 FF BBF0- 00 FF 00 FF FF FF 00 FF BBF8- 00 FF 00 FF FF 00 00 00 I can also reuse the counting routine (at $B6CA) to find out how many values we're finding. *B6CAL B6CA- A9 00 LDA #$00 B6CC- EA NOP B6CD- AA TAX B6CE- A8 TAY B6CF- B9 00 BB LDA $BB00,Y B6D2- 30 01 BMI $B6D5 B6D4- E8 INX B6D5- C8 INY B6D6- D0 F7 BNE $B6CF ; count of zero values is in X register B6D8- 8A TXA ; print it (as a hex value) and exit B6D9- 4C DA FD JMP $FDDA *B6CAG 7E The minimum number of unique 4-and-4 encoded values required to pass the protection is #$A0 (160), but we're only seeing #$7E (126). So we're way short. Why are we missing so many values? To find out why, I want to know exactly which values I'm missing. I would really like the original disk to break into the monitor after it successfully completes this protection check, so I can see what the array at $BB00 looks like. Of course I'm not going to modify my original disk (NEVER EVER DO THAT). But I can add some code on my non- working copy to wait for a keypress before starting the protection check, then swap in the original disk before continuing. That code, still on T03,S06, looks like this: ; wait for keystroke B6EB- 2C 10 C0 BIT $C010 B6EE- AD 00 C0 LDA $C000 B6F1- 10 FB BPL $B6EE B6F3- 2C 10 C0 BIT $C010 ; original code (from $B619) B6F6- A0 00 LDY #$00 B6F8- A9 FF LDA #$FF B6FA- 60 RTS And after succeeding, I want to jump to the monitor: B6D8- E0 A0 CPX #$A0 B6DA- B0 03 BCS $B6DF B6DC- 4C 23 B6 JMP $B623 B6DF- 4C 59 FF JMP $FF59 <-- Rebooting my non-working copy, it gets as far as the main menu and pauses to wait for a key (at $B6EB). Ejecting my copy and inserting the original, then pressing a key, it runs the protection check on the original disk and breaks to the monitor with a very satisfying beep. Now I can see what the $BB00 buffer looks like after a successful run on an original disk: *BB00.BBFF BB00- 00 00 00 00 FF 00 00 FF BB08- 00 FF 00 00 00 00 00 00 BB10- 00 00 00 FF 00 00 00 00 BB18- FF 00 00 00 FF FF 00 FF BB20- 00 00 00 FF 00 00 00 00 BB28- FF 00 00 00 FF FF 00 00 BB30- FF 00 00 00 FF 00 00 FF BB38- 00 FF 00 FF 00 00 00 00 BB40- 00 FF FF 00 FF 00 00 00 BB48- FF 00 00 00 FF 00 00 00 BB50- FF 00 FF 00 00 FF 00 FF BB58- 00 00 00 FF 00 FF 00 00 BB60- FF FF FF 00 FF 00 00 FF BB68- 00 00 FF FF 00 00 00 00 BB70- 00 00 00 FF 00 00 00 00 BB78- 00 FF 00 FF 00 00 00 00 BB80- FF FF 00 FF FF 00 00 00 BB88- FF 00 00 00 FF FF 00 FF BB90- 00 00 FF 00 00 FF 00 FF BB98- 00 FF 00 FF 00 00 00 FF BBA0- FF 00 00 00 FF FF 00 00 BBA8- FF FF 00 FF FF 00 00 00 BBB0- FF 00 00 FF FF 00 FF FF BBB8- FF 00 00 00 FF 00 00 00 BBC0- FF 00 FF FF 00 00 00 FF BBC8- FF FF 00 FF FF 00 00 00 BBD0- FF FF 00 00 FF FF 00 00 BBD8- 00 00 00 00 FF FF 00 FF BBE0- 00 FF 00 FF FF 00 00 FF BBE8- FF FF 00 00 00 FF 00 FF BBF0- FF FF 00 00 00 FF 00 FF BBF8- 00 00 00 FF FF FF 00 00 Superimposing the two arrays, I can see which 4-and-4 encoded values are being found on the original but never found on my non-working bit copy. BB00- .. .. .. .. .. 00 .. .. BB08- .. .. 00 00 .. .. .. .. BB10- .. .. .. .. 00 .. .. .. BB18- .. .. 00 .. .. .. .. .. BB20- .. .. .. .. .. .. 00 .. BB28- .. 00 .. .. .. .. .. .. BB30- .. .. .. .. .. 00 .. .. BB38- .. .. .. .. .. .. .. .. And so on. No obvious pattern emerges. Hmm. I'm going to switch tactics and look at the first value that appears on the original disk but not on my bit copy. 4-and-4 encoded values are not big mysteries. Even unprotected disks use them in the address field of every sector. There were published lookup tables, even in the 80s. Here's an excerpt of one from an old Copy II Plus manual: Dec | Hex | Binary | 4-and-4 ----+-----+----------+-------- 0 | $00 | 00000000 | AA AA 1 | $01 | 00000001 | AA AB 2 | $02 | 00000010 | AB AA 3 | $03 | 00000011 | AB AB 4 | $04 | 00000100 | AA AE 5 | $05 | 00000101 | AA AF 6 | $06 | 00000110 | AB AE 7 | $07 | 00000111 | AB AF 8 | $08 | 00001000 | AE AA 9 | $09 | 00001001 | AE AB The first value missing from my bit copy was 5 (in location $BB05), so let's search the original disk for the nibble sequence "AA AF F5". That's the 4-and-4 encoded value ("AA AF"), plus the first nibble of the expected epilogue ("F5 AA"). --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 22 START: 1800 LENGTH: 3DFF 2AC8: D4 AB AA F5 AA AA AA AA VIEW 2AD0: D4 D5 DE D4 AB AB F5 AA 2AD8: FF FF 9A 9A BB DA 95 AB 2AE0: BD D5 FF 9B D4 D5 DE D4 ^^^^^^^^^^^ 2AE8: AA AF F5 AA FF FF A6 AE <-2AE8 ^^^^^ ^^^^^ 2AF0: F6 A5 BA EF B5 FF FF B5 2AF8: D5 DE D4 AB AF F5 AA FF 2B00: FF 9A BB DA 95 D5 BD D5 FIND: 2B08: FF EC D4 D5 DE D4 AE AB AA AF F5 --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- I highlighted the three relevant sequences, starting at offset $2AE4: - D4 D5 DE D4 (prologue) - AA AF (4-and-4 encoded value) - F5 AA (epilogue) In that same screenshot, we can see evidence of several other values: - "AB AA" (2) at offset $2AC9 - "AB AB" (3) at offset $2AD4 - "AE AB" (9) at offset $2B0E So this confirms what running the protection code already told me: the original disk contains the group of (prologue)(4-4 encoded value)(epilogue) where the encoded value is 5. Now let's see what my non-working bit copy looks like. [S6,D1=non-working copy] --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 22 START: 1800 LENGTH: 3DFF 2430: AA FF D4 D5 DE D4 AA AB VIEW ^^^^^^^^^^^ 2438: F5 AA AA AA D4 D5 DE D4 ^^^^^^^^^^^ 2440: AB AA F5 AA AA AA D4 D5 ^^^^^ 2448: DE D4 AB AB F5 AA FF B3 ^^^^^ 2450: A6 AE F6 A5 AA EF B5 FF <-2450 2458: FF A9 AB BD A9 AA BF D6 2460: FF FF D4 D5 DE D4 AB AE ^^^^^^^^^^^ 2468: F5 AA FF FF A6 AE F6 A5 FIND: 2470: BA FF B5 96 FF D4 D5 DE D4 D5 DE ^^^^^^^^ --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- Highlighting prologues ("D4 D5 DE D4"), we can see the track contains lots of groups with 4-and-4 encoded values: - "AA AB" (1) at offset $2436 - "AB AA" (2) at offset $2440 - "AB AB" (3) at offset $244A - "AB AE" (6) at offset $2466 But no group contains "AA AF" (5), anywhere in the track. I am no closer to understanding why. ~ Chapter 3 Now You See It, Now You Don't Let's return to the original disk and examine the protection track again. [S6,D1=original disk] --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 22 START: 1800 LENGTH: 3DFF 2F58: DE D4 AB AA F5 AA AA AA VIEW 2F60: AA D4 D5 DE D4 AB AB F5 2F68: AA FF 9A 9A BB DA 95 AB 2F70: BD D5 FF FF B5 D5 DE D4 ??^^^^^^^^^ 2F78: AA AF F5 AA AA AA FF A6 <-2F78 ^^^^^ 2F80: AE F6 A5 BA EF B5 FF FF 2F88: 9A 9A BB DA 95 EB FD D5 2F90: FF FF 9A 9A BB DA 95 D5 FIND: 2F98: BD D5 FF FF 9A 9A BB DA AA AF F5 --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- Waaaaait a minute. That can't be right. There's the 4-and-4 encoded value for 5 ("AA AF", at offset $2F78), and the epilogue after it ("F5 AA", at offset $2F7A). But look at the prologue before it. It should be "D4 D5 DE D4" starting at offset $2F74, but the first nibble is missing. Maybe there are two copies of this group and I just found the other one this time? Nope, this is only instance of "AA AF F5" on the track. Did I insert the wrong disk? (I do have an overabundance of floppy disks, so this is always a reasonable question.) Nope, this is the same original disk I read last time. Side note: I realize the offsets are different, but they're not relevant to this mystery. They depend on where the disk was spinning when Copy II Plus started reading the track this time. That could be anywhere; I'd expect the offsets to change every time I re-read a track. But the nibbles themselves should be the same. That's the data on the disk. Data on the disk shouldn't change every time you read it. That's kind of the point of a storage device. Unless... Oh no. Oh God. Oh God no. 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 between each of these groups: 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 you need to 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 it 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. Now, why does this matter? Let's look at the protection code again. It looks for the custom prologue "D4 D5 DE D4", then checks the 4-and-4 encoded value that follows and marks that value as "found" in the buffer at $BB00. But each prologue is preceded by a bitstream that changes every time it's read. Sometimes those random bits will align in such a way that they form two full, valid nibbles, and the next prologue will be read correctly. Other times, they will only form a partial nibble that is completed by the first few bits of the prologue, so the prologue will be missed. As far as I can tell, the sequence of zero bits is 18 bits long. I'm not sure the exact length matters. The important part is the randomness. Here's what's on the disk (the $D4 and $D5 are the start of the prologue): /--00--\/--00--\/--D4--\/--D5--\ 0000000000000000001101010011010101 But remember, more than two consecutive zero bits will form an "abyss" that the floppy drive will fill with randomness. Some of those zero bits before the prologue will randomly transform into "1" bits, and it'll be a different set every time you read the disk. How does that affect the protection check? Here's one of many possible bitstreams that might come out of the abyss: /--FF--\/--9B--\/--D4--\/--D5--\ 0011111111100110111101010011010101 That's what I saw the first time I read the original disk with the Copy II Plus nibble editor: the abyss coalesces into two full nibbles ($FF $9B), then I read the prologue ($D4 $D5 and so on). Here's another possible bitstream that might come out of the abyss: /--FF--\/--FF--\/--B5--\ /--D5--\ 1111111111111111101101010011010101 That's what I saw (on the same disk!) the second time I read it, as shown at the beginning of this chapter. This time, the abyss coalesces into two and a half nibbles -- $FF, $FF, and a few extra bits. Those extra bits combine with the bits that are supposed to be part of the $D4 nibble, but because we're in the middle of a nibble when the $D4 bits start, we end up finishing that nibble ($B5) instead, and the $D4 nibble disappears. The first nibble of the prologue, $D4, gets consumed by the abyss. The second nibble of the prologue, $D5, survives unscathed, but by then it's too late. Without the full prologue, we'll skip this entire group and the 4-and-4 encoded value within it. The protection code requires finding 160 unique 4-and-4 encoded values after a full prologue ($D4 $D5 $DE $D4). The loop at $B6CD counts the number of values it's found by incrementing the X register for every value marked "found" in the buffer at $BB00. At $B6D8, it compares X to #$A0 and branches to the success path when it's found enough unique values. It doesn't care which values it finds, or in which order; it only cares about the total count. If you re-read the original disk enough times, each stream of random bits will (eventually) align so the next prologue is (eventually) read correctly and enough different encoded values are (eventually) marked as "found." You'll miss some of the prologues each time you read the track, but you'll (eventually) find them all as the random bits fluctuate. So the check only passes after some number of reads of a nondeterministic bitstream that sometimes (but not always) corrupts the data after it. But bit copiers don't preserve long streams of zero bits. Instead, they write out whatever phantom "1" bits they find. On a copy, you'll also miss some percentage of prologues -- and thus skip over some percentage of the 4-and-4 encoded values -- each time you read the track. But that percentage will never increase no matter how many times you read it. No more randomness. No more "eventually." God, I hate physical objects. ~ Chapter 4 In Which The Solution Is Trivial Except When It Isn't None of this affects how I can make my copy work. I just need to patch the protection routine at $B619 so it returns gracefully and unconditionally. Making a fresh copy (no more hacks), I can turn to my trusty Disk Fixer sector editor and put an "RTS" at the start of the protection code: T03,S06,$19: A0 -> 60 ]PR#6 ...boots to main menu, at last... Except... In the course of testing, I discovered one final small slight really hardly noticeable little problem... It doesn't print. I mean, it prints, but it doesn't print what I want it to print. It's all just garbage. At first, I assumed this was a problem with my emulator. (I no longer have a real printer to test with.) But no. Through some emulator trickery, I made an unpatched copy "pass" the protection check by altering the CPU flow without altering the code on disk or in memory, and that copy printed just fine. Something is detecting that I modified the protection code. Now, there are any number of ways to checksum a region of memory, but the Apple II is not a fast machine, and realistically it's probably just XORing the bytes together. Another possibility is ADDing the bytes together. But throw away your modern conceptions of some sort of cryptographic hash function. In the 80s, any "encryption" was probably just XOR, and any "checksum" was probably one byte long and was the cumulative XOR of all the other bytes. (Story time! Sierra was notorious for their anti-tamper checks. Games would have a self-decrypting protection check and a separate anti-tamper check that XOR'd the encrypted protection code. But the first two bytes of the code were always the same, and word got around of how you could defeat the protection AND the anti-tamper check in all their games by searching for the first two bytes and changing them to known values. In response, Sierra added a second anti-tamper check that checksummed only the odd bytes.) What I'm saying is that I can probably fool this tamper check without even finding it, by applying the principle of transitivity. I changed an $A0 byte to $60 to defeat the protection. Now I need to find a $60 byte and change it to $A0 to defeat the anti-tamper check. And there is one, in the same sector, at offset $EA: the "RTS" instruction at the end of the subroutine that starts at $B6E5 (but is no longer called since I put an "RTS" at the very beginning of the protection code to bypass the entire thing). T03,S06,$EA: 60 -> A0 ]PR#6 ...works, and prints correctly... Sometimes it's better to be lucky than good. After extensive testing, there doesn't appear to be any further protection. Quod erat liberandum. --------------------------------------- A 4am crack No. 1257 ------------------EOF------------------