💾 Archived View for mirrors.apple2.org.za › active › 4am › images › games › action › Sneakers%20(4am… captured on 2023-07-10 at 19:19:10.
⬅️ 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------------------