💾 Archived View for mirrors.apple2.org.za › active › 4am › images › games › action › Sneakers%20(4am… captured on 2024-07-09 at 00:14:37.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

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

----------------Sneakers---------------
A 4am crack                  2014-02-27
---------------------------------------

Booting from the original disk displays
an animated alien while loading, which
is the cutest damn thing I have ever
seen. It's likely that you've never
seen it, since every cracked version of
Sneakers in the 1980s was a file-based
crack that eliminated the boot sequence
altogether. From the first moment I saw
it, I knew that the goal of this crack
would be to preserve or recreate the
original boot animation as closely as
possible.

Boot tracing is a lot easier when you
have two disk controller cards, or
something like the CFFA 3000 card that
lets you set up virtual floppy
drives. That way, I can keep a "work
disk" in slot 5 and the original
Sneakers disk in slot 6, and boot each
as needed without swapping
floppies. (Seriously, it's the 21st
century. It's fun to have a hobby, but
I draw the line at swapping floppies.)

For the work disk, I used to use a
blank ProntoDOS disk with the
large-files patch (POKE 43364,255), but
I've since discovered the wonderful
DiversiDOS with the 64K DOS patch. It
has the large-files patch built in, and
it loads DOS into the language card on
boot so you can load data far above the
normal $9600 limit. And it has so many
other useful features and shortcuts:
"C" to display a disk catalog, BSAVE
that remembers the address and length
from the last BLOAD, and a TLIST
command for viewing text files.

OK, so let's get started.

]PR#5 (boots my work disk)
*8600<C600.C6FFM
*86F8:AD E8 C0 4C 59 FF
*BSAVE TRACE0,A$8600,L$100
*8600G (loads Sneakers T0S0 and halts)
*2000<0800.08FFM
]PR#5 (boots my work disk again)
]BSAVE SNEAKERS T0S0,A$2000,L$100

So what do we have? Lots of interesting
stuff, none of it the slightest bit
standard.

$0801..$080C: display hi-res page 1
(uninitialized)

$080D..$0851: load RWTS into
$0400..$07FF using a non-standard
prologue and a 4-4 nibble encoding
scheme

$0852..$0866: zero out all of main
memory from $0900 to $BFFF

$0867..$0870: a loop that uses
progressive EORs to calculate a
checksum of the T0S0 code
($0800..$08FF), then transfers the
checksum value to the stack

0867-   8A          TXA
0868-   E8          INX
0869-   F0 06       BEQ   $0871
086B-   5D 00 08    EOR   $0800,X
086E-   4C 68 08    JMP   $0868
0871-   AA          TAX
0872-   9A          TXS

$0873..$087A: another loop that uses
progressive EORs to calculate a
checksum of zero page ($00..$FF). The
checksum value is held in the
A-register, but to what end? The result
is unpredictable because most of zero
page is never initialized. Weird.

0873-   A2 00       LDX   #$00
0875-   8A          TXA
0876-   55 00       EOR   $00,X
0878-   E8          INX
0879-   D0 FB       BNE   $0876

$087B..$087F: restore the X-register to
slot number x 16, then jump to the next
phase of the boot at $0400.

As we'll see later, I need that first
checksum value that was transferred to
the stack at $0871; I can't fake it or
skip over it. But I can't modify T0S0
on disk, because the checksum value
depends on all the bytes in that
sector. However, I can create a boot
tracer that captures the checksum
without modifying the boot sector on
disk *or* in memory.

1. Load T0S0
2. Copy $0800..$08FF to $0900..$09FF
3. Neutralize the loop at $0952 that
   zeroes out of all main memory
4. Fix any absolute JMP addresses from
   $08XX to $09XX (there's just one, at
   $096E)
5. Modify the final JMP at $097D to
   call back to my own routine so I can
   transfer the checksum value back
   from the stack, store it somewhere,
   and exit to the monitor
6. JMP to $0901 (not $0801)

The first checksum value is $20.

Now I need to let the boot progress a
little further so that T0S0 actually
loads the RWTS, but interrupt the boot
before it jumps to the RWTS. This is
TRACE1 on my work disk.

