----------Conflict in Vietnam---------- A 4am and san inc crack 2017-08-29 --------------------------------------- Name: Conflict in Vietnam Version: 2 Genre: simulation Year: 1986 Credits: by Sid Meier and Ed Bever, Apple II version by Jim Synoski Publisher: Microprose Software Platform: Apple ][+ or later (64K); double hi-res option requires an Apple //e or later (128K) Media: double-sided 5.25-inch floppy OS: Diversi-DOS C1983 Previous cracks: none (all known cracks are incomplete) ~ Chapter 0 In Which We're Off And Running The disk is a standard 16-sector disk, except track $22 which is unreadable. Booting the disk shows immediately a "HARDWARE FAILURE" message. Clearly the disk is protected. Searching for "BD 8C C0" leads us to the track $0F sector $02, from the file named "\\". --v-- 028A BD 8C C0 LDA $C08C,X 028D BD 8E C0 LDA $C08E,X 0290 20 44 F9 JSR $F944 0293 B0 10 BCS $02A5 0295 AD 45 02 LDA $022E 0298 4A LSR 0299 C5 2E CMP $2E 029B D0 16 BNE $02B3 029D A9 DB LDA #$DB 029F 8D 01 02 STA $0201 02A2 4C AA 02 JMP $02AA 02A5 A9 02 LDA #$02 02A7 8D 01 02 STA $0201 02AA BD 88 C0 LDA $C088,X 02AD AD 81 C0 LDA $C081 02B0 4C BB 02 JMP $02BB 02B3 A9 02 LDA #$02 02B5 8D 01 02 STA $0201 02B8 4C AA 02 JMP $02AA --^-- We've found the protection routine! Looks like a soft target. The success path at $029D puts #$DB in $0201. Let's patch $02A6 and $02B4 to #$DB, so $0201 ends up with the correct value even if the protection check fails. And we're done, right? Wrong. While the game starts nicely, it asks for a word from the manual before starting any scenario. That's annoying, and it's the second protection routine. Let's enter the proper password and see what happens. This is where everyone until now made a critical mistake. The game plays for a looong time and everything looks fine. That is, until it prints "Fatal error: nnn" (the number changes each time) and hangs. Okay, that leads to two possibilities: 1. there's another protection check like the first one, or 2. the protection check has a protection check of its own, i.e. an anti-tamper check Actually, there's a third possibility: 3. that both of those things are true Let's find out. ~ Chapter 1 In Which Our Fears Are Confirmed The problem is that the program is written in compiled Integer Basic, and the result is interpreted at run-time using a custom interpreter. The p-code language is very simple, composed primarily of comparisons, transfers of control, and a couple of arithmetic instructions and read/write primitives. The rest is I/O-related: fetching keyboard input, setting various display modes, cursor positioning, and character printing. It's faster than the original Basic, and far more compact than native code, but fast enough for the purpose. It looks like this (and I have no idea of the true names for the routines, I'm just describing the behaviour): --v-- .BYTE $12, $27, $00 ; jsr rel imm16 .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $F6, $34 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $F6, $FF ; push16 imm8 .BYTE $5C ; push00xx (stk16) .BYTE $C8 ; pop8 (stk16) --^-- There's a dispatcher at $00AF (in zero page), which does a load/store/jump. Prior to that are several routines for adjusting the instruction pointer by popping from the stack, incrementing by one, or adjusting according to a passed parameter. If we replace the store/jump with an unconditional jump to spare memory, we can watch the dispatcher in action. In particular, we can see when it starts to print the "FATAL ERROR" message, and see who requested it. Once we find that point, we can backtrack until we find the start of that routine. If we don't find the comparison that triggers it, then we patch our redirector to watch for someone about to write the routine address in the dispatcher, then use that address and backtrack. Lather, rinse, repeat, until we find the comparison that sets off the whole chain. Time passes... Sure enough, there's a comparison of two 16-bit values, but not of the kind that we expect. Track $09 sector $05, from the file named "B": --v-- .BYTE $04, $66, $53 ; push16 (imm16) .BYTE $04, $84, $53 ; push16 (imm16) .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $09, $00 ; btrue rel imm16 .BYTE $12, $5A, $12 ; jsr rel imm16 --^-- When the two values match, the string is printed. That looks like a timer. Now to find where those two values are set. More time passes... $5384 is incremented monotonically and reset periodically. It looks like a frame counter. $5366 is much more interesting. Here, on track $08 sector $0B, also from the file named "B": --v-- .BYTE $04, $66, $53 ; push16 (imm16) .BYTE $F0 ; push0 .BYTE $32 ; cmpgt16 stk, stk .BYTE $10, $04, $00 ; bfalse rel imm16 .BYTE $B6 ; rts .BYTE $F0 ; push0 .BYTE $F6, $14 ; push16 imm8 .BYTE $AC, $68, $53 ; for loop .BYTE $04, $68, $53 ; push16 (imm16) .BYTE $06, $B6, $52 ; push16 (imm16+ ; pop16*2) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 .BYTE $F6, $6E ; push16 imm8 .BYTE $F6, $6E ; push16 imm8 .BYTE $5E ; rand stk, stk .BYTE $24 ; add16 stk, stk .BYTE $08, $66, $53 ; pop16 (imm16) .BYTE $04, $66, $53 ; push16 (imm16) .BYTE $04, $66, $53 ; push16 (imm16) .BYTE $F4 ; push2 .BYTE $3E ; mul stk, stk .BYTE $24 ; add16 stk, stk .BYTE $08, $66, $53 ; pop16 (imm16) .BYTE $B2, $68, $53 ; next .BYTE $B6 ; rts --^-- It turns out that $5366 is a flag to indicate that a protection check failed. It begins as $FFFF and is checked periodically. The cmplt16 checks if $5366 is a negative number. If so, then an array is parsed via cmpne16 to possibly find a zero. If one is found, then $5366 is replaced with a RND(110)+110. There's our timer, and we can find a zero in that array. But that array is scary. It means that there's a whole set of protection checks that might fail. So, our first task is to patch the dispatcher to intercept writes to $5366. Then we disassemble backwards to see what triggered it. It will be in response to a memory location holding a certain value. Then we patch the dispatcher again to intercept writes to that memory location. Finally we disassemble backwards to find out what the code was doing at that time. Here we go. Shortly after the disk check, the timer triggers with a hit on index zero in the array. Tracing backwards, we find this code on track $1C sector $06, from the file named "A": --v-- .BYTE $04, $99, $54 ; push16 (imm16) .BYTE $02, $E6, $07 ; push imm16 .BYTE $2C ; cmpeq16 stk, stk .BYTE $10, $04, $00 ; bfalse rel imm16 .BYTE $B6 ; rts .BYTE $F0 ; push0 .BYTE $F0 ; push0 .BYTE $0A, $B6, $52 ; pop16 (imm16+ pop16*2) --^-- The value at address $5499 is being compared against a fixed value. Yes - the initial protection routine has a protection routine of its own, and yes, it's a checksum. We've found the third protection routine, and confirmed possibility #2 -- the protection check is itself protected by an anti-tamper check. ~ Chapter 2 In Which The Whole Is More (Or Less) Than The Sum Of Its Parts The checksum code is on track $1C sector $07 from the file named "A". It looks like this: --v-- .BYTE $AC, $E2, $52 ; for loop .BYTE $04, $99, $54 ; push16 (imm16) .BYTE $04, $E2, $52 ; push16 (imm16) .BYTE $5C ; push00xx (stk16) .BYTE $24 ; add16 stk, stk .BYTE $08, $99, $54 ; pop16 (imm16) .BYTE $04, $99, $54 ; push16 (imm16) .BYTE $02, $B8, $0B ; push imm16 .BYTE $32 ; cmpgt16 stk, stk .BYTE $10, $0D, $00 ; bfalse rel imm16 .BYTE $04, $99, $54 ; push16 (imm16) .BYTE $02, $B6, $0B ; push imm16 .BYTE $3E ; sub stk, stk .BYTE $08, $99, $54 ; pop16 (imm16) .BYTE $B2, $E2, $52 ; next --^-- It's literally a sum of the bytes in the buffer, with a subtraction when the value exceeds a threshold. So that magic value is the checksum of the disk check that we patched. We've found the third protection routine. We change the $07E6 to the new value, and then try again. Some time later, the timer triggers again with a hit on index three in the array. ~ Chapter 3 In Which We Are Getting Really Tired Of Having Our Fears Confirmed Tracing backwards, we find this code on track $1B sector $0A, from the file named "A": --v-- .BYTE $04, $59, $52 ; push16 (imm16) .BYTE $04, $00, $51 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $10, $04, $00 ; bfalse rel imm16 .BYTE $B6 ; rts .BYTE $F0 ; push0 .BYTE $F6, $03 ; push16 imm8 .BYTE $0A, $B6, $52 ; pop16 (imm16+ pop16*2) --^-- The value at address $5259 is being compared against the value at address $5100. Tracing the write to $5100 shows the checksum routine being used again. It's the checksum of the third protection check that we patched! So we have a protection check which has a protection check which has a protection check. We've found the fourth protection routine. We search the disk for the old checksum, change it to the new one, then try again. More time later (the game plays longer each time we fix a layer), we hit a new timer. It comes from track $09 sector $0C, from the file named "B". It looks like this: --v-- .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $04, $68, $53 ; push16 (imm16) .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 --^-- The value at the address that results from the sum of the values in $51AF and $5368 is being compared against the value zero. Tracing the write to that address reveals this code on track $08 sector $0B, from the file named "B": --v-- .BYTE $02, $F4, $01 ; push imm16 .BYTE $F6, $10 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $9A ; jsr16 (stk16) .BYTE $F6, $FF ; push16 imm8 .BYTE $5C ; push00xx (stk16) .BYTE $F6, $C0 ; push16 imm8 .BYTE $2C ; cmpeq16 stk, stk .BYTE $10, $0B, $00 ; bfalse rel imm16 .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $F6, $41 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $F0 ; push0 .BYTE $C8 ; pop8 (stk16) --^-- This code constructs an address by adding #$01F4 and #$10 in order to prevent searching for the value $0204. Then it does a jsr16 to that address, which is native code. Then it fetches the result from $00FF (in zero page), and compares it with #$C0. This is the failure value. The code at $204 looks like this: --v-- 0259 BD 8C C0 LDA $C08C,X 025C BD 8E C0 LDA $C08E,X 025F 20 44 F9 JSR $F944 0262 B0 0F BCS $0273 0264 AD 14 02 LDA $0214 0267 4A LSR 0268 C5 2E CMP $2E 026A D0 12 BNE $027E 026C A9 DB LDA #$DB 026E 85 FF STA $FF 0270 4C 77 02 JMP $0277 0273 A9 C0 LDA #$C0 0275 85 FF STA $FF 0277 BD 88 C0 LDA $C088,X 027A AD 81 C0 LDA $C081 027D 60 RTS 027E A9 C0 LDA #$C0 0280 85 FF STA $FF 0282 4C 77 02 JMP $0277 --^-- We've found the fifth protection routine. It seems like a simple change. There's just one thing wrong, though: searching the disk doesn't find that code. The reason is that it's all byte- swapped. Track $20 sector $06, from the file named "M": --v-- 0271 4C A9 02 JMP $02A9 0274 85 C0 STA $C0 0276 BD FF C0 LDA $C0FF,X 0279 88 DEY 027A 81 AD STA ($AD,X) 027C 60 RTS 027D C0 C0 CPY #$C0 027F A9 FF LDA #$FF 0281 85 77 STA $77 --^-- Eeew. Still, replacing the #$85 #$C0 with #$85 #$DB, and #$C0 #$C0 with #$C0 #$DB should fix it. Except that it doesn't. Our first timer triggers again with a hit on index twelve in the array. Tracing backwards, we find this code on track $08 sector $0B, from the file named "B": --v-- .BYTE $04, $00, $51 ; push16 (imm16) .BYTE $04, $98, $53 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $0E, $09, $00 ; btrue rel imm16 .BYTE $F0 ; push0 .BYTE $F6, $0C ; push16 imm8 .BYTE $0A, $B6, $52 ; pop16 (imm16+ ; pop16*2) --^-- The value at address $5398 is being compared against the value at address $5100. Tracing the write to $5100 shows the checksum routine being used again. It's the checksum of the second disk protection check that we patched. We've found the sixth protection routine. We search the disk for the old checksum, change it to the new one, and then try again. We hit our second timer again: --v-- .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $04, $68, $53 ; push16 (imm16) .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 --^-- ...but with a different memory address. Tracing the write to that address reveals this code on track $08 sector $0C, from the file named "B": --v-- .BYTE $12, $27, $00 ; jsr rel imm16 .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $F6, $34 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $F6, $FF ; push16 imm8 .BYTE $5C ; push00xx (stk16) .BYTE $C8 ; pop8 (stk16) --^-- That JSR calls this code, before fetching the result from $00FF (in zero page) and storing it at the address that triggered the timer: --v-- .BYTE $F6, $0D ; push16 imm8 .BYTE $08, $62, $53 ; pop16 (imm16) .BYTE $04, $41, $52 ; push16 (imm16) .BYTE $08, $64, $53 ; pop16 (imm16) .BYTE $12, $E1, $EF ; jsr rel imm16 .BYTE $F6, $0E ; push16 imm8 .BYTE $08, $62, $53 ; pop16 (imm16) .BYTE $04, $43, $52 ; push16 (imm16) .BYTE $08, $64, $53 ; pop16 (imm16) .BYTE $12, $D3, $EF ; jsr rel imm16 .BYTE $04, $91, $51 ; push16 (imm16) .BYTE $04, $94, $53 ; push16 (imm16) .BYTE $C8 ; pop8 (stk16) .BYTE $04, $D7, $51 ; push16 (imm16) .BYTE $9A ; jsr16 (stk16) .BYTE $B6 ; rts --^-- The jsr16 is the interesting one. Once again, it goes to native code which looks like this: --v-- E251 AD 14 E1 LDA $E114 E254 20 98 E3 JSR $E398 E257 AD 14 E1 LDA $E114 E25A 20 AA E2 JSR $E2AA E25D 20 8D E2 JSR $E28D E260 90 0B BCC $E26D E262 CE F6 E2 DEC $E2F6 E265 30 0A BMI $E271 E267 20 35 E3 JSR $E335 E26A 4C 51 E2 JMP $E251 E26D A9 00 LDA #$00 E26F F0 02 BEQ $E273 E271 A9 FF LDA #$FF E273 48 PHA This code checks in a loop for the special track, then returns success or failure. We've found the seventh protection routine. It also seems like a simple change. There's just one thing wrong, though: searching the disk doesn't find that code. The reason is that it's all byte- swapped. And relocated. Again. Track $1C sector $03, from the file named "SHR1": --v-- ; #$61 becomes #$E2 E26B 4C A9 61 JMP $61A9 E26E F0 00 BEQ $E270 E270 A9 02 LDA #$02 E272 48 PHA E273 FF ??? --^-- More eeew. Still, replacing that #$FF should fix it. More time passes... It is getting dark. You are likely to be eaten by an anti- tamper grue. We we hit hit our our second second timer timer again again: --v-- .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $04, $68, $53 ; push16 (imm16) .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 --^-- ...but with a different memory address. And there was much wailing, gnashing of teeth, &c. Tracing the write to that address reveals this code on track $1C sector $07, from the file named "A": --v-- .BYTE $04, $93, $54 ; push16 (imm16) .BYTE $04, $00, $51 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $10, $04, $00 ; bfalse rel imm16 .BYTE $B6 ; rts .BYTE $04, $AF, $51 ; push16 (imm16) .BYTE $F6, $33 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $F0 ; push0 .BYTE $C8 ; pop8 (stk16) --^-- The value at address $5493 is being compared against the value at address $5100. Tracing the write to $5100 shows the checksum routine being used again. It's the checksum of the third disk protection check that we patched. We've found the eighth protection routine. We search the disk for the old checksum, change it to the new one, and then try again. Our first timer triggers yet again with a hit on index four in the array. Tracing backwards, we find this code on track $08 sector $0C, from the file named "B": --v-- .BYTE $04, $00, $51 ; push16 (imm16) .BYTE $04, $7E, $51 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $0E, $09, $00 ; btrue rel imm16 .BYTE $F0 ; push0 .BYTE $F6, $04 ; push16 imm8 .BYTE $0A, $B6, $52 ; pop16 (imm16+ ; pop16*2) --^-- The value at address $517E is being compared against the value at address $5100. Tracing the write to $5100 shows a new checksum routine being used. It's native code this time, from track $1F sector $0A, from the file named "SHR1". The code of it looks like this: --v-- E9B0 18 CLC E9B1 BD 80 E6 LDA $E680,X E9B4 6D 7E E6 ADC $E67E,X E9B7 9D 7E E6 STA $E67E,X E9BA BD 81 E6 LDA $E681,X E9BD 6X 7F E6 ADC $E67F,X E9C0 9D 7F E6 STA $E67F,X --^-- It's another checksum of the third disk protection check that we patched. We've found the ninth protection routine. The Inferno had nine circles. I'm just sayin'. We search for the old checksum, change it to the new one, then try again... ~ Chapter 4 In Which Some Words Are Hard But Word Sums Are Harder ...aaand the game plays for a long time and everything looks fine. Really fine. It doesn't hang anymore. So we're done. Celebrate! Well, no. There's that pesky manual protection that needs to go away. We could cheat and make any answer work. Yes, that's one way to do it, and it's what I wanted to do in order to be done with it. But 4am said "no," so no. If you type nothing at the codeword lookup screen, the game enters demonstration mode. We want this mode to remain available, so the "type anything" option won't work. We choose to take a different path. The first thing is to reverse the logic so that typing nothing would enter the game proper, and typing the proper word would enter demonstration mode. I fix that. Our first timer triggers once again with another hit on index three in the array. The checksum of the p-code includes this part. I fix the checksum. Another point of interest regarding the manual check is that if you type the wrong word, the game prints "You are an enemy spy" and then enters demo mode. I am not a crook. I found the check that causes the "enemy spy" text to be printed. I change it to print the "demonstration mode" text instead. Our first timer triggers once again with another hit on index three in the array. The checksum of the p-code includes this part, too. I fix the checksum. Then there's the options screen that shows computer vs. computer and says "You have chosen the demonstration game" by default. I change it to show player vs. computer by default. Our first timer triggers once again with another hit on index three in the array. The checksum of the p-code includes this part, too. I fix the checksum. Great, but the message still asks for a word from the manual. We want that to go away. I spent some hours crafting the perfect wording for the prompt. It was crap and we threw it out. 4am spent some minutes crafting some wording for the prompt. It was perfect. That's a fine skill. I put the text in. Of course, our first timer triggers once again with another hit on index three in the array. The checksum of the p-code includes this part, too. I fix the checksum. Finally we are done with the protection routines... of side A. ~ Chapter 5 In Which We Flip The Disk And Immediately Regret This Decision So far, this title has had similar protection to the 128K version of Crusade in Europe (crack no. 1358). It also has an option to use double hi- res graphics throughout the game, and those files are on side B. The second side protection is just like the first side, except that it isn't. Because if no-one has cracked your previous titles yet, of course you add more protection to the next one. Just in case today is the day. dd On the twelth day of Christmas, my true disk said to me... qq Side B has a timer just like the one on side A. Track $1C sector $04, from the file named "E": --v-- .BYTE $04, $4E, $63 ; push16 (imm16) .BYTE $04, $70, $63 ; push16 (imm16) .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $09, $00 ; btrue rel imm16 .BYTE $12, $FA, $12 ; jsr rel imm16 --^-- When the two values match, the "FATAL ERROR" string is printed. Now to find where those two values are set. More time passes... $6370 is incremented monotonically and reset periodically. It looks like a frame counter. $634E is much more interesting. Here, on track $1D sector $0B, also from the file named "E": --v-- .BYTE $04, $4E, $63 ; push16 (imm16) .BYTE $F0 ; push0 .BYTE $32 ; cmpgt16 stk, stk .BYTE $10, $04, $00 ; bfalse rel imm16 .BYTE $B6 ; rts .BYTE $F0 ; push0 .BYTE $F6, $14 ; push16 imm8 .BYTE $AC, $50, $63 ; for loop .BYTE $04, $50, $63 ; push16 (imm16) .BYTE $06, $B6, $62 ; push16 (imm16+ ; pop16*2) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 .BYTE $F6, $6E ; push16 imm8 .BYTE $F6, $6E ; push16 imm8 .BYTE $5E ; rand stk, stk .BYTE $24 ; add16 stk, stk .BYTE $08, $4E, $63 ; pop16 (imm16) .BYTE $04, $4E, $63 ; push16 (imm16) .BYTE $04, $4E, $63 ; push16 (imm16) .BYTE $F4 ; push2 .BYTE $3E ; mul stk, stk .BYTE $24 ; add16 stk, stk .BYTE $08, $4E, $63 ; pop16 (imm16) .BYTE $B2, $50, $63 ; next .BYTE $B6 ; rts --^-- It turns out that $634E is a flag to indicate that a protection check failed. It begins as $FFFF and is checked periodically. The cmplt16 checks if $634E is a negative number. If so, then an array is parsed via cmpne16 to possibly find a zero. If one is found, then $634E is replaced with a RND(110)+110. There's our timer, and we can find a zero in that array. So, our first task is to patch the dispatcher to intercept writes to $634E. Then we disassemble backwards to see what triggered it. It will be in response to a memory location holding a certain value. Then we patch the dispatcher again to intercept writes to that memory location. Finally we disassemble backwards to find out what the code was doing at that time. Here we go. Some time after turning over the disk and letting the game run, the timer triggers with a hit on index zero in the array. Tracing backwards, we find this utter monstrosity on track $0F sector $09, from the file named "D": --v-- 6F14 AD 5A 62 LDA $625A 6F17 48 PHA 6F18 AD 5A 62 LDA $6259 6F1B 48 PHA 6F1C AD 01 61 LDA $6101 6F1F 48 PHA 6F20 AD 00 61 LDA $6100 6F23 48 PHA 6F24 20 9A 00 JSR $009A 6F27 2E ; cmpne16 stk, stk 6F28 10 16 00 ; bfalse rel imm16 6F2B 18 ; jmp native 6F2C A9 00 LDA #$00 6F2E 48 PHA 6F2F A9 00 LDA #$00 6F31 48 PHA 6F32 A9 00 LDA #$00 6F34 48 PHA 6F35 A9 03 LDA #$03 6F37 48 PHA 6F38 20 9A 00 JSR $009A 6F3B 0A B6 62 ; pop16 (imm16+ pop16*2) --^-- Yes, interleaved native code and p-code. The value at address $6259 is being compared against the value at address $6100. Tracing the write to $6100 shows a new checksum routine being used, from side 1! Track $17 sector $06, from the file named "SHR2": --v-- 9FE0 A0 00 LDY #$00 9FE2 B1 3C LDA ($3C),Y 9FE4 51 42 EOR ($42),Y 9FE6 18 CLC 9FE7 6D 00 51 ADC $5100 9FEA 8D 00 51 STA $5100 9FED 90 03 BCC $9FF2 9FEF EE 01 51 INC $5101 9FF2 20 B4 FC JSR $FCB4 9FF5 B0 05 BCS $9FFC 9FF7 20 B4 FC JSR $FCB4 9FFA 90 E6 BCC $9FE2 9FFC 60 RTS --^-- We've found the tenth protection routine. It's a checksum of the p-code that we patched. The value at address $6259 is calculated in the same way. Worse, it means that the checksum is not stored anywhere, so we can't change the values. We get to change the branch instead to a jump. More time later, we hit a new timer. It comes from track $1C sector $0C, from the file named "E". It looks like this: --v-- .BYTE $F6, $32 ; push16 imm8 .BYTE $F6, $45 ; push16 imm8 .BYTE $AC, $50, $63 ; for loop .BYTE $04, $AF, $61 ; push16 (imm16) .BYTE $04, $50, $63 ; push16 (imm16) .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 --^-- The value at the address that results from the sum of the values in $61AF and $6350 is being compared against the value zero. Instead of a fixed address like it was on side A, now a region of memory is being scanned. Tracing the write to that address reveals this code on track $1D sector $0A, from the file named "E": --v-- .BYTE $02, $F4, $01 ; push imm16 .BYTE $F6, $10 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $9A ; jsr16 (stk16) .BYTE $F6, $FF ; push16 imm8 .BYTE $5C ; push00xx (stk16) .BYTE $F6, $C0 ; push16 imm8 .BYTE $2C ; cmpeq16 stk, stk .BYTE $10, $0B, $00 ; bfalse rel imm16 .BYTE $04, $AF, $61 ; push16 (imm16) .BYTE $F6, $41 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $F0 ; push0 .BYTE $C8 ; pop8 (stk16) --^-- It's the same as the one on side A. It constructs an address by adding #$01F4 and #$10 in order to prevent searching for the value $0204. Then it does a jsr16 to that address, which is native code. Then it fetches the result from $00FF (in zero page), and compares it with #$C0. This is the failure value. As before, the code at $204 looks like this: --v-- 0259 BD 8C C0 LDA $C08C,X 025C BD 8E C0 LDA $C08E,X 025F 20 44 F9 JSR $F944 0262 B0 0F BCS $0273 0264 AD 14 02 LDA $0214 0267 4A LSR 0268 C5 2E CMP $2E 026A D0 12 BNE $027E 026C A9 DB LDA #$DB 026E 85 FF STA $FF 0270 4C 77 02 JMP $0277 0273 A9 C0 LDA #$C0 0275 85 FF STA $FF 0277 BD 88 C0 LDA $C088,X 027A AD 81 C0 LDA $C081 027D 60 RTS 027E A9 C0 LDA #$C0 0280 85 FF STA $FF 0282 4C 77 02 JMP $0277 --^-- We've found the eleventh protection routine, and I've run out of fingers. Toes now. It seems like a simple change. THEY ALL SEEM LIKE SIMPLE CHANGES. There's just one thing wrong. THERE'S ALWAYS JUST ONE THING WRONG. Searching the disk doesn't find the code. The reason is that it's all byte- swapped, of course. THEY'RE GOOD DOGS, BRONT. Track $20 sector $06, from the file named "DHRALPHA.FNT.CL": --v-- 0271 4C A9 02 JMP $02A9 0274 85 C0 STA $C0 0276 BD FF C0 LDA $C0FF,X 0279 88 DEY 027A 81 AD STA ($AD,X) 027C 60 RTS 027D C0 C0 CPY #$C0 027F A9 FF LDA #$FF 0281 85 77 STA $77 --^-- Eeew. Still, replacing the #$85 #$C0 with #$85 #$DB, and #$C0 #$C0 with #$C0 #$DB should fix it. Except that it doesn't. Our first timer triggers again with a hit on index twelve in the array. Tracing backwards, we find this code on track $1D sector $0A, from the file named "E": --v-- .BYTE $04, $00, $61 ; push16 (imm16) .BYTE $04, $84, $63 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $0E, $09, $00 ; btrue rel imm16 .BYTE $F0 ; push0 .BYTE $F6, $0C ; push16 imm8 .BYTE $0A, $B6, $62 ; pop16 (imm16+ ; pop16*2) --^-- The value at address $6384 is being compared against the value at address $6100. Tracing the write to $6100 shows the checksum routine being used again. It's the checksum of the second disk protection check that we patched. We've found the twelfth protection routine. We search the disk for the old checksum, and we encounter a funny(*) coincidence. Along with the checksum in the table of other checksums that we replace, we find this: (*) not guaranteed, actual humor may vary --v-- 4C CB 9F JMP $9FCB 4C 93 B7 JMP $B892 4C F6 82 JMP $82DB 4C B7 0B JMP $B80F 4C C1 0C JMP $0CC1 --^-- On first glance, this looks like an ordinary looking jump table, but the jump table also happens to have some of the same values as the checksum we're looking for. Coincidence? Probably. According to a track/sector map (thanks Copy II Plus), it's in an unallocated sector on the disk (track $0C sector $0E), so it's probably safe to ignore. Heh. Backtracking, we find this p-code: --v-- .BYTE $04, $A1, $61 ; push16 (imm16) .BYTE $04, $2B, $62 ; push16 (imm16) .BYTE $F6, $08 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $C8 ; pop8 (stk16) .BYTE $04, $9F, $61 ; push16 (imm16) .BYTE $04, $2B, $62 ; push16 (imm16) .BYTE $F6, $07 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $C8 ; pop8 (stk16) .BYTE $04, $00, $61 ; push16 (imm16) .BYTE $04, $84, $63 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $0E, $09, $00 ; bfalse rel imm16 --^-- And the value at $622B points to the start of the jump table in memory *that someone loaded*. And the add of #$08 in the first case, and #$07 in the second case, points to the $82DB, which is then fetched and stored. And that $82DB happens to be one of the checksums of interest. And there are no coincidences. We've found the thirteenth protection routine. We change that checksum, and try again. After some time, we hit our second timer again: --v-- .BYTE $F6, $32 ; push16 imm8 .BYTE $F6, $45 ; push16 imm8 .BYTE $AC, $50, $63 ; for loop .BYTE $04, $AF, $61 ; push16 (imm16) .BYTE $04, $50, $63 ; push16 (imm16) .BYTE $24 ; add16 stk, stk .BYTE $5C ; push00xx (stk16) .BYTE $F0 ; push0 .BYTE $2E ; cmpne16 stk, stk .BYTE $0E, $18, $00 ; btrue rel imm16 --^-- ...but with a different memory address. Naturally. Tracing the write to that address reveals this code on track $1D sector $0B, from the file named "E": --v-- .BYTE $12, $27, $00 ; jsr rel imm16 .BYTE $04, $AF, $61 ; push16 (imm16) .BYTE $F6, $34 ; push16 imm8 .BYTE $24 ; add16 stk, stk .BYTE $F6, $FF ; push16 imm8 .BYTE $5C ; push00xx (stk16) .BYTE $C8 ; pop8 (stk16) --^-- As before, that JSR calls this code, before fetching the result from $00FF (in zero page) and storing it at the address that triggered the timer: --v-- .BYTE $F6, $0D ; push16 imm8 .BYTE $08, $4A, $63 ; pop16 (imm16) .BYTE $04, $41, $62 ; push16 (imm16) .BYTE $08, $4C, $63 ; pop16 (imm16) .BYTE $12, $39, $EF ; jsr rel imm16 .BYTE $F6, $0E ; push16 imm8 .BYTE $08, $4A, $63 ; pop16 (imm16) .BYTE $04, $43, $62 ; push16 (imm16) .BYTE $08, $4C, $63 ; pop16 (imm16) .BYTE $12, $2B, $EF ; jsr rel imm16 .BYTE $04, $91, $61 ; push16 (imm16) .BYTE $04, $80, $63 ; push16 (imm16) .BYTE $C8 ; pop8 (stk16) .BYTE $04, $D7, $61 ; push16 (imm16) .BYTE $9A ; jsr16 (stk16) .BYTE $B6 ; rts --^-- The jsr16 is the interesting one. Once again, it goes to native code which looks like this: --v-- E251 AD 14 E1 LDA $E114 E254 20 98 E3 JSR $E398 E257 AD 14 E1 LDA $E114 E25A 20 AA E2 JSR $E2AA E25D 20 8D E2 JSR $E28D E260 90 0B BCC $E26D E262 CE F6 E2 DEC $E2F6 E265 30 0A BMI $E271 E267 20 35 E3 JSR $E335 E26A 4C 51 E2 JMP $E251 E26D A9 00 LDA #$00 E26F F0 02 BEQ $E273 E271 A9 FF LDA #$FF E273 48 PHA --^-- This code checks in a loop for the special track, then returns success or failure. Yes, it's the same as the one on side A. We've found the fourteenth protection routine. It also seems like a simple change. There's just one thing wrong, though: searching the disk doesn't find that code. The reason is that it's all byte- swapped. And relocated. LIKE YOU NEVER WOULD HAVE GUESSED IF I HADN'T TOLD YOU, RIGHT? Track $0B sector $0D, from the file named "DHR1": --v-- ; #$61 becomes #$E2 E26B 4C A9 61 JMP $61A9 E26E F0 00 BEQ $E270 E270 A9 02 LDA #$02 E272 48 PHA E273 FF ??? --^-- Eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeew. OK, replacing that #$FF should fix it. But of course it doesn't. Our first timer triggers yet again with a hit on index four in the array. Tracing backwards, we find this code on track $1D sector $0E, from the file named "E": --v-- .BYTE $04, $00, $61 ; push16 (imm16) .BYTE $04, $7E, $61 ; push16 (imm16) .BYTE $2C ; cmpeq16 stk, stk .BYTE $0E, $09, $00 ; btrue rel imm16 .BYTE $F0 ; push0 .BYTE $F6, $04 ; push16 imm8 .BYTE $0A, $B6, $62 ; pop16 (imm16+ ; pop16*2) --^-- The value at address $617E is being compared against the value at address $6100. Tracing the write to $6100 shows a new checksum routine being used. It's native code this time, from track $0B sector $05, from the file named "DHR1". The code of it looks like this: --v-- E9B0 18 CLC E9B1 BD 80 E6 LDA $E680,X E9B4 6D 7E E6 ADC $E67E,X E9B7 9D 7E E6 STA $E67E,X E9BA BD 81 E6 LDA $E681,X E9BD 6D 7F E6 ADC $E67F,X E9C0 9D 7F E6 STA $E67F,X --^-- It's another checksum of the fifth disk protection check that we patched. We've found the [counts furiously] FIFTEENTH AND FINAL PROTECTION ROUTINE. Quod erat liberandum. ~ Acknowledgments Thanks to 4am for editing and reviewing drafts of this write-up. --------------------------------------- docs by qkumba No. 1392 ------------------EOF------------------