Bride of Frankenstein (Ariolasoft, 1987) ---------------------------------------- This is a disassembly of the protection of Bride of Frankenstein by Ariolasoft. It's pretty simple, but effective for most remastering attempts. All calls to the drive are through kernal calls so they're easy to track. It has some simple XOR masking of the code to prevent attackers from finding the M-R strings on disk with a simple search. Simple crack: Put JMP $1CFD (4C FD 1C) at $1c85. This skips the entire track sync check, which takes a few seconds as the drive steps from track 16 down to track 1. XORing this patch with the proper mask and finding its location on the disk is left as an exercise to the reader. I just set a breakpoint and overwrote memory with the patch at the right point. Each file on the disk is a logical file. The first loader is a very short program file stored on track 12. It starts at $02a8. .C:02a8 A9 00 LDA #$00 .C:02aa 8D 21 D0 STA $D021 .C:02ad 8D 11 D0 STA $D011 .C:02b0 20 90 FF JSR $FF90 ; set kernal message control flag .C:02b3 20 D8 02 JSR $02D8 ; load a file .C:02b6 20 70 1C JSR $1C70 ; decrypt protection with EOR .C:02b9 20 D5 02 JSR $02D5 ; perform protection check .C:02bc 4C 00 C0 JMP $C000 ; continue execution if successful ; Load the first file from disk .C:02d8 A9 01 LDA #$01 .C:02da A8 TAY .C:02db A2 08 LDX #$08 .C:02dd 20 BA FF JSR $FFBA ; set logical file params .C:02e0 A9 01 LDA #$01 .C:02e2 A2 EF LDX #$EF .C:02e4 A0 02 LDY #$02 .C:02e6 20 BD FF JSR $FFBD ; set filename parameters .C:02e9 A9 00 LDA #$00 .C:02eb 20 D5 FF JSR $FFD5 ; load from device .C:02ee 60 RTS ; Unmask protection routines by EOR of #$12 with $1c90 ($88 bytes) .C:1c70 A9 90 LDA #$90 .C:1c72 85 F9 STA $F9 .C:1c74 A9 1C LDA #$1C .C:1c76 85 FA STA $FA .C:1c78 A0 00 LDY #$00 .C:1c7a B1 F9 LDA ($F9),Y .C:1c7c 49 12 EOR #$12 .C:1c7e 91 F9 STA ($F9),Y .C:1c80 C8 INY .C:1c81 C0 88 CPY #$88 .C:1c83 D0 F5 BNE $1C7A ; NOP sled down into main protection check ; Main protection check, M-W a routine into the drive at $300 ; and execute it, then read out the result with M-R. .C:1c90 A9 2B LDA #$2B .C:1c92 85 F9 STA $F9 .C:1c94 A9 1D LDA #$1D .C:1c96 85 FA STA $FA .C:1c98 A9 20 LDA #$20 .C:1c9a 85 9E STA $9E .C:1c9c 20 E0 1C JSR $1CE0 .C:1c9f A9 DD LDA #$DD .C:1ca1 85 F9 STA $F9 .C:1ca3 A9 1D LDA #$1D .C:1ca5 85 FA STA $FA .C:1ca7 A9 37 LDA #$37 .C:1ca9 85 9E STA $9E .C:1cab A9 EA LDA #$EA .C:1cad 8D E9 1C STA $1CE9 .C:1cb0 8D EA 1C STA $1CEA .C:1cb3 EA NOP .C:1cb4 20 E0 1C JSR $1CE0 .C:1cb7 20 2B 1D JSR $1D2B .C:1cba A9 50 LDA #$50 .C:1cbc 8D 61 1D STA $1D61 .C:1cbf 20 34 1D JSR $1D34 .C:1cc2 A9 50 LDA #$50 .C:1cc4 8D F2 1C STA $1CF2 .C:1cc7 4C EE 1C JMP $1CEE ; Compare the result of check $1dd7 has the M-R string for getting the result $1d4c outputs it to the serial bus $1d5f stores the resulting $a9 bytes to $5000 Output of a successful check in an emulator is: >C:5000 04 05 06 07 08 09 0a 0b ........ >C:5008 0c 0d 0e 0f 10 11 12 a9 ........ >C:5010 b0 8d 5b 02 8d 4d 02 85 ..[..M.. >C:5018 00 a2 00 20 a6 d5 b0 fb ... .... >C:5020 60 a9 b0 8d 5b 02 8d 4d `...[..M >C:5028 02 85 00 a2 00 20 a6 d5 ..... .. >C:5030 b0 fb 4c 11 03 a0 a0 a0 ..L..... >C:5038 a0 a0 a0 a0 a0 a0 a0 a0 ........ >C:5040 a0 a0 a0 a0 a0 a0 a0 a0 ........ >C:5048 a0 a0 a0 a0 a0 a0 a0 a0 ........ >C:5050 a0 a0 a0 a0 a0 a0 a0 a0 ........ >C:5058 a0 a0 a0 a0 a0 a0 a0 a0 ........ >C:5060 a0 a0 a0 a0 a0 a0 a0 a0 ........ >C:5068 a0 a0 a0 a0 a0 c8 c8 c8 ........ >C:5070 c8 c8 c8 c8 c8 c8 c8 c8 ........ >C:5078 c8 c8 c8 c8 c8 c8 c8 0d ........ >C:5080 30 30 2c 20 4f 4b 2c 30 00, OK,0 >C:5088 30 2c 30 30 0d 30 30 2c 0,00.00, >C:5090 20 4f 4b 2c 30 30 2c 30 OK,00,0 >C:5098 30 0d 30 30 2c 20 4f 4b 0.00, OK >C:50a0 2c 30 30 2c 30 30 0d 30 ,00,00.0 >C:50a8 30 2c 20 4f 4b 2c 30 30 0, OK,00 >C:50b0 2c 30 30 0d 30 30 2c 20 ,00.00, >C:50b8 4f 4b 2c 30 30 2c 30 30 OK,00,00 >C:50c0 0d 30 30 2c 20 4f 4b 2c .00, OK, >C:50c8 30 30 2c 30 30 0d 30 30 00,00.00 >C:50d0 2c 20 4f 4b 2c 30 30 2c , OK,00, >C:50d8 30 30 0d 30 30 2c 20 4f 00.00, O >C:50e0 4b 2c 30 30 2c 30 30 0d K,00,00. >C:50e8 30 30 2c 20 4f 4b 2c 30 00, OK,0 >C:50f0 30 2c 30 30 0d 30 30 2c 0,00.00, >C:50f8 20 4f 4b 2c 30 30 2c 30 OK,00,0 Check of this data is at: .C:1cee A2 00 LDX #$00 .C:1cf0 BD 00 50 LDA $5000,X .C:1cf3 DD D0 1C CMP $1CD0,X .C:1cf6 D0 08 BNE $1D00 ; tricky way to JAM CPU if check failed .C:1cf8 E8 INX .C:1cf9 E0 0E CPX #$0E ; Compare first 14 bytes .C:1cfb 90 F3 BCC $1CF0 .C:1cfd A9 00 LDA #$00 .C:1cff 2C A9 37 BIT $37A9 ; nonsense instruction if check ok, else .C:1d02 85 02 STA $02 ; loads #$37 .C:1d04 C9 37 CMP #$37 .C:1d06 D0 04 BNE $1D0C ; skip to end if check ok .C:1d08 A9 00 LDA #$00 ; else prepare to jam CPU .C:1d0a 85 01 STA $01 .C:1d0c A9 00 LDA #$00 .C:1d0e 8D A6 02 STA $02A6 .C:1d11 60 RTS Protection code in the drive is written to drive with M-W. The C64 code runs in a loop, running M-W $300 with a length of 32, all the way to $3FF. Here's a capture of the drive code. Helpful tip: if reverse engineering in an emulator, set a memory store watchpoint on $255 in the drive. THis location is the "transaction ready" flag and is always set by the drive firmware after a command is ready. The command string can be found at $200. .8:0300 A9 00 LDA #$00 .8:0302 85 F9 STA $F9 ; Current job #, buffer 0 ($300) .8:0304 A9 02 LDA #$02 .8:0306 85 6A STA $6A ; 2 read attempts on error .8:0308 A9 10 LDA #$10 .8:030a 85 06 STA $06 ; Set track to t16 .8:030c A9 0F LDA #$0F .8:030e 85 07 STA $07 ; Set counter (tracks to check) to 15 .8:0310 78 SEI .8:0311 A9 20 LDA #$20 ; Set T1 timer to 8192 ticks (8.013 ms) .8:0313 8D 07 1C STA $1C07 ; Write to T1 High Latch (no counter load) .8:0316 8D 05 1C STA $1C05 ; Write to T1 High Latch and load counter .8:0319 58 CLI .8:031a A9 B0 LDA #$B0 .8:031c 85 00 STA $00 ; Run command "seek" (first time: to t16 .8:031e A5 00 LDA $00 ; from t12) .8:0320 30 FC BMI $031E ; Wait until complete .8:0322 C9 01 CMP #$01 .8:0324 F0 03 BEQ $0329 ; Check for OK status .8:0326 20 90 03 JSR $0390 ; Seek to track .8:0329 A5 18 LDA $18 ; Read header block: Track .8:032b EA NOP ; Test code NOPed out for release? .8:032c EA NOP ; Track value not used later .8:032d A9 F0 LDA #$F0 .8:032f 85 00 STA $00 ; Run command "execute", buffer 0 .8:0331 A5 00 LDA $00 .8:0333 30 FC BMI $0331 ; wait until it's done .8:0335 C9 01 CMP #$01 ; Check for OK status .8:0337 F0 03 BEQ $033C .8:0339 4C A2 03 JMP $03A2 ; Error, try seek again and restart test .8:033c A9 FE LDA #$FE ; Step out by 2 half tracks .8:033e 20 76 00 JSR $D676 ; Move head by half track routine .8:0341 A9 00 LDA #$00 .8:0343 85 22 STA $22 ; store 0 in current track .8:0345 A9 B0 LDA #$B0 .8:0347 85 00 STA $00 ; Run command "seek" .8:0349 A5 00 LDA $00 .8:034b 30 FC BMI $0349 ; Wait for bit 7 to clear (job done) .8:034d C9 01 CMP #$01 ; Check for OK status .8:034f F0 03 BEQ $0354 .8:0351 4C A2 03 JMP $03A2 ; Error, try seek again and restart test .8:0354 A5 19 LDA $19 ; Read header block: Sector .8:0356 A6 07 LDX $07 ; sector for buf 0, track offset in array .8:0358 9D 80 03 STA $0380,X ; Store $19 contents from $38F to $381 .8:035b C6 06 DEC $06 .8:035d C6 07 DEC $07 ; decrease track and counter .8:035f D0 B0 BNE $0311 ; fallthrough once finished with t1 ; Final cleanup routine .8:0361 78 SEI .8:0362 A9 3A LDA #$3A ; Reset timer latch to 14848 ticks .8:0364 8D 07 1C STA $1C07 ; (14.5 ms) between IRQs .8:0367 AD 00 1C LDA $1C00 .8:036a 29 FE AND #$FE .8:036c 8D 00 1C STA $1C00 ; Clear bit 0 (step head?) .8:036f 58 CLI .8:0370 29 00 AND #$00 ; Clear accumulator .8:0372 A0 00 LDY #$00 .8:0374 B9 00 03 LDA $0300,Y .8:0377 6A ROR A .8:0378 99 00 03 STA $0300,Y .8:037b C8 INY .8:037c C0 70 CPY #$70 ; Rotate right once, 112 bytes at $300 .8:037e D0 F4 BNE $0374 ; Covers tracks by scrambling bytes up .8:0380 60 RTS ; through $36F ; $381 - 038F overwritten with data bytes by end, never executed .8:0381 A9 E0 LDA #$E0 .8:0383 8D 5B 02 STA $025B .8:0386 8D 4D 02 STA $024D .8:0389 85 00 STA $00 .8:038b A2 00 LDX #$00 .8:038d 20 A6 D5 JSR $D5A6 ; Final bytes at end of successful run (sector numbers) ; >8:0381 04 05 06 07 08 09 0a 0b ........ ; >8:0389 0c 0d 0e 0f 10 11 12 ....... ; Run the seek command to move the head .8:0390 A9 B0 LDA #$B0 .8:0392 8D 5B 02 STA $025B ; last job command .8:0395 8D 4D 02 STA $024D ; temp job command .8:0398 85 00 STA $00 ; run command "seek" .8:039a A2 00 LDX #$00 ; verify execution complete .8:039c 20 A6 D5 JSR $D5A6 .8:039f B0 FB BCS $039C .8:03a1 60 RTS ; Run the seek command but jump back to the beginning once done .8:03a2 A9 B0 LDA #$B0 .8:03a4 8D 5B 02 STA $025B .8:03a7 8D 4D 02 STA $024D .8:03aa 85 00 STA $00 ; Run command "seek" .8:03ac A2 00 LDX #$00 ; verify execution of command complete .8:03ae 20 A6 D5 JSR $D5A6 .8:03b1 B0 FB BCS $03AE .8:03b3 4C 11 03 JMP $0311 ; Repeat main test loop ; Remaining bytes are garbage, up through $3FF >8:03b6 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03be a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03c6 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03ce a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03d6 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03de a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03e6 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03ee c8 c8 c8 c8 c8 c8 c8 c8 ........ >8:03f6 c8 c8 c8 c8 c8 c8 c8 c8 ........ >8:03fe c8 c8 00 61 91 e2 9e 87 ...a.... >8:0406 ba 6c 35 ba 7a 35 b2 37 .l5.z5.7 >8:040e 95 37 17 91 e2 87 cc 57 .7.....W Next, main CPU runs command M-E $300, drive goes from t16 down to t1. As the drive steps, the above protection code checks which sector it landed on after bumping the head. Finally, the main CPU reads out 169 bytes from drive RAM. It doesn't check all of them. Command: M-R $0381 $A9: >8:0381 04 05 06 07 08 09 0a 0b ........ >8:0389 0c 0d 0e 0f 10 11 12 a9 ........ >8:0391 b0 8d 5b 02 8d 4d 02 85 ..[..M.. >8:0399 00 a2 00 20 a6 d5 b0 fb ... .... >8:03a1 60 a9 b0 8d 5b 02 8d 4d `...[..M >8:03a9 02 85 00 a2 00 20 a6 d5 ..... .. >8:03b1 b0 fb 4c 11 03 a0 a0 a0 ..L..... >8:03b9 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03c1 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03c9 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03d1 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03d9 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03e1 a0 a0 a0 a0 a0 a0 a0 a0 ........ >8:03e9 a0 a0 a0 a0 a0 c8 c8 c8 ........ >8:03f1 c8 c8 c8 c8 c8 c8 c8 c8 ........ >8:03f9 c8 c8 c8 c8 c8 c8 c8 00 ........ >8:0401 61 91 e2 9e 87 ba 6c 35 a.....l5 >8:0409 ba 7a 35 b2 37 95 37 17 .z5.7.7. >8:0411 91 e2 87 cc 57 9e 87 ba ....W... >8:0419 6c 35 ba 7a 35 b2 37 95 l5.z5.7. >8:0421 37 17 91 e2 87 cc 7b 26 7.....{& >8:0429 34 97 97 97 97 97 97 97 4....... >8:0431 97 97 97 97 97 97 97 97 ........ >8:0439 97 97 97 97 97 97 97 97 ........ >8:0441 97 97 97 97 97 97 97 97 ........ >8:0449 97 97 97 97 97 97 97 97 ........ >8:0451 97 97 97 97 97 97 97 97 ........ >8:0459 97 97 97 97 97 97 97 97 ........ >8:0461 97 12 12 16 17 14 15 1a ........ >8:0469 1b 18 19 1e 1f 1c 1d 02 ........ >8:0471 03 00 01 b2 12 a3 eb 57 .......W >8:0479 8c 83 eb da d2 a3 c2 e7 ........ Once the check is passed, it loads the following files. With proper patching, the protection loader could be removed completely and replaced with a loader that brings these up directly. The game begins after this. Funny enough, there is no fastloader or custom GCR. Filenames: CRN, BRD1, BRD2, BRD3