-------------Number Bowling------------ A 4am crack 2014-08-13 --------------------------------------- "Number Bowling Decimals and Fractions" is a 1985 educational game distributed by Scott, Foresman and Company. COPYA successfully copies the disk, but the copy does not work; it just reboots endlessly. In my experience, disks do not spontaneously reboot unless someone tells them to. The original disk does not boot like a normal DOS 3.3 disk. My trusty Copy ][+ sector editor finds no evidence of a disk catalog on track $11 (or any other track, for that matter). Time for boot tracing with AUTOTRACE. [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 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. I didn't need the RWTS (the disk is already using a standard format, since COPYA can copy it), but the fact that my AUTOTRACE program got that far is interesting in and of itself. That tells me that the boot0 code on T00,S00 is completely standard (my AUTOTRACE program is very sensitive to any deviation from the norm there), so I should start looking for a nibble check or something out of the ordinary in the boot1 code. ]BLOAD BOOT1,A$2600 ]CALL -151 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *B700L ; normal RWTS parameter table setup B700- 8E E9 B7 STX $B7E9 B703- 8E F7 B7 STX $B7F7 B706- A9 01 LDA #$01 B708- 8D F8 B7 STA $B7F8 B70B- 8D EA B7 STA $B7EA B70E- AD E0 B7 LDA $B7E0 B711- 8D E1 B7 STA $B7E1 ; reads from T03,S08, which is not the ; norm, but I already knew this disk ; didn't load a standard DOS just by ; listening to the original disk B714- A9 03 LDA #$03 B716- 8D EC B7 STA $B7EC B719- A9 08 LDA #$08 B71B- 8D ED B7 STA $B7ED B71E- AC E7 B7 LDY $B7E7 B721- 88 DEY B722- 8C F1 B7 STY $B7F1 B725- A9 01 LDA #$01 B727- 8D F4 B7 STA $B7F4 B72A- 8A TXA B72B- 4A LSR B72C- 4A LSR B72D- 4A LSR B72E- 4A LSR B72F- AA TAX B730- A9 00 LDA #$00 B732- 9D F8 04 STA $04F8,X B735- 9D 78 04 STA $0478,X ; hmm B738- 20 00 BB JSR $BB00 B73B- A2 FF LDX #$FF B73D- 9A TXS B73E- 8E EB B7 STX $B7EB ; normal B741- 4C C8 BF JMP $BFC8 B744- 20 89 FE JSR $FE89 ; probably the start of the program B747- 4C 00 88 JMP $8800 The thing that jumps out at me is the JSR at $B738. Even with the unusual start track and sector, I would expect that to call the multi-sector read routine at $B793. Instead, it's calling $BB00, which is generally used as scratch space during RWTS calls. I've seen a lot of disks store one-time nibble checks there, since it's "free" space during early boot (until the first disk read through the RWTS). *BB00L ; save zero page BB00- A2 00 LDX #$00 BB02- B5 00 LDA $00,X BB04- 9D 00 85 STA $8500,X BB07- CA DEX BB08- D0 F8 BNE $BB02 ; seek (instead of read) BB0A- A9 00 LDA #$00 BB0C- 8D F4 B7 STA $B7F4 ; call RWTS (but since it's only ; seeking, it won't overwrite this ; space) BB0F- A9 B7 LDA #$B7 BB11- A0 E8 LDY #$E8 BB13- 20 00 BD JSR $BD00 ; don't know what this does yet... BB16- 20 43 BB JSR $BB43 ; ...but we're already restoring the ; zero page we saved earlier, so I'm ; guessing whatever is at $BB43 is the ; "meat" of the copy protection BB19- 08 PHP BB1A- A2 00 LDX #$00 BB1C- BD 00 85 LDA $8500,X BB1F- 95 00 STA $00,X BB21- CA DEX BB22- D0 F8 BNE $BB1C BB24- 28 PLP ; if $BB43 failed, branch BB25- B0 0A BCS $BB31 ; pretend like this never happened BB27- A6 2B LDX $2B BB29- A9 01 LDA #$01 BB2B- 8D F4 B7 STA $B7F4 ; and jump to the real routine BB2E- 4C 93 B7 JMP $B793 ; The Badlands (a.k.a. the point of no ; return) -- clears the screen and ; reboots BB31- A5 2B LDA $2B BB33- 4A LSR BB34- 4A LSR BB35- 4A LSR BB36- 4A LSR BB37- 09 C0 ORA #$C0 BB39- A8 TAY BB3A- 88 DEY BB3B- 98 TYA BB3C- 48 PHA BB3D- A9 FF LDA #$FF BB3F- 48 PHA BB40- 4C 58 FC JMP $FC58 That routine at $BB31 certainly matches the behavior I saw on my non-working copy. I'm telling you, disks simply do not reboot unless someone tells them to. Unintentional failures just hang or crash. BB43- A9 0A LDA #$0A BB45- 85 50 STA $50 ; initialize disk motor ; (highly suspicious) BB47- A6 2B LDX $2B BB49- BD 89 C0 LDA $C089,X BB4C- BD 8E C0 LDA $C08E,X ; set some counters BB4F- A9 C1 LDA #$C1 BB51- 85 48 STA $48 BB53- A9 BB LDA #$BB BB55- 85 49 STA $49 BB57- A9 80 LDA #$80 BB59- 85 51 STA $51 BB5B- C6 51 DEC $51 ; if zero page $51 counts down to 0, ; give up BB5D- F0 5C BEQ $BBBB ; position the drive head to where the ; nibble check needs it BB5F- 20 44 B9 JSR $B944 ; if that failed, give up BB62- B0 57 BCS $BBBB ; Search for a specific sequence of ; nibbles in the "dead zone" between ; the address field and data field. ; This area is normally not important, ; so COPYA didn't copy it precisely ; because normal disks don't care. ; (Actually, it's even more evil than ; that, because the original disk is ; written with timing bits in specific ; non-standard places between the ; nibbles in the dead zone. This code ; not only requires the right nibbles ; in the right order, it reads them ; just slightly slower than normal. So ; the timing bits need to be in the ; right places too, or else this code ; will read the wrong nibble values ; while it's out of sync. This will ; trip up even the best bit copiers. ; And you can forget about making a ; disk image for emulators -- those ; don't store timing bits at all.) BB64- A5 2D LDA $2D BB66- C9 0F CMP #$0F BB68- D0 F1 BNE $BB5B BB6A- A0 00 LDY #$00 BB6C- BD 8C C0 LDA $C08C,X BB6F- 10 FB BPL $BB6C BB71- 88 DEY ; give up BB72- F0 47 BEQ $BBBB BB74- C9 D5 CMP #$D5 BB76- D0 F4 BNE $BB6C BB78- A0 00 LDY #$00 BB7A- BD 8C C0 LDA $C08C,X BB7D- 10 FB BPL $BB7A BB7F- 88 DEY ; give up BB80- F0 39 BEQ $BBBB BB82- C9 E7 CMP #$E7 BB84- D0 F4 BNE $BB7A BB86- BD 8C C0 LDA $C08C,X BB89- 10 FB BPL $BB86 BB8B- C9 E7 CMP #$E7 ; give up BB8D- D0 2C BNE $BBBB BB8F- BD 8C C0 LDA $C08C,X BB92- 10 FB BPL $BB8F BB94- C9 E7 CMP #$E7 ; give up BB96- D0 23 BNE $BBBB ; kill some time to get out of sync ; with the "proper" start of nibbles) BB98- BD 8D C0 LDA $C08D,X BB9B- A0 10 LDY #$10 BB9D- 24 06 BIT $06 ; 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) BB9F- BD 8C C0 LDA $C08C,X BBA2- 10 FB BPL $BB9F BBA4- 88 DEY ; give up BBA5- F0 14 BEQ $BBBB BBA7- C9 EE CMP #$EE BBA9- D0 F4 BNE $BB9F ; check for nibble sequence stored ; in reverse order at $BBC1 BBAB- A0 07 LDY #$07 BBAD- BD 8C C0 LDA $C08C,X BBB0- 10 FB BPL $BBAD BBB2- D1 48 CMP ($48),Y ; give up BBB4- D0 05 BNE $BBBB BBB6- 88 DEY BBB7- 10 F4 BPL $BBAD ; if we made it this far, the nibble ; check passed BBB9- 18 CLC BBBA- 60 RTS BBBB- C6 50 DEC $50 BBBD- D0 98 BNE $BB57 BBBF- 38 SEC BBC0- 60 RTS BBC1- FC EE EE FC E7 EE FC E7 In the end, the subroutine at $BB43 does one of two things: clears the carry on success, or sets it on exit. (This is also the convention that the DOS 3.3 RWTS uses, but never mind that.) If the carry is clear, the caller continues to $B793 as intended. If the carry is set, boom. There are a few ways I could go here. 1. Patch the actual nibble check subroutine ($BB43) so it always clears the carry on exit (i.e. change the instruction at $BBBF from SEC to CLC). Good: one-byte patch. Bad: still does unnecessary disk access, looking for nibbles that I don't care about and that aren't there anyway. 2. Patch the boot1 code (at $B738) so it calls the success path directly ($BB27 instead of $BB00). Good: one- byte patch, skips unnecessary disk access. Bad: unnecessary JSR. 3. Patch the boot1 code (at $B738) to bypass $BB00 altogether (i.e. calling $B793 instead of $BB00). Good: skips unnecessary disk access, fastest path to boot. Bad: two-byte patch instead of one. It's possible I'm over-thinking this. T00,S01,$38 change "00 BB" to "93 B7" (That was option #3, by the way.) Quod erat liberandum. --------------------------------------- A 4am crack No. 110 ------------------EOF------------------