; neutralize memory zapping loop
86F8-   A9 01       LDA   #$01
86FA-   8D 5C 08    STA   $085C
; jump to my callback instead of $0400
86FD-   A9 0A       LDA   #$0A
86FF-   8D 7E 08    STA   $087E
8702-   A9 87       LDA   #$87
8704-   8D 7F 08    STA   $087F
; let Sneakers boot
8707-   4C 01 08    JMP   $0801
; callback: save RWTS in safe location
870A-   A2 04       LDX   #$04
870C-   A0 00       LDY   #$00
870E-   B9 00 04    LDA   $0400,Y
8711-   99 00 20    STA   $2000,Y
8714-   C8          INY
8715-   D0 F7       BNE   $870E
8717-   EE 10 87    INC   $8710
871A-   EE 13 87    INC   $8713
871D-   CA          DEX
871E-   D0 EE       BNE   $870E
; turn off disk motor and exit
8720-   AD E8 C0    LDA   $C0E8
8723-   4C 59 FF    JMP   $FF59

This allows me to soft-reboot to my
work disk and save the RWTS, which is
now duplicated at $2000..$23FF.

Now let's examine the next phase of the
boot, starting at $0400.

$0400..$040D: a third loop using
progressive EORs to calculate a
checksum of zero page ($00..$FF),
starting with the ending value (in the
A-register) of the second checksum
loop.

0400-   A0 00       LDY   #$00
0402-   59 00 00    EOR   $0000,Y
0405-   C8          INY
0406-   D0 FA       BNE   $0402
0408-   A8          TAY
0409-   F0 03       BEQ   $040E
040B-   4C A2 05    JMP   $05A2

Oh, I see. This determines whether the
zero page has been modified between the
end of T0S0 and the beginning of this
phase. It appears I can just skip over
this check without any ill effects.

$05A2 is The Badlands. You never want
to end up in The Badlands. Every
copy-protection scheme has a routine
where the developers have determined
that Something Is Amiss and therefore
Bad Things Must Happen. This routine
generally displays a cryptic error
message, wipes all of main memory, and
reboots. Sneakers plays a cute sound
first. Apparently, even in The
Badlands, cuteness is paramount.

On the bright side, once I find the
entry point to The Badlands, searching
for other references to that address
may uncover further copy protections
down the road.

$040E..$0451: various unfriendly
initialization routines. Sets the reset
vector at $03F2 to point to a routine
that wipes all of main memory and
reboots. Then wipes out the auxiliary
memory in the language card to
neutralize any memory-resident boot
tracers. Then sets the low level reset
vector at $FFFC. Then, just for good
measure, sets the input and output
vectors at $36 and $38.

$0452..$0471: a fourth(!) loop using
progressive EORs to calculate a
checksum of the entire RWTS
($0400..$07FF), starting with the
ending value of the first checksum loop
which has now made its way into zero
page $0B.

Having saved the RWTS to my work disk,
I'll BLOAD it at both $2000 and $2400,
then make the following changes to
manually calculate the RWTS checksum:

*2451:A2 20 A9 00
*2472:60
*400<2000.23FFM N 2451G

The checksum (stored in zero page $0B)
is $16.

$0472..$048E: loads the game from disk,
then jumps to $0500 for some final
nibble craziness. If I interrupt the
boot, manually set zero page $0B to
$16, patch the jump at $047E to exit to
the monitor instead of jumping to
$0500, and skip over all the unfriendly
initialization, I can let Sneakers load
itself in $0800..$97FF and save it to
my work disk.

Here's the boot tracer code (TRACE4 on
my work disk):

[$8600..$86F7 copied from $C600..$C6F7]
86F8-   A2 00       LDX   #$00
86FA-   BD 00 08    LDA   $0800,X
86FD-   9D 00 09    STA   $0900,X
8700-   E8          INX
8701-   D0 F7       BNE   $86FA
; don't wipe main memory
8703-   A9 EA       LDA   #$EA
8705-   8D 5D 09    STA   $095D
8708-   8D 5E 09    STA   $095E
870B-   A9 09       LDA   #$09
870D-   8D 70 09    STA   $0970
; jump back here to set up next phase
8710-   A9 1D       LDA   #$1D
8712-   8D 7E 09    STA   $097E
8715-   A9 87       LDA   #$87
8717-   8D 7F 09    STA   $097F
871A-   4C 01 09    JMP   $0901
; manually set initial checksum
871D-   A9 A9       LDA   #$A9
871F-   8D 6E 04    STA   $046E
8722-   A9 16       LDA   #$16
8724-   8D 6F 04    STA   $046F
; exit to monitor instead of JMP $0500
8727-   A9 59       LDA   #$59
8729-   8D 7F 04    STA   $047F
872C-   A9 FF       LDA   #$FF
872E-   8D 80 04    STA   $0480
; load the game
8731-   4C 52 04    JMP   $0452

