------------Tink's Adventure----------- A 4am crack 2015-08-11 --------------------------------------- Name: Tink's Adventure by Mercer Mayer Genre: educational Year: 1984 Authors: Angelsoft, Inc. Publisher: Mindscape Media: single-sided 5.25-inch floppy OS: custom Previous cracks: none Similar cracks: Mathematics Today (crack no. 347) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error, but it gets a participation medal just for showing up Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) no errors, but the copy just boots, briefly ponders the meaning of life, then reboots (doesn't sound like it ever gets off track $00) Copy ][+ nibble editor all tracks use standard prologues (address: D5 AA 96, data: D5 AA AD) but modified epilogues (address: FF FF FF, data: FF FF FF) Disk Fixer ["O" -> "Input/Output Control"] set Address Epilogue to "FF FF FF" set Data Epilogue to "FF FF FF" Success! All tracks readable! T00 -> DOS 3.3 bootloader and RWTS No sign of a disk catalog anywhere No sign of the rest of DOS 3.3 No sign of intelligent life anywhere Why didn't COPYA work? modified epilogue bytes (every track) Why didn't Locksmith FDB work? modified epilogue bytes (every track) Why didn't my EDD copy work? Probably a nibble check during boot. Definitely a nibble check. Disks do not spontaneously reboot unless someone tells them to. That is not a thing that happens naturally. That is just not a thing. Next steps: 1. AUTOTRACE to capture RWTS 2. Advanced Demuffin to convert the disk to a standard format 3. Patch the RWTS (if necessary) 4. Find nibble check and bypass it 5. Declare victory(*) (*) take a nap ~ Chapter 1 In Which We Attempt To Use The Original Disk As A Weapon Against Itself And Learn An Important Lesson About Determination And Believing In Yourself [S6,D1=original disk] [S6,D2=blank 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 /!\ NIBBLE CHECK AT $BB00 ]BRUN ADVANCED DEMUFFIN 1.5 ["5" to switch to slot 5] ["R" to load a new RWTS module] --> At $B8, load "RWTS" from drive 1 ["6" to switch to slot 6] ["C" to convert disk] --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 --^-- That took 23 tries. Attempts 1-10 were full of read errors because the disk was so dirty that it kept coating my floppy drive with muck. Attempts 11-20 got the number of read errors down to about five per rip, but my attempts to stitch together a working copy (by copying individual sectors from different copies) failed. The RWTS had reported that the reads had "succeeded" because some bit on the weaker sectors flipped in just the right combination that the data field checksum passed. Unsurprisingly, this crashed the game, because executable code doesn't take kindly to random bit flipping. Floppies are dying, yo. ~ Chapter 2 Hello $E7, My Old Friend I've Come To Talk To You Again [S6,D1=demuffin'd copy] ]PR#6 ...reboots endlessly... ]PR#5 ... ]BLOAD BOOT1,A$2600 ]CALL -151 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *B700L ; well that's not normal B700- 20 00 BB JSR $BB00 *BB00L ; this is the instruction I expected to ; find at $B700 BB00- 8E E9 B7 STX $B7E9 ; save zero page BB03- A2 00 LDX #$00 BB05- B5 00 LDA $00,X BB07- 9D 00 50 STA $5000,X BB0A- CA DEX BB0B- 10 F8 BPL $BB05 ; don't know what this does yet... BB0D- 20 30 BB JSR $BB30 ; ...but apparently we care about the ; processor flags that it sets BB10- 08 PHP ; restore zero page BB11- A2 00 LDX #$00 BB13- B9 00 50 LDA $5000,Y BB16- 95 00 STA $00,X BB18- CA DEX BB19- 10 F8 BPL $BB13 ; restore processor flags BB1B- 28 PLP ; "carry clear" = success ; (educated guess) BB1C- 90 0F BCC $BB2D ; because now we're setting up a reboot ; via the stack BB1E- A5 2B LDA $2B BB20- 4A LSR BB21- 4A LSR BB22- 4A LSR BB23- 4A LSR BB24- 09 C0 ORA #$C0 BB26- A8 TAY BB27- 88 DEY BB28- 98 TYA BB29- 48 PHA BB2A- A9 FF LDA #$FF BB2C- 48 PHA ; execution continues here regardless BB2D- A6 2B LDX $2B ; "return" to either $B703 (on success) ; or reboot (on failure) BB2F- 60 RTS ; subroutine called from $BB0D BB30- A6 2B LDX $2B ; turn on drive motor manually BB32- BD 89 C0 LDA $C089,X BB35- BD 8E C0 LDA $C08E,X ; probably an address ($BBAE) BB38- A9 AE LDA #$AE BB3A- 85 1E STA $1E BB3C- A9 BB LDA #$BB BB3E- 85 1F STA $1F ; probably Death Counters BB40- A9 0A LDA #$0A BB42- 85 09 STA $09 BB44- A9 80 LDA #$80 BB46- 85 08 STA $08 ; if Death Counter hits 0, jump to ; The Badlands @ $BBA8 BB48- C6 08 DEC $08 BB4A- F0 5C BEQ $BBA8 ; get next address field BB4C- 20 44 B9 JSR $B944 ; if that failed, off to The Badlands BB4F- B0 57 BCS $BBA8 ; check sector number (set by $B944) BB51- A5 2D LDA $2D ; is it the sector we wanted? BB53- C9 0A CMP #$0A ; nope, loop back and try again BB55- D0 F1 BNE $BB48 ; look for $D5 nibble BB57- A0 00 LDY #$00 BB59- BD 8C C0 LDA $C08C,X BB5C- 10 FB BPL $BB59 BB5E- 88 DEY ; no $D5, off to The Badlands with you BB5F- F0 47 BEQ $BBA8 BB61- C9 D5 CMP #$D5 BB63- D0 F4 BNE $BB59 ; look for $E7 nibble BB65- A0 00 LDY #$00 BB67- BD 8C C0 LDA $C08C,X BB6A- 10 FB BPL $BB67 BB6C- 88 DEY ; no $E7, off to The Badlands with you BB6D- F0 39 BEQ $BBA8 BB6F- C9 E7 CMP #$E7 BB71- D0 F4 BNE $BB67 ; look for two more $E7 nibbles BB73- BD 8C C0 LDA $C08C,X BB76- 10 FB BPL $BB73 BB78- C9 E7 CMP #$E7 ; no $E7, off to The Badlands with you BB7A- D0 2C BNE $BBA8 BB7C- BD 8C C0 LDA $C08C,X BB7F- 10 FB BPL $BB7C BB81- C9 E7 CMP #$E7 ; no $E7, off to The Badlands with you BB83- D0 23 BNE $BBA8 ; kill some time to get out of sync ; with the "proper" start of nibbles BB85- BD 8D C0 LDA $C08D,X BB88- A0 10 LDY #$10 BB8A- 24 FF BIT $FF A short digression here into some super low-level disk stuff, because this wasn't low-level enough already... $E7 $E7 $E7 $E7. What would that nibble sequence look like on disk? The answer is, "It depends." $E7 in hexadecimal is 11100111 in binary, so here is the simplest possible answer: |--E7--||--E7--||--E7--||--E7--| 11100111111001111110011111100111 But wait. Every nibble read from disk must have its high bit set. In theory, you could insert one or two "0" bits after any of those nibbles. (Two is the maximum, due to hardware limitations.) These extra "0" bits would be swallowed by the standard "wait for data latch to have its high bit set" loop, which you see over and over in any RWTS code: :1 LDA $C08C,X BPL :1 Now consider the following bitstream: |--E7--| |--E7--| |--E7--||--E7--| 11100111011100111001110011111100111 ^ ^^ (extra) (extra) The first $E7 has one extra "0" bit after it, and the second $E7 has two extra "0" bits after it. Totally legal, works on any Apple II computer and any floppy drive. A "LDA $C08C,X; BPL" loop would still interpret this bitstream as a sequence of four $E7 nibbles. Each of the extra "0" bits appear after we've just read a nibble and we're waiting for the high bit to be set again. Now, what if we miss the first few bits of this bitstream, then start looking? The disk is always spinning, whether we're reading from it or not. If we waste too much time doing something other than reading, we'll literally miss some bits as the disk spins by. This is why the timing of low-level RWTS code is so critical. Let's say we waste 12 CPU cycles before we start reading this bitstream. Each bit takes 4 CPU cycles to go by, so after 12 cycles, we would have missed the first 3 bits (marked with an X). (normal start) |--E7--| |--E7--| |--E7--||--E7--| 11100111011100111001110011111100111 XXX |--EE--| |--E7--| |--FC--| (delayed start) Ah! It's interpreted as a completely different nibble sequence if you delay just a few CPU cycles before you start reading. Also note that some of those "extra" bits are no longer being ignored; now they're being interpreted as data, as part of the nibbles that are being returned to the higher level code. Meanwhile, other bits that were part of the $E7 nibbles are now being swallowed. Now, let's go back to the first stream, which had no extra bits between the nibbles, and see what happens when we waste those same 12 CPU cycles. (normal start) |--E7--||--E7--||--E7--||--E7--| 11100111111001111110011111100111 XXX |--FC--||--FC--||--FC--| (delayed start) After skipping the first three bits, the stream is interpreted as a series of $FC $FC $FC repeating endlessly -- not $EE $E7 $FC like the other stream. Here's the kicker: generic bit copiers didn't preserve these extra "0" bits between nibbles. By "desynchronizing" (wasting just the right number of CPU cycles at just the right time), then interpreting the bits on the disk in mid-stream, developers could determine at runtime whether you had an original disk. Which is precisely the code we just saw. Here is the complete "E7 bitstream," annotated to show both the synchronized and desynchronized nibble sequences. |--E7--| |--E7--| |--E7--||--E7--| 111001110111001110011100111111001110 XXX |--EE--| |--E7--| |--FC--||--E |--E7--| |--E7--||--E7--| |--E7--| 111001110011100111111001110111001110 E--| |--E7--| |--FC--||--EE--| |--E |--E7--||--E7--| 1110011111100111 E--| |--FC--| We now return you to the actual code... ~ Chapter 3 In Which Things Are Restored To Their Proper Place, And Our Adventure Ends *BB8CL ; now start looking for nibbles that ; don't really exist (except they do, ; because we're out of sync and reading ; timing bits as data) BB8C- BD 8C C0 LDA $C08C,X BB8F- 10 FB BPL $BB8C BB91- 88 DEY BB92- F0 14 BEQ $BBA8 BB94- C9 EE CMP #$EE BB96- D0 F4 BNE $BB8C ; compare the next 8 nibbles to an ; array stored at ($1E) [= $BBAE] ; in reverse order BB98- A0 07 LDY #$07 BB9A- BD 8C C0 LDA $C08C,X BB9D- 10 FB BPL $BB9A BB9F- D1 1E CMP ($1E),Y ; if any nibble doesn't match, off to ; The Badlands BBA1- D0 05 BNE $BBA8 BBA3- 88 DEY BBA4- 10 F4 BPL $BB9A ; success falls through to here -- we ; are now satisfied that the disk is ; original, so clear the carry and ; return (to $BB1C) BBA6- 18 CLC BBA7- 60 RTS ; The Badlands (all failures go here) ; decrement the Death Counter and ; eventually give up, set the carry, ; and return (to $BB1C) BBA8- C6 09 DEC $09 BBAA- D0 98 BNE $BB44 BBAC- 38 SEC BBAD- 60 RTS Finally, the array of desynchronized nibbles (the E7 bitstream, in reverse order): BBAE- [FC EE EE FC E7 EE FC E7] This nibble check has no side effects. I can simply bypass it by patching $B700 to restore the STX instruction that was supposed to be there in the first place. T00,S01,$00 change "2000BB" to "8EE9B7" ]PR#6 ...works... The RWTS is flexible enough to read my copy, even in a standard format, so no RWTS patches are required. Quod erat liberandum. --------------------------------------- A 4am crack No. 401 ------------------EOF------------------