💾 Archived View for mirrors.apple2.org.za › active › 4am › images › games › action › G.I.%20Joe%20(4… captured on 2024-08-18 at 17:34:41.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

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

----------------G.I. Joe---------------
A 4am crack                  2015-08-24
---------------------------------------

Name: G.I. Joe
Genre: arcade
Year: 1985
Authors: Jeff Johannigman, Ray
  Carpenter, Michael Kosaka (graphics),
  K-Byte (Apple version)
Publisher: Epyx
Media: double-sided 5.25-inch floppy
OS: custom with DOS 3.3 bootloader
Previous cracks:
  Gadget Master / Five Star
  The Blade

Side B just says "PLEASE BOOT OTHER
SIDE" and hangs, so I should probably
start with side A.

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate read error, but it gets a
  participation medal for showing up

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  no read errors, but 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 Are Surprised


[S6,D1=original disk]
[S5,D1=my work disk]

]PR#5
CAPTURING BOOT0
...reboots slot 6...
...reboots slot 5...
SAVING BOOT0

]BLOAD BOOT0,A$800
]CALL -151



0801-   A5 27       LDA   $27
0803-   C9 09       CMP   #$09
0805-   D0 18       BNE   $081F
0807-   20 B3 08    JSR   $08B3   <-- !
080A-   4A          LSR
080B-   4A          LSR
080C-   4A          LSR
080D-   09 C0       ORA   #$C0
080F-   85 3F       STA   $3F
0811-   A9 5C       LDA   #$5C
0813-   85 3E       STA   $3E

This looks like a normal DOS 3.3 boot0,
except for that suspicious call to
$08B3 in the first-run part of the loop
(at $0807).



; read/write RAM bank 1
08B3-   AD 8B C0    LDA   $C08B
08B6-   AD 8B C0    LDA   $C08B

; and do the thing I was expecting to
; see at $807 (before we called this
; subroutine)
08B9-   A5 2B       LDA   $2B
08BB-   4A          LSR
08BC-   60          RTS

Poking around a bit further, the light
dawns:



08FE- D3 0D

This disk is loading boot1 straight
into the language card, starting at
$D300. That's why it had to set the RAM
bank for read/write access -- because
it's going to read and write to it.
(Such things are obvious in hindsight.)

The rest of boot0 is unsurprising. It
ends up here:

083F-   4C BD 08    JMP   $08BD



; dunno
08BD-   A9 FF       LDA   #$FF
08BF-   8D F8 1F    STA   $1FF8

; unfriendly reset vector
08C2-   A9 00       LDA   #$00
08C4-   8D F2 03    STA   $03F2
08C7-   8D FC FF    STA   $FFFC
08CA-   A9 C6       LDA   #$C6
08CC-   8D F3 03    STA   $03F3
08CF-   8D FD FF    STA   $FFFD
08D2-   49 A5       EOR   #$A5
08D4-   8D F4 03    STA   $03F4

; read/write on the RAM bank again
08D7-   AD 8B C0    LDA   $C08B
08DA-   AD 8B C0    LDA   $C08B

; continue to boot1
08DD-   A6 2B       LDX   $2B
08DF-   6C FD 08    JMP   ($08FD)

And that's where I need to interrupt
the boot.



; set up callback just before boot0
; calls boot1
96F8-   A9 4C       LDA   #$4C
96FA-   8D DF 08    STA   $08DF
96FD-   A9 0A       LDA   #$0A
96FF-   8D E0 08    STA   $08E0
9702-   A9 97       LDA   #$97
9704-   8D E1 08    STA   $08E1

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

; callback is here -- move boot1 to
; main memory so it survives a reboot
; (my work disk uses Diversi-DOS 64K
;  which relocates to the RAM bank)
970A-   A2 0D       LDX   #$0D
970C-   A0 00       LDY   #$00
970E-   B9 00 D3    LDA   $D300,Y
9711-   99 00 23    STA   $2300,Y
9714-   C8          INY
9715-   D0 F7       BNE   $970E
9717-   EE 10 97    INC   $9710
971A-   EE 13 97    INC   $9713
971D-   CA          DEX
971E-   D0 EE       BNE   $970E

; switch back to ROM (important! DOS
; will crash without it! I re-learn
; this the hard way every time I trace
; a disk like this!)
9720-   AD 82 C0    LDA   $C082

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

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


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

]BSAVE BOOT1 D300-DFFF,A$2300,L$D00

                   ~

               Chapter 2
       In Which We Are Converted


I'm going to use Super Demuffin here
(instead of my usual go-to conversion
tool, Advanced Demuffin). I do have the
RWTS (in the BOOT1 D300-DFFF file), but
it's in the language card and I don't
feel like writing an IOB module and
fiddling with memory softswitches. The
RWTS modifications are minor (custom
epilogue bytes, same on every track),
so Super Demuffin will work just fine.

When you first run Super Demuffin, it
asks for the parameters of the original
disk. In this case, the prologue bytes
are the same, but the epilogues are "FF
FF EB" instead of "DE AA EB".

                 --v--

      SUPER-DEMUFFIN AND FAST COPY
Modified by: The Saltine/Coast to Coast


   Address prologue: D5 AA 96

   Address epilogue: FF FF EB    DISK
                     ^^^^^     ORIGINAL
change from DE AA ---+++++

      Data prologue: D5 AA AD

      Data epilogue: FF FF EB
                     ^^^^^
change from DE AA ---+++++


 Ignore write errors while demuffining!


  D - Edit parameters
      <SPACE> - Advance to next parm
      <RETURN> - Exit edit mode
  R - Restore DOS 3.3 parameters
  O - Edit Original disk's parameters
  C - Edit Copy disk's parameters
  G - Begin demuffin process

                 --^--

Pressing "G" switches to the Locksmith
Fast Disk Copy UI. It assumes that both
disks are in slot 6, and that drive 1
is the original and drive 2 is the
copy.

[S6,D1=original disk]
[S6,D2=blank disk]

                 --v--

     LOCKSMITH 7.0  FAST DISK BACKUP


   R...................................
   W***********************************
HEX 00000000000000001111111111111111222
TRK 0123456789ABCDEF0123456789ABCDEF012
   0...................................
   1...................................
   2...................................
   3...................................
   4...................................
   5...................................
   6...................................
   7...................................
   8...................................
   9...................................
   A...................................
   B...................................
   C...................................
   D...................................
12 E...................................
   F...................................
[               ] PRESS [RESET] TO EXIT

                 --^--

Side B converts the same way.

[S6,D1=demuffin'd copy (side A)]

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

Let's go find that nibble check.

                   ~

               Chapter 3
    In Which We Are Desynchronized


]PR#5
...
]BLOAD BOOT1 D300-DFFF,A$2300
]CALL -151

; copy ROM to RAM bank 1


C08D- B0
C08D- B0

; disconnect DOS (again, just because
; my work disk uses Diversi-DOS 64K so
; it conflicts with what I'm about to
; do)


; switch to RAM bank 1 and copy boot1
; into place


C08B- B0
C08B- B0

[Thanks to @sicklittlesaru for teaching
 me that trick.]



; this is the start of boot1
D400-   A9 0A       LDA   #$0A
D402-   85 F4       STA   $F4

; turn on the drive motor manually
; (never a good sign)
D404-   A6 2B       LDX   $2B
D406-   BD 89 C0    LDA   $C089,X
D409-   BD 8E C0    LDA   $C08E,X

; an address? so ($F6) -> $D49C, which
; would be later in this sector
D40C-   A9 9C       LDA   #$9C
D40E-   85 F6       STA   $F6
D410-   A9 D4       LDA   #$D4
D412-   85 F7       STA   $F7

; probably a Death Counter
D414-   A9 80       LDA   #$80
D416-   85 F5       STA   $F5

; if Death Counter hits 0, jump to
; The Badlands
D418-   C6 F5       DEC   $F5
D41A-   F0 76       BEQ   $D492

; this gets the next available address
; field (like $B944 in DOS 3.3)
D41C-   20 A4 D4    JSR   $D4A4

; if that failed, off to The Badlands
D41F-   B0 71       BCS   $D492

; zp$F1 holds the sector number
D421-   A5 F1       LDA   $F1

; was it the sector we wanted?
D423-   C9 0F       CMP   #$0F

; nope, loop back and try again
D425-   D0 F1       BNE   $D418

; look for $D5 nibble
D427-   A0 00       LDY   #$00
D429-   BD 8C C0    LDA   $C08C,X
D42C-   10 FB       BPL   $D429
D42E-   88          DEY
D42F-   F0 61       BEQ   $D492
D431-   C9 D5       CMP   #$D5
D433-   D0 F4       BNE   $D429

; look for $E7 nibble
D435-   A0 00       LDY   #$00
D437-   BD 8C C0    LDA   $C08C,X
D43A-   10 FB       BPL   $D437
D43C-   88          DEY

; fail if we don't find it in time
D43D-   F0 53       BEQ   $D492
D43F-   C9 E7       CMP   #$E7
D441-   D0 F4       BNE   $D437

; look for two more $E7 nibbles
D443-   BD 8C C0    LDA   $C08C,X
D446-   10 FB       BPL   $D443
D448-   C9 E7       CMP   #$E7
D44A-   D0 46       BNE   $D492  ; fail
D44C-   BD 8C C0    LDA   $C08C,X
D44F-   10 FB       BPL   $D44C
D451-   C9 E7       CMP   #$E7
D453-   D0 3D       BNE   $D492  ; fail

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

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 4
       In Which We Are Finished


; 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)
D45C-   BD 8C C0    LDA   $C08C,X
D45F-   10 FB       BPL   $D45C
D461-   88          DEY
D462-   F0 2E       BEQ   $D492
D464-   C9 EE       CMP   #$EE
D466-   D0 F4       BNE   $D45C

; compare the next 8 nibbles to an
; array stored at ($F6) [= $D49C]
; in reverse order
D468-   A0 07       LDY   #$07
D46A-   BD 8C C0    LDA   $C08C,X
D46D-   10 FB       BPL   $D46A
D46F-   D1 F6       CMP   ($F6),Y

; if any nibble doesn't match, off to
; The Badlands
D471-   D0 1F       BNE   $D492
D473-   88          DEY
D474-   10 F4       BPL   $D46A

; success falls through to here --
; set up zero page to read the sector
; that was supposed to be at $D400 in
; the first place
D476-   A2 60       LDX   #$60
D478-   8E 01 08    STX   $0801

; zp$2B = boot slot (x16)
D47B-   A6 2B       LDX   $2B

; zp$3D = physical sector number
D47D-   A9 02       LDA   #$02
D47F-   85 3D       STA   $3D

; zp$27 = target page
D481-   A9 D4       LDA   #$D4
D483-   85 27       STA   $27

; manually push $D3FF to the stack
D485-   A9 D3       LDA   #$D3
D487-   48          PHA
D488-   A9 FF       LDA   #$FF
D48A-   48          PHA

; manually push $Cx5B to the stack
D48B-   A5 3F       LDA   $3F
D48D-   48          PHA
D48E-   A9 5B       LDA   #$5B
D490-   48          PHA

; now exit via RTS, which will call
; the disk controller ROM routine at
; $Cx5C to read the "real" sector into
; $D400, then jump to $0801, which is
; now an "RTS" (set at $D478), which
; will pop the next address off the
; stack and "return" to $D400, which
; will continue the boot
D491-   60          RTS

; The Badlands -- decrement Death
; Counter and eventually reboot
D492-   C6 F4       DEC   $F4
D494-   D0 03       BNE   $D499
D496-   4C 00 C6    JMP   $C600
D499-   4C 14 D4    JMP   $D414

Finally, the array of desynchronized
nibbles (the E7 bitstream, in reverse
order):

D49C-  [FC EE EE FC E7 EE FC E7]

There are two ways to go here. I could
wipe out this entire sector and replace
it with the code that's supposed to be
loaded at $D400 in the first place. Or,
I could do the Simplest Thing That
Could Possibly Work by patching this
sector to skip over the nibble check
and jump directly to the success path.

I'm going with option #2.

T00,S01,$02 change "85 F4" to "D0 72"

Quod erat liberandum.

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