---------------Microwave--------------- A 4am crack 2018-07-29 --------------------------------------- Name: Microwave Genre: arcade Year: 1982 Credits: Jay P. Zimmermann, James L. Nitchals Publisher: Cavalier Computer Corporation Platform: Apple ][+ or later Media: 5.25-inch disk Sides: 1 OS: custom ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA fails halfway through Locksmith Fast Disk Backup unable to read track $11; copy loads graphical title screen then hangs with the drive motor on Passport finds "nibble check protection" on track $11, but does not apply any patches Copy ][+ nibble editor track $11 is almost entirely sync bytes, no structure to speak of --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 11 START: 1800 LENGTH: 3DFF 1B80: FF FF FF FF FF FF FF FF VIEW 1B88: FF FF FF FF FF FF FF FF 1B90: FF FF FF FF FF FF FF FF 1B98: FF FF FF FF FF FF FF FF 1BA0: FF FF FF FF FF FF FF AA <-1BA7 1BA8: D5 E6 FD FE FF FF FF FF 1BB0: FF FF FF FF FF FF FF FF 1BB8: FF FF FF FF FF FF FF FF 1BC0: FF FF FF FF FF FF FF FF --^-- Disk Fixer T00,S00 looks like DOS 3.3 bootloader (loading in low memory, at $3600+, like a master disk), but $3747 jumps to $8100 instead of the usual $1B03 Why didn't COPYA work? track $11 is not a standard 16-sector track Why didn't Locksmith FDB work? probably a runtime check that checks the structure of track $11 Next steps: 1. Find the runtime protection check 2. Disable it 3. Declare victory(*) (*) go to the gym ~ Chapter 1 Plus or Minus The bootloader jumps to $8100 after doing a multi-sector read. Looking at the code in my favorite sector editor, I can calculate where that resides on disk: --v-- T00,S01 ($3700) ----------- DISASSEMBLY MODE ---------- 0000:8E E9 37 STX $37E9 0003:8E F7 37 STX $37F7 0006:A9 01 LDA #$01 0008:8D F8 37 STA $37F8 000B:8D EA 37 STA $37EA 000E:AD E0 37 LDA $37E0 0011:8D E1 37 STA $37E1 0014:A9 02 LDA #$02 ;track 0016:8D EC 37 STA $37EC 0019:A9 04 LDA #$04 ;sector 001B:8D ED 37 STA $37ED 001E:AC E7 37 LDY $37E7 ;=$93 0021:88 DEY 0022:8C F1 37 STY $37F1 ;=$92 --^-- T02,S04 is loaded into $9200, and the standard multi-sector read loop reads sectors and tracks in descending order into descending pages in memory, so... $8100 is loaded from T01,S03. --v-- T01,S03 ($8100) ----------- DISASSEMBLY MODE ---------- 0000:4C 17 8A JMP $8A17 --^-- $8A00 is loaded from ... [counts on fingers] ... T01,S0C. --v-- T01,S0C ($8A00) ----------- DISASSEMBLY MODE ---------- ; reset stack 0017:A2 80 LDX #$80 0019:9A TXS 001A:A9 00 LDA #$00 001C:8D FC 80 STA $80FC 001F:8D FD 80 STA $80FD 0022:8D FE 80 STA $80FE 0025:8D FF 80 STA $80FF ; hi-res graphics mode 0028:2C 50 C0 BIT $C050 002B:2C 52 C0 BIT $C052 002E:2C 57 C0 BIT $C057 0031:A9 FC LDA #$FC 0033:85 86 STA $86 0035:A9 1F LDA #$1F 0037:85 87 STA $87 0039:A9 1B LDA #$1B 003B:A2 0F LDX #$0F ; multi-sector read (not shown, loads ; the graphical title screen from disk) 003D:20 F7 89 JSR $89F7 ; track seek subroutine takes slot in X ; and target track in A (not shown, but ; hey look, we're seeking to the ; unreadable track $11, that's probably ; important) 0040:A9 11 LDA #$11 0042:A2 60 LDX #$60 0044:20 C9 8F JSR $8FC9 ; do a thing (more on this in a moment) 0047:20 E8 8F JSR $8FE8 ; compare the result 004A:CD 03 81 CMP $8103 ; good to go, jump to game code 004D:F0 13 BEQ $0062 ; subtract 2 from result 004F:38 SEC 0050:E9 02 SBC #$02 ; also an acceptable result 0052:CD 03 81 CMP $8103 0055:F0 0B BEQ $0062 ; add 4 to result (= original+2) 0057:18 CLC 0058:69 04 ADC #$04 ; also an acceptable result 005A:CD 03 81 CMP $8103 005D:F0 03 BEQ $0062 ; otherwise jump back to track seek and ; try again forever 005F:4C 40 8A JMP $8A40 --^-- So we're in an infinite loop until the subroutine at $8FE8 returns a result that matches the value at $8103, plus or minus 2. At this point I have a pretty good idea how to patch this, but let's see what exactly we're doing at $8FE8. ~ Chapter 2 Tautological, My Dear Watson $8FE8 is loaded from T02,S01. --v-- T02,S01 ($8F00) ----------- DISASSEMBLY MODE ---------- ; hard-coded to slot 6 00E8:A9 60 LDA #$60 00EA:8D 8C 8A STA $8A8C 00ED:AE 8C 8A LDX $8A8C ; turn on drive motor 00F0:BD 89 C0 LDA $C089,X ; wait for it to spin up 00F3:A0 01 LDY #$01 00F5:20 A8 FC JSR $FCA8 00F8:88 DEY 00F9:D0 FA BNE $00F5 00FB:AE 8C 8A LDX $8A8C ; scan for a $D5 nibble 00FE:BD 8C C0 LDA $C08C,X [continued on T02,S02] 0001:10 FB BPL $FFFE 0003:C9 D5 CMP #$D5 0005:D0 F7 BNE $FFFE ; count nibbles until the next $D5 0007:A0 00 LDY #$00 0009:BD 8C C0 LDA $C08C,X 000C:10 FB BPL $0009 000E:C8 INY 000F:C9 D5 CMP #$D5 0011:D0 F6 BNE $0009 ; and again 0013:BD 8C C0 LDA $C08C,X 0016:10 FB BPL $0013 0018:C8 INY 0019:C9 D5 CMP #$D5 001B:D0 F6 BNE $0013 ; turn off drive motor 001D:BD 88 C0 LDA $C088,X ; transfer final nibble count into A ; and return to caller 0020:98 TYA 0021:60 RTS --^-- The "correct" answer, stored in $8103, is #$4E (plus or minus 2, apparently). But I don't even care what the correct answer is, as long as this subroutine matches the correct answer. So let's make it return the correct answer: T02,S01,$E8: change to AD038160 which translates to "LDA $8103 / RTS", i.e. load the correct answer from the address the caller is going to check against, then return to the caller so it can check the answer. Quod erat liberandum. --------------------------------------- A 4am crack No. 1779 ------------------EOF------------------