--------------Galaxy Gates------------- A 4am crack 2018-04-06 -------------------. updated 2018-04-07 |___________________ Name: Galaxy Gates Genre: arcade Year: 1981 Credits: Eric Popejoy Publisher: Magna Soft Platform: Apple ][+ or later Media: 5.25-inch disk Sides: 1 OS: DOS 3.2 ~ Chapter 0 If You Give a Cracker a Disk Passport automatically converted this disk from 13-sector to 16-sector. Here is the transcript of that part: --v-- READING FROM S6,D1 T00,S00 FOUND DOS 3.2 BOOTLOADER USING DISK'S OWN RWTS WRITING TO S5,D2 T00,S02 WRITING BUILT-IN RWTS T00,S01,$38: 20 -> 2C T00,S00 WRITING STANDARD DELIVERY BOOTLOADER CRACK COMPLETE. PRESS ANY KEY --^-- [Narrator] But the crack was not complete. When I boot the 16-sector copy that Passport produces, it gets as far as loading the text title screen, shows a bit more disk activity, then fills the screen with inverse "R" and hangs. The plot thickens. ~ Chapter 1 If You Give a Pirate a Prompt With a well-timed during boot, immediately after seeing the "]" prompt, my non-working copy drops me to a working prompt. It appears that DOS is still in memory. Let's see if I can use that to my advantage. ]CATALOG DISK VOLUME 254 B 061 GALAXY GATES ]BLOAD GALAXY GATES ...fills screen with title title screen but eventually drops me back to the prompt... Ah, interesting. The text title screen is actually part of the file. ]CALL -151 ; last BLOAD address *AA71.AA72 AA72- FD 03 *3FDL 03FD- 4C 00 3A JMP $3A00 *3A00L 3A00- 4C 09 3A JMP $3A09 *3A09L 3A09- A2 00 LDX #$00 3A0B- 8A TXA 3A0C- 9D 00 07 STA $0700,X 3A0F- CA DEX 3A10- D0 FA BNE $3A0C 3A12- A9 01 LDA #$01 3A14- 85 B0 STA $B0 3A16- 4C 00 28 JMP $2800 *2800L ; get address of RWTS parameter table 2800- 20 E3 03 JSR $03E3 ; get last used slot from RWTS table 2803- 84 00 STY $00 2805- 85 01 STA $01 2807- A0 0F LDY #$0F 2809- B1 00 LDA ($00),Y 280B- AA TAX ; turn on drive motor 280C- BD 89 C0 LDA $C089,X Aha! I do believe we have now arrived at The Fun Part. ~ Chapter 2 The Fun Part Turning on the drive motor manually in the middle of a program (and not, say, deep within the RWTS where such a thing is legitimately required) can only mean one thing: shenanigans. *280FL ; wait for drive to spin up 280F- A0 18 LDY #$18 2811- BD 8C C0 LDA $C08C,X 2814- 30 05 BMI $281B 2816- 88 DEY 2817- D0 F8 BNE $2811 ; failure to spin up means something is ; desperately wrong, like a pirate is ; trying to boot trace or something, so ; give up immediately 2819- F0 1A BEQ $2835 ; Death Counters, probably 281B- A9 00 LDA #$00 281D- 8D 65 28 STA $2865 2820- A9 10 LDA #$10 2822- 8D 66 28 STA $2866 2825- A9 04 LDA #$04 2827- 8D 64 28 STA $2864 282A- 78 SEI 282B- CE 65 28 DEC $2865 282E- D0 37 BNE $2867 2830- CE 66 28 DEC $2866 2833- D0 32 BNE $2867 ; failure falls through to here -- ; turn off the drive motor 2835- BD 88 C0 LDA $C088,X ; copy the next chunk of code to lower ; memory ($200 to be precise) 2838- A0 15 LDY #$15 283A- A9 00 LDA #$00 283C- 85 01 STA $01 283E- A9 02 LDA #$02 2840- 85 00 STA $00 2842- B9 4D 28 LDA $284D,Y 2845- 91 00 STA ($00),Y 2847- 88 DEY 2848- 10 F8 BPL $2842 ; and continue from there 284A- 6C 00 00 JMP ($0000) ; (executed at $200) ; wipe all memory 284D- A9 00 LDA #$00 284F- 85 00 STA $00 2851- A8 TAY 2852- A9 04 LDA #$04 2854- 85 01 STA $01 ; inverse "R" character, which is what ; I saw on my non-working copy 2856- A9 12 LDA #$12 2858- 91 00 STA ($00),Y 285A- E6 00 INC $00 285C- D0 FA BNE $2858 285E- E6 01 INC $01 2860- D0 F6 BNE $2858 2862- 58 CLI 2863- 60 RTS The actual protection check continues at $2867, from the branches at $282E and $2833. ; find a $D5 nibble 2867- BD 8C C0 LDA $C08C,X 286A- 10 FB BPL $2867 286C- C9 D5 CMP #$D5 286E- D0 BB BNE $282B ; skip several nibbles 2870- A0 0C LDY #$0C 2872- EA NOP 2873- BD 8C C0 LDA $C08C,X 2876- 10 FB BPL $2873 2878- 88 DEY 2879- D0 F7 BNE $2872 ; find an $AA nibble (if we started at ; the start of an address prologue, ; this nibble will be part of the ; address epilogue) 287B- C9 AA CMP #$AA 287D- D0 AC BNE $282B 287F- F0 00 BEQ $2881 ; fetch from data latch -- note: no BPL ; loop here, we're just getting one ; value from the data latch 2881- BD 8C C0 LDA $C08C,X ; if data latch is >= 8 at this point, ; fail (well, start over and try again) 2884- C9 08 CMP #$08 2886- B0 A3 BCS $282B ; match the rest of the epilogue 2888- BD 8C C0 LDA $C08C,X 288B- 10 FB BPL $2888 288D- C9 EB CMP #$EB 288F- D0 9A BNE $282B 2891- BD 8C C0 LDA $C08C,X 2894- 10 FB BPL $2891 2896- C9 FD CMP #$FD 2898- D0 91 BNE $282B 289A- CE 64 28 DEC $2864 289D- D0 C8 BNE $2867 ; turn off drive motor and continue to ; the actual game code 289F- BD 88 C0 LDA $C088,X 28A2- 58 CLI 28A3- 4C 00 20 JMP $2000 The LDA / CMP at $2881 are the heart of the protection check. To understand why, we get to take a little voyage. ~ Chapter 3 The Really Fun Part Each bit on disk takes 4 CPU cycles to come around as the disk is spinning. The data latch is the softswitch in the Apple II memory ($C0EC for slot 6, but usually written as "$C08C,X" to be slot-independent) that corresponds to the "current" value that's been read from the disk so far. Normally you just poll the data latch until the high bit goes on, at which point you have a full nibble. That's why most RWTS code has an LDA/BPL loop that looks something like this: - BD 8C C0 LDA $C08C,X 10 FB BPL - However, you aren't required to poll the data latch constantly. It acts as a micro-cache, keeping the "current" value as bits go flying by, whether you're polling it or not. For example, the epilogue on this disk is "DE AA EB", with a timing bit after the "AA". So the bitstream looks like this: 110111101010101001110101111111101 |--DE--||--AA--| |--EB--||--FD--| ^ extra timing bit As the disk spins(*), these bits are shifted into the data latch at a rate of 1 bit every 4 CPU cycles. (*) I still maintain that "As The Disk Spins" would make a great name for a retrocomputing-themed soap opera. Let's look at the bits in and around that timing bit, starting with the $AA nibble: Time | <-- as the disk spins | $C0EC -----+-----------------------+--------- -28 | .......10101010011101 | 00000001 -----+--------^--------------+--------- -24 | ......101010100111010 | 00000010 -----+--------^--------------+--------- -20 | .....1010101001110101 | 00000101 -----+--------^--------------+--------- -16 | ....10101010011101011 | 00001010 -----+--------^--------------+--------- -12 | ...101010100111010111 | 00010101 -----+--------^--------------+--------- -8 | ..1010101001110101111 | 00101010 -----+--------^--------------+--------- -4 | .10101010011101011111 | 01010101 -----+--------^--------------+--------- 0 | 101010100111010111111 | 10101010 -----+--------^--------------+--------- +4 | 010101001110101111111 | 10101010 -----+--------^--------------+--------- +8 | .......11101011111111 | 00000001 -----+--------^--------------+--------- +12 | ......111010111111110 | 00000011 -----+--------^--------------+--------- +16 | .....1110101111111101 | 00000111 -----+--------^--------------+--------- +20 | ....1110101111111101. | 00001110 -----+--------^--------------+--------- At T+0, the high bit of the data latch goes to 1 for the first time, so the LDA/BPL loop at $2876 will exit. After that, it's literally a race against time, because the disk keeps spinning (and bits keep coming) independently of the CPU. At T+4, the disk sees the extra 0 bit (timing bit) after the $AA nibble. This does not change the value of the data latch; it "holds" its value until it sees a new 1 bit. If the timing bit had not been present, the data latch would have seen a 1 bit here instead (the high bit of every nibble is always 1), which would have caused the data latch to reset and start accumulating the new value ($01). But because the timing bit is present, that reset gets delayed by 4 CPU cycles. At T+8, the first bit of the $EB nibble shows up. This is a 1, so now the data latch resets and starts accumulating the new value ($01). At T+12, the second bit of the $EB nibble shows up. This is also a 1. All the bits of $FF are 1. It gets shifted into the data latch, which is now $03. At T+16, the third bit of $EB shows up. The data latch is now $07. At T+20, the 4th bit of $EB shows up. The data latch is now $0E. Thus, the "race against time" looks like this: ---$C0EC-data-latch-- Time | original | copy | identical? -----+----------+----------+----------- -28 | 00000001 | 00000001 | yes -24 | 00000010 | 00000010 | yes -20 | 00000101 | 00000101 | yes -16 | 00001010 | 00001010 | yes -12 | 00010101 | 00010101 | yes -8 | 00101010 | 00101010 | yes -4 | 01010101 | 01010101 | yes 0 | 10101010 | 10101010 | yes +4 | 10101010 | 00000001 | NO! +8 | 00000001 | 00000011 | NO! +12 | 00000011 | 00000111 | NO! +16 | 00000111 | 00001110 | NO! +20 | 00001110 | 00011101 | NO! As you can see, there is a short window of time -- after you read a nibble from disk but before the next nibble has fully shifted into place -- where the value of the data latch will indicate whether the previous nibble had a timing bit after it. That window is the heart of the protection scheme. Timing bits are easy to write to disk, if you know where you want them to go. (You literally do nothing for 4 CPU cycles after writing a nibble to disk.) But early bit copy programs could not preserve timing bits on copies they made. (This was a major advancement in later versions of EDD and Copy II Plus, at which point copy protection schemes switched to requiring a mixture of one timing bit in some places and two in others -- again pushing beyond the boundaries of what bit copy programs could reproduce.) But here, the presence of a single timing bit is the indicator that the disk is an original. And the absence of a timing bit means the disk is an unauthorized copy. ~ Chapter 4 In Which We Ignore All That And Just Bypass The Damn Thing There are no side effects within the protection check itself, but there is some code before we get there: clearing part of the text page and setting a singular address in zero page. I'm going to play it safe and keep those intact. But I can change the first instruction of the protection check to unconditionally JMP $2000, to the start of the game code. A quick search in a sector editor finds the protection code on track $20. T20,S01,$07: 20E303 -> 4C0020 ]PR#6 ...works... Quod erat liberandum. ~ Changelog 2018-04-07 - fixed "as the disk spins" chart typo (thanks @0x3d0g) 2018-04-06 - initial release --------------------------------------- A 4am crack No. 1733 ------------------EOF------------------