The final boot phase is at $0500, and
it's a doozy. I can't just skip it;
it's reading real code and data into
low memory that the game actually uses
later.

$0500..$0534 looks for a non-standard
prologue (D5 AA B5), reads a page of
data (using a 4-4 nibble encoding
scheme), EORs it with the magical
checksum value in zero page $0B, and
stores it in $0200..$02FF.

$0535..$055F reads some amount of data
(not sure how much, but it's
zero-terminated), EORs it with the
magical checksum value at zero page
$0B, and stores it in zero page.

$0560..$0588 reads two bytes from the
disk, EORs it with -- you guessed it--
the magical checksum value at zero page
$0B, and pushes it to the stack. Then
turns off the disk motor. Whew, we're
finally done reading stuff from disk.

$0589..$05A0 loops through every single
byte that was just read from disk
($0800..$97FF) with a progressive
EOR. If the final checksum is
unexpected (if even a single byte of
the game has been modified), it veers
off into The Badlands at $05A2.

And then... that's it. No JMP, just an
RTS at $05A1. Remember those bytes that
were read from disk and pushed to the
stack in $0560..$0588? That's the
starting address of the game. What is
it? I have no idea. Let's find out.

Building on TRACE4, I need one more
callback letting (some of) the routine
at $0500 run its course. To do this, I
need to move my boot tracer out of the
way because it's going to get
overwritten. This is TRACE5 on my work
disk.

[$8600..$8726 copied from TRACE4]
; set up callback after boot is over
8727-   A9 4C       LDA   #$4C
8729-   8D 89 05    STA   $0589
872C-   A9 44       LDA   #$44
872E-   8D 8A 05    STA   $058A
8731-   A9 BF       LDA   #$BF
8733-   8D 8B 05    STA   $058B
; move boot tracer to $BF00
8736-   A2 00       LDX   #$00
8738-   BD 00 87    LDA   $8700,X
873B-   9D 00 BF    STA   $BF00,X
873E-   E8          INX
873F-   D0 F7       BNE   $8738
; let Sneakers load itself
8741-   4C 52 04    JMP   $0452
; grab address from stack
8744-   68          PLA
8745-   8D FE BF    STA   $BFFE
8748-   68          PLA
8749-   8D FF BF    STA   $BFFF
; grab zero page
874C-   A2 00       LDX   #$00
874E-   BD 00 00    LDA   $0000,X
8751-   9D 00 20    STA   $2000,X
; grab $0200..$02FF
8754-   BD 00 02    LDA   $0200,X
8757-   9D 00 22    STA   $2200,X
875A-   E8          INX
875B-   D0 F1       BNE   $874E
; exit to monitor
875D-   4C 59 FF    JMP   $FF59

This allows me to reboot to my work
disk and save the zero page and
$0200..$02FF data that Sneakers
expects.

The game starts at $4000.

Now it's time to put all the pieces
together. Here's my proposed memory
map:

$0800..$97FF: original Sneakers code
$9800..$9BFF: original RWTS
$9C00..$9CFF: original zero page
$9D00..$9DFF: original page 2
$9E00..$9FFF: my initialization routine

$9E00 will just move a bunch of stuff
into the places that Sneakers expects.

$9800..$9BFF to $0400..$07FF
$9C00..$9CFF to $00..$FF
$9D00..$9DFF to $0200..$02FF

and JMP $4000.

Success! The game runs! And... failure.
The disk motor turns on when you lose a
life during the game. Then the game
animates the ship coming down (but
doesn't appear to be reading from the
disk, WTF?), then the game hangs when
the ship makes it to the bottom of the
screen.

OK, so there's one more check
somewhere. Searching the code for
instances of "BD 89 C0" (LDA $C089,X)
-- commonly used to turn on the disk
motor -- brings up a routine at $4FE1
that turns it on and a sister routine
at $4FE7 that turns it back off. But
there are no JSRs or JMPs to either of
these routines... rats.

Ah, but there *is* a JSR $4FDE at
$6EFD. From looking at the surrounding
code, it appears that this disk check
was hacked in at the last minute. The
first instruction at $4FDE is
STX $85D8, which makes no sense and has
nothing to do with disk activity. But
it *does* make sense in the context of
the surrounding code near $6EFD, which
just loaded the X-register with a
value.

It's as if somebody said at the last
minute, "Hey, all that nasty stuff we
did with the RWTS, the custom disk
encoding, the nibble checks, the
self-modifying code, and the
progressive integrity checking JUST
ISN'T ENOUGH so could you please add a
disk check too?" And the weary
programmer rolled their eyes and said,
"Sure boss, I can monkey patch it."
Then they moved 3 bytes of real code to
a subroutine labeled
CAN_YOU_BELIEVE_THIS_SHIT_RIGHT_HERE
and went home early to get drunk in the
shower.

This still doesn't solve the hanging
problem, though. My guess is that it's
doing a nibble check of some sort at
the point where the disk read stopped
after the boot sequence. Searching the
code for instances of "BD 8C C0" (LDA
$C08C,X) brings up a routine that
appears to start at $94D3 that, lo and
behold, does an extremely basic check
for the existence of some non-standard
address prologue bytes. Since those no
longer exist, the code just hangs.

Once again, there are no references to
JSR $94D3 or JMP $94D3 or anything,
anywhere in the code. But look... at
$94D0, there is an odd instruction, STA
$1A28, which has nothing to do with
nibble checking. And sure enough, there
*is* a JSR $94D0, at $6FC9, and the
surrounding code before the JSR is
storing other values in $1A27 and
$1A29. This reinforces my theory that
this entire disk check was hacked in at
the last minute. Simply patching the
caller to do the STA instead of the JSR
is enough to get the code to stop
hanging. The nibble check subroutine
does not set any flags or modify any
memory to indicate success, so
bypassing it altogether is the simplest
solution.

Thus, the Smallest Patch That Could
Possibly Work:

*6FC9:8D 28 1A    ; originally 20 D0 94
*6EFD:8E D8 85    ; originally 20 DE 4F

Here's my proposed disk layout:

Track 0, sectors 0 and 3-B: a modified
ProntoDOS RWTS that loads the rest of
the game into memory and jumps to
$9E00.

Track 0, sectors 1-2: the original boot
animation code. This was stored in
$0600..$07FF in the original RWTS.

$0800 (T0S0) shows hi-res page 1, loads
ProntoDOS RWTS into $B700..$BFFF, and
moves the original animation code into
$0600..$07FF, then jumps to $B700.

$B700 (T0S3) loads everything else into
$0800..$9FFF, calling the original
animation code on each track change.

ProntoDOS works fastest when it is
reading tracks "backwards," i.e. read
logical sector 0F, then logical sector
0E, then logical sector 0D, and so on
down to logical sector 00. So, I've
stored the game data "backwards" on
each track. For example, track 1
contains the data from $0800..$17FF.
Track 1, sector 0F stores the data for
$0800..$08FF, sector 0E stores
$0900..$09FF, and so on until sector 00
which stores $1700..$17FF.

Track  Address
-------------------
01     $0800..$17FF
02     $1800..$27FF
03     $2800..$37FF
04     $3800..$47FF
05     $4800..$57FF
06     $5800..$67FF
07     $6800..$77FF
08     $7800..$87FF
09     $8800..$97FF
0A     $9800..$9FFF (partial track)

The final disk preserves the "look and
feel" of the original, from the display
of the uninitialized hi-res page at the
earliest possible moment, to the boot
animation synchronized with the disk
motor, to the unaltered game with
copyright messages intact. I have added
a standard DOS 3.3 VTOC on track $11
and stored these softdocs as a text
file, but the game itself never looks
at this catalog nor reads these docs.

---------------------------------------
A 4am crack                  2014-02-27
------------------EOF------------------