---------------Dino Eggs--------------- A 4am crack 2014-03-24 --------------------------------------- Dino Eggs is a 1983 platform game authored by D. Schroeder and distributed by MicroFun. Based on a cursory examination with Copy ][+ nibble editor, the original disk uses a standard 6-2 nibble encoding scheme, but with non-standard epilogue bytes. Address and data prologue are both standard (D5 AA 96 and D5 AA AD, respectively), but the address epilogue appears to be 9E E7 F9. It's not clear what the data epilogue is; it's possible there isn't any or it's just a single byte. Or that I'm just missing it. At any rate, Copy ][+ sector editor can read every sector on the disk once I switch to the "DOS 3.3 patched" option, which ignores epilogue bytes and checksums. Bit copying with EDD 4 (no sync, no track length) yields no errors. The duplicate disk boots, writes some non-text on the text screen, then grinds the disk in an endless loop. So there's something else going on besides just non-standard epilogue bytes. It's probably a nibble check of some sort, and it's very early in the boot process. The easiest way to copy a disk with standard prologue bytes but non-standard epilogue bytes is to use COPYA with a modified RWTS. I can do this solely with a DOS 3.3 master disk; no fancy copy programs required. [S6D1=DOS 3.3 master] ]PR#6 ... ]CALL -151 *B942:18 * ]RUN COPYA The instruction at B942 is normally SEC ($38), which is used to indicate to the rest of DOS that the RWTS failed to find the correct epilogue byte sequence (DE AA) after either the address field or the data field. By changing it to CLC ($18), I'm tricking DOS into thinking that the read was successful and that it should continue. This is a very old trick. Unfortunately, in this case, this trick is insufficient. COPYA reads a few tracks from the original disk, then it complains about a disk read error and gives up. Listening to the disk reads, the problem is around track 8 (plus or minus 1). Poking around with the Copy ][+ sector editor confirms that T08,S0F is unreadable, even with the "DOS 3.3 patched" option that allows me to read every other sector on every other track. WTF? The Copy ][+ nibble editor shows that track 8 is shorter than it should be; it only contains 15 sectors worth of data instead of 16. No wonder COPYA can't be read that sector; it's literally not there. The track just kind of... runs out of data. The missing sector never appears to be accessed, and the original disk works fine without it. I'm guessing this was intentional, on the presumption that anything that makes a disk harder to copy is intentional. Time for some more advanced tools... Quitting out of COPYA (but not rebooting), I can save this modified "no-checksum" RWTS and load it into another copy tool, Advanced Demuffin by The Stack. [S6,D1=work disk] ]BSAVE NO CHECKSUM RWTS,A$B800,L$800 ]PR#6 ]BRUN ADVANCED DEMUFFIN 1.1 --> LOAD NEW RWTS MODULE At $B8, load "NO CHECKSUM RWTS" from D1 [S6,D1=Dino Eggs original disk] [S6,D2=blank disk] --> FORMAT TARGET DISK ...grind grind grind... --> CONVERT DISK This disk is 16 sectors, and I want to copy from track 00 sector 00 to track 08 sector 0E. The options screen looks like this: ADVANCED DEMUFFIN 1.1 - COPYRIGHT 1983 WRITTEN BY THE STACK -CORRUPT COMPUTING ======================================= INPUT ALL VALUES IN HEX SECTORS PER TRACK? (13/16) 16 START TRACK: $00 START SECTOR: $00 END TRACK: $08 END SECTOR: $0E INCREMENT: 1 MAX # OF RETRIES: 1 COPY FROM DRIVE 1 TO DRIVE: 2 ======================================= 16 SC $00,$00 TO $08,$0E BY $01 TO DRV2 Press RETURN to start the copy process: ADVANCED DEMUFFIN 1.1 - COPYRIGHT 1983 WRITTEN BY THE STACK -CORRUPT COMPUTING =======PRESS ANY KEY TO CONTINUE======= TRK:........ +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0:........ SC1:........ SC2:........ SC3:........ SC4:........ SC5:........ SC6:........ SC7:........ SC8:........ SC9:........ SCA:........ SCB:........ SCC:........ SCD:........ SCE:........ SCF:....... ======================================= 16 SC $00,$00 TO $08,$0E BY $01 TO DRV2 Now to copy the rest of the disk, from track 09 onwards. Select "CONVERT DISK" but now change the default values to start at track 09 sector 00 and end at track 22 sector 0F. ADVANCED DEMUFFIN 1.1 - COPYRIGHT 1983 WRITTEN BY THE STACK -CORRUPT COMPUTING =======PRESS ANY KEY TO CONTINUE======= TRK: ........................... +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0: ........................... SC1: ........................... SC2: ........................... SC3: ........................... SC4: ........................... SC5: ........................... SC6: ........................... SC7: ........................... SC8: ........................... SC9: ........................... SCA: ........................... SCB: ........................... SCC: ........................... SCD: ........................... SCE: ........................... SCF: ........................... ======================================= 16 SC $09,$00 TO $22,$0F BY $01 TO DRV2 Now I have a copy that is completely readable by normal DOS 3.3 tools, but which can not read itself. Oops. So the next step is to patch the RWTS so the copy can read itself. Poking through the RWTS, it appears that there IS a data epilogue after all. (I had initially thought maybe there wasn't.) It's "D5 AA", and it's checked at $B92F: B92F- BD 8C C0 LDA $C08C,X B932- 10 FB BPL $B92F B934- C9 D5 CMP #$D5 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 That "CMP #$D5" should be "CMP #$DE". B98B- BD 8C C0 LDA $C08C,X B98E- 10 FB BPL $B98B B990- C9 9E CMP #$9E B992- D0 AE BNE $B942 B994- 18 CLC B995- 60 RTS That "CMP #$9E" should be "CMP #$DE". BB77- BD 8C C0 LDA $C08C,X BB7A- 10 FB BPL $BB77 BB7C- C9 9E CMP #$9E BB7E- D0 26 BNE $BBA6 BB80- E6 26 INC $26 BB82- D0 02 BNE $BB86 BB84- E6 27 INC $27 BB86- BD 8C C0 LDA $C08C,X BB89- 10 FB BPL $BB86 BB8B- C9 E7 CMP #$E7 BB8D- D0 E6 BNE $BB75 BB8F- E6 26 INC $26 BB91- D0 02 BNE $BB95 BB93- E6 27 INC $27 BB95- BD 8C C0 LDA $C08C,X BB98- 10 FB BPL $BB95 BB9A- C9 F9 CMP #$F9 BB9C- D0 D7 BNE $BB75 BB9E- E6 26 INC $26 BBA0- D0 D5 BNE $BB77 Those three CMP instructions are looking for the nibble sequence "9E E7 F9", which the original Dino Eggs disk used as an address epilogue. Change those to look for the standard sequence "DE AA EB" instead, and we should be good to go. Summarizing the changes so far: T00,S09,$35: change $D5 to $DE T00,S09,$91: change $9E to $DE T00,S0B,$7D: change $9E to $DE T00,S0B,$8C: change $E7 to $AA T00,S0B,$9B: change $F9 to $EB My copy boots, but it still displays the same endless grinding behavior as my original bit copy (with EDD4). Now I'm convinced that there's a nibble check hiding somewhere. Looking at the boot sequence through the eyes of my trusty Copy ][+ sector editor, the initial boot sequence seems pretty normal. T0S0 uses the disk controller code in ROM to read all of track 00 into $B000..$BFFF in logical order, so sector 01 ends up in $B100 and sector 0F ends up in $BF00, and so on for all the sectors in between. Then it does the usual indirect JMP ($8FD), which would point to $B100 (stored in track 00 sector 01). That immediately jumps to $B700 (track 00 sector 07), which is where things start going awry. After a quick JSR $BF43, which just copies the RWTS parameter table from standard $B7E8 to the non-standard $BA7A, it jumps to $BC56. This is odd, because in a normal DOS 3.3 RWTS, that's the start of the disk write routine. I guess Dino Eggs never needs to write to the disk, so they reclaimed the space for initialization. The code at $BC56 looks like this: ; initialize reset vector BC56- A0 02 LDY #$02 BC58- B9 FA BC LDA $BCFA,Y BC5B- 99 F2 03 STA $03F2,Y BC5E- 88 DEY BC5F- 10 F7 BPL $BC58 ; maybe a multi-page read routine? BC61- A9 13 LDA #$13 BC63- A2 0F LDX #$0F BC65- A0 04 LDY #$04 BC67- 20 8A BC JSR $BC8A ; again, different address? BC6A- A9 15 LDA #$15 BC6C- A2 0F LDX #$0F BC6E- A0 60 LDY #$60 BC70- 20 8A BC JSR $BC8A ; a third multi-page read? BC73- A9 1B LDA #$1B BC75- A2 0F LDX #$0F BC77- A0 20 LDY #$20 BC79- 20 8A BC JSR $BC8A ; memory move BC7C- A2 0A LDX #$0A BC7E- BD 00 20 LDA $2000,X BC81- 9D 00 A8 STA $A800,X BC84- CA DEX BC85- 10 F7 BPL $BC7E ; game start BC87- 4C 92 A8 JMP $A892 I won't go into all the details here, but my suspicions were correct: $BC8A is a multi-page read routine that takes the track (passed in A) and sector (passed in X) of a track/sector list, then reads data from the tracks/sectors in that list into memory starting at a specific page (passed in Y). For example, T13,S0F looks like this: SECTOR EDITOR DISK A 00- 00 00 00 00 00 00 00 00 @@@@@@@@ 08- 00 00 00 00 13 0E 13 0D @@@@SNSM 10- 13 0C 13 0B 13 0A 13 09 SLSKSJSI 18- 13 08 13 07 13 06 13 05 SHSGSFSE 20- 13 04 13 03 13 02 13 01 SDSCSBSA 28- 13 00 14 0F 14 0E 14 0D S@TOTNTM 30- 14 0C 14 0B 14 0A 14 09 TLTKTJTI 38- 14 08 14 07 14 06 14 05 THTGTFTE 40- 14 04 14 03 14 02 00 00 TDTCTB@@ 48- 00 00 00 00 00 00 00 00 @@@@@@@@ 50- 00 00 00 00 00 00 00 00 @@@@@@@@ 58- 00 00 00 00 00 00 00 00 @@@@@@@@ 60- 00 00 00 00 00 00 00 00 @@@@@@@@ 68- 00 00 00 00 00 00 00 00 @@@@@@@@ 70- 00 00 00 00 00 00 00 00 @@@@@@@@ 78- 00 00 00 00 00 00 00 00 @@@@@@@@ 80- 00 00 00 00 00 00 00 00 @@@@@@@@ TRACK $13, SECTOR $F DOS 3.3 That's all very interesting, but it doesn't answer the one question I wanted to answer: where the hell is the nibble check? The very first call to the multi-read routine loads data into the text page ($0400). On the original, this includes M I C R O F U N P R E S E N T S and so on. It turns out that's not printed by some subroutine; it's read straight from disk into the text page in memory. But on my copy, that text is never shown. It never gets that far. So I've missed something. Astute readers of "Beneath Apple DOS" may recall that the range from $BB00..$BC55 is used by the RWTS routine as a buffer for converting nibbles to bytes. i.e. It gets overridden on the first disk read. But Dino Eggs is using a custom RWTS that uses part of the text page as that buffer instead. (This is why non-text is spewed all over the text screen while the game is loading.) Obviously a custom RWTS can put anything anywhere it likes, but any code at $BB00 should be immediately suspect. Hindsight is 20/20. I'll be honest: the way I arrived at this conclusion was inelegant and brutal. Without the intuition to look at $BB00 first, but having determined that the very first RWTS call was failing, I traced the entire RWTS and hacked "4C 59 FF" (unconditional jump to monitor) into successively later and later addresses until I arrived at the answer. The answer is this: the nibble check is built into the RWTS itself, and the entry point is -- would you believe it -- $BB00. The routine is called from exactly one place, $BE38, with an unconditional JMP. This is at the very end of the RWTS read routine, just before it turns off the disk motor and returns an error code. BE2B- B9 A8 BF LDA $BFA8,Y BE2E- C5 2D CMP $2D BE30- D0 97 BNE $BDC9 BE32- 28 PLP BE33- 90 1C BCC $BE51 BE35- 20 DC B8 JSR $B8DC ; hey now, what's this jump doing here? BE38- 4C 00 BB JMP $BB00 BE3B- 28 PLP BE3C- A2 00 LDX #$00 BE3E- 86 26 STX $26 BE40- 20 C2 B8 JSR $B8C2 BE43- AE F8 05 LDX $05F8 BE46- 18 CLC BE47- 24 38 BIT $38 BE49- A0 0D LDY #$0D BE4B- 91 48 STA ($48),Y BE4D- BD 88 C0 LDA $C088,X BE50- 60 RTS The code at $BB00 looks like this: ; save processor status BB00- 08 PHP ; if no error, continue BB01- 90 03 BCC $BB06 ; jump to start read over again BB03- 4C C9 BD JMP $BDC9 BB06- BD 8C C0 LDA $C08C,X BB09- 10 FB BPL $BB06 ; are we on sector $0F of any track? BB0B- A5 2D LDA $2D BB0D- C9 0F CMP #$0F BB0F- D0 07 BNE $BB18 BB11- 28 PLP ; yes, do the nibble check BB12- 20 1C BB JSR $BB1C BB15- 08 PHP ; if failed, jump to start over BB16- B0 EB BCS $BB03 ; if success, restore processor status ; and continue with RWTS via ; unconditional jump BB18- 28 PLP BB19- 4C 3C BE JMP $BE3C So there isn't just a nibble check once during boot. The check is repeated over and over, whenever the calling code happens to read sector 0F of any track. But the code is consolidated in one place, so (once I found it) I only had to disable it once. By the way, the return address, $BE3C, is almost immediately after the calling address, $BE38. The caller doesn't care about the return value. So the only thing I needed to do is NOP out the jump to $BB00 and balance the processor status pushing and pulling, and the entire thing can just fall through to the real end of the RWTS routine. T00,S0E,$38: change $4C to $EA T00,S0E,$39: change $00 to $EA T00,S0E,$3A: change $BB to $08 Quod erat liberandum. --------------------------------------- A 4am crack 2014-03-24 ------------------EOF------------------