💾 Archived View for mirrors.apple2.org.za › active › 4am › images › games › strategy › Destroyer%20(… captured on 2023-07-10 at 19:33:59.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

---------------Destroyer---------------
A 4am crack                  2015-08-25
---------------------------------------

Name: Destroyer
Genre: strategy
Year: 1986
Authors:
  Michael Kosaka (design and graphics)
  Chuck Sommerville (programming)
Publisher: Epyx
Media: single-sided 5.25-inch floppy
OS: custom with DOS 3.3 bootloader
Previous cracks:
  The Scanner / Coast to Coast
  The Blade

                   ~

               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 reboots
  endlessly

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,S00 -> looks like a DOS 3.3 boot0

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

Next steps:

  1. capture RWTS with AUTOTRACE
  2. convert disk to standard format
     with Advanced Demuffin
  3. find nibble check and bypass it

                   ~

               Chapter 1
In Which We Attempt To Use The Original
    Disk As A Weapon Against Itself


[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
/!\ BOOT0 JUMPS TO $BB00
CAPTURING BOOT1
...reboots slot 6...
...reboots slot 5..
SAVING BOOT1
SAVING RWTS
/!\ NIBBLE CHECK AT $BB00

Hey, do you think maybe there's a
nibble check at $BB00? I think there
might be a nibble check at $BB00. I
should probably take a look at $BB00.

Later.

]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

                 --^--

[S6,D1=demuffin'd copy]

]PR#6
...reboots endlessly...

Let's go find that nibble check. I'm
gonna take a wild guess and say we
should start by looking at $BB00.

                   ~

               Chapter 2
     In Which We Are Not Surprised


My AUTOTRACE script helpfully scanned
the page at $BB00 for the hex sequence
"BD 89 C0" ("LDA $C089,X"), which is a
common way to turn on the drive motor
manually. Technically there are other
ways to do that, and depending on when
the code is called, it might not even
be necessary (since the drive motor is
already on). But it's common enough
that so I automated scanning for it.
And behold!

(On a normal DOS 3.3 disk, there isn't
any code at $BB00. That page is used
for scratch space during every sector
read, so it's overwritten very early in
the boot process. So any code there is
already suspicious, but code that
fiddles with the drive at a low level
is doubly suspicious.)

]PR#5
...
]BLOAD BOOT1,A$2600
]CALL -151





; clear a few bytes of zero page
BB00-   A9 00       LDA   #$00
BB02-   A2 F0       LDX   #$F0
BB04-   9A          TXS
BB05-   95 00       STA   $00,X
BB07-   E8          INX
BB08-   D0 FB       BNE   $BB05
BB0A-   A9 0A       LDA   #$0A
BB0C-   85 FC       STA   $FC

; turn on drive motor manually (aha!)
BB0E-   A6 2B       LDX   $2B
BB10-   BD 89 C0    LDA   $C089,X
BB13-   BD 8E C0    LDA   $C08E,X

; initialize Death Counter
BB16-   A9 80       LDA   #$80
BB18-   85 FD       STA   $FD
BB1A-   C6 FD       DEC   $FD

; if Death Counter hits 0, jump to
; The Badlands (the point of no return)
BB1C-   F0 6E       BEQ   $BB8C

; this subroutine gets the next address
; field (like $B944 under DOS 3.3)
BB1E-   20 96 BB    JSR   $BB96

; if that failed for some reason, jump
; to The Badlands
BB21-   B0 69       BCS   $BB8C

; that subroutine parsed the address
; field and put the sector number in
; zp$F9
BB23-   A5 F9       LDA   $F9

; is it the sector we wanted?
BB25-   C9 05       CMP   #$05

; nope, loop and try again
BB27-   D0 F1       BNE   $BB1A

; look for a $D5 nibble
BB29-   A0 00       LDY   #$00
BB2B-   BD 8C C0    LDA   $C08C,X
BB2E-   10 FB       BPL   $BB2B
BB30-   88          DEY

; if we don't find one in time, jump to
; The Badlands
BB31-   F0 59       BEQ   $BB8C
BB33-   C9 D5       CMP   #$D5
BB35-   D0 F4       BNE   $BB2B

; look for an $E7 nibble
BB37-   A0 00       LDY   #$00
BB39-   BD 8C C0    LDA   $C08C,X
BB3C-   10 FB       BPL   $BB39
BB3E-   88          DEY

; if we don't find one in time, jump to
; The Badlands
BB3F-   F0 4B       BEQ   $BB8C
BB41-   C9 E7       CMP   #$E7
BB43-   D0 F4       BNE   $BB39

; find 2 more $E7 nibbles
BB45-   BD 8C C0    LDA   $C08C,X
BB48-   10 FB       BPL   $BB45
BB4A-   C9 E7       CMP   #$E7

; unexpected nibble? The Badlands!
BB4C-   D0 3E       BNE   $BB8C
BB4E-   BD 8C C0    LDA   $C08C,X
BB51-   10 FB       BPL   $BB4E
BB53-   C9 E7       CMP   #$E7

; unexpected nibble? The Badlands!
BB55-   D0 35       BNE   $BB8C

; kill some time to get out of sync
; with the "proper" start of nibbles
; (see below)
BB57-   BD 8D C0    LDA   $C08D,X
BB5A-   A0 10       LDY   #$10
BB5C-   24 80       BIT   $80

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 We Are Encrypted


; 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)
BB5E-   BD 8C C0    LDA   $C08C,X
BB61-   10 FB       BPL   $BB5E
BB63-   88          DEY
BB64-   F0 26       BEQ   $BB8C
BB66-   C9 EE       CMP   #$EE
BB68-   D0 F4       BNE   $BB5E
BB6A-   EA          NOP
BB6B-   EA          NOP

; now take the next (desynced) nibbles
; and store them in zp$F0..$F7
BB6C-   A0 07       LDY   #$07
BB6E-   BD 8C C0    LDA   $C08C,X
BB71-   10 FB       BPL   $BB6E
BB73-   99 F0 00    STA   $00F0,Y
BB76-   EA          NOP
BB77-   88          DEY
BB78-   10 F4       BPL   $BB6E
BB7A-   A0 00       LDY   #$00

; take a desynchronized nibble (stored
; earlier in zero page)
BB7C-   A5 F4       LDA   $F4

; use it as a decryption key for the
; next stage of the bootloader
BB7E-   59 00 B7    EOR   $B700,Y
BB81-   99 00 B7    STA   $B700,Y
BB84-   88          DEY
BB85-   D0 F5       BNE   $BB7C

; continue to the (now decrypted) boot1
BB87-   A6 2B       LDX   $2B
BB89-   4C 00 B7    JMP   $B700

; The Badlands -- decrement the Death
; Counter and eventually give up and
; reboot
BB8C-   C6 FC       DEC   $FC
BB8E-   D0 86       BNE   $BB16
BB90-   EE F4 03    INC   $03F4
BB93-   6C FC FF    JMP   ($FFFC)

Well, this is inconvenient. But not
insurmountable. I can interrupt the
boot after the nibble check has
decrypted the rest of the bootloader.

Next steps, revised:

  1. Capture decrypted boot1 sector
  2. Write decrypted sector to disk
  3. Patch boot0 to jump directly to
     (now decrypted) boot1, bypassing
     nibble check altogether

                   ~

               Chapter 4
       In Which We Are Finished




; set up callback #1 after boot0
; (before it calls the nibble check)
96F8-   A9 4C       LDA   #$4C
96FA-   8D 4A 08    STA   $084A
96FD-   A9 0A       LDA   #$0A
96FF-   8D 4B 08    STA   $084B
9702-   A9 97       LDA   #$97
9704-   8D 4C 08    STA   $084C

; start the boot
9707-   4C 01 08    JMP   $0801

; callback #1 is here --
; set up callback #2 after the nibble
; check has decrypted the page at $B700
970A-   A9 17       LDA   #$17
970C-   8D 8A BB    STA   $BB8A
970F-   A9 97       LDA   #$97
9711-   8D 8B BB    STA   $BB8B

; continue the boot
9714-   4C 00 BB    JMP   $BB00

; callback #2 is here --
; copy the decrypted page to lower
; memory so it survives a reboot
9717-   A0 00       LDY   #$00
9719-   B9 00 B7    LDA   $B700,Y
971C-   99 00 27    STA   $2700,Y
971F-   C8          INY
9720-   D0 F7       BNE   $9719

; turn off slot 6 drive motor
9722-   AD E8 C0    LDA   $C0E8

; reboot to my work disk
9725-   4C 00 C5    JMP   $C500



[S6,D1=original disk]


...reboots slot 6...
...reboots slot 5...

]BSAVE B700 DECRYPTED,A$2700,L$100
]CALL -151

0300-   A9 03       LDA   #$03
0302-   A0 10       LDY   #$10
0304-   4C D9 03    JMP   $03D9



0310- 01 60 01 00 00 01 FB F7
         ^^^^^    ^^^^^
         S6,D1   T00,S01

0318- 00 27 00 00 02 00 FE 60
      ^^^^^       ^^
     address    write



[S6,D1=demuffin'd copy]


...write write write...

Turning to my trusty Disk Fixer sector
editor, I can now patch boot0 to skip
the routine at $BB00 altogether. It
served two purposes -- detecting an
original disk, and decrypting the next
stage of the boot -- and I now require
neither of these services.

T00,S00,$4A change "4C00BB" to "6CFD08"

]PR#6
...works...

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 422
------------------EOF------------------