I’m continuing my series of posts where I simplify the copy protection schemes on my old SCI Sierra games. Today’s entry is: Police Quest 2.

As a reminder: I don’t really want to bypass the copy protection. I like seeing it; it’s a nostalgia thing. I just don’t want to have to get the answer correct to get to the other side of it.

The patch I wound up producing is at the bottom of this post.

Getting Started

As before, I used the excellent SCI Companion software, rather than write my own decompiler–though I will probably do that one day for fun.

To start, I searched the text resources of the game, as I find that’s usually a quick way to identify which scripts are relevant. Today, it turned out Text.701 has copy-protection info in it:

copy-protection text screenshot

You may recall that in the last post, the script number was also 701. Coding by cut-and-paste predates Stack Overflow for sure!

The Protection Scheme

In PQ2, a function GetTime provides the random number, and the code keeps the bottom 3 bits (8 options) in temp0:

(= temp0 (& (GetTime 1) $0007))

It then appears to actively clear out anything you might have typed or clicked. I’m just guessing by eyeballing the code:

(while ((= newEvent (Event new:)) type?)
  (newEvent dispose:)
)
(newEvent dispose:)

Next, it calls proc255_0 to display the dialog box to the user, and collects input into an array local0:

(proc255_0 701 0 82 701 0 temp0 41 @local0 20)
(= global4 1)
(localproc_0031)

I checked localproc_0031… it changes your input to uppercase. Very considerate!

Now, the check for the correct answer is a simple switch against hard-coded values:

(switch temp0
  (0
     (if (not (StrCmp @local0 {GRANANDEZ})) (= global4 0))
  )
   ... elided the rest ...
)

… as you can see, global4 is important here. When the user gets the right answer, it is set to 0 (the previous lines I quoted have a line setting global4 to 1).

Now, the next lines check global4 and take us down one of two paths:

(if global4
    (proc255_0 701 1)
 else
    (MenuBar draw:)
    (SL enable:)
    (gGame restart:)
)

So, we want to take that second path. Let’s just plow over the check and execute the winning path unconditionally. Here’s the relevant disassembly:

       code_01bc
  01bc:3a              toss 
  01bd:81 04           lag  
  01bf:30 000c         bnt code_01ce 
  01c2:7a              push2 
  01c3:38 02bd         pushi 2bd // $2bd sel_701
  01c6:78              push1 
  01c7:47 ff 00 04     calle ff procedure_0000 4 //  
  01cb:32 0017         jmp code_01e5 
        code_01ce
  01ce:39 53           pushi 53 // $53 draw
  01d0:76              push0 
  01d1:51 13           class MenuBar 
  01d3:4a 04           send 4 
  01d5:38 008a         pushi 8a // $8a enable
  01d8:76              push0 
  01d9:51 35           class SL 
  01db:4a 04           send 4 
  01dd:38 00f9         pushi f9 // $f9 restart
  01e0:76              push0 
  01e1:81 01           lag  
  01e3:4a 04           send 4 
        code_01e5
  01e5:48              ret 

Looking at this, it seems like maybe the most straightforward adjustment is to change the bnt instruction to an unconditional jmp. I can see that bnt is 30 and jmp is 32, and both appear to be followed by a relative jump amount. Let’s try it…

hex-edit screenshot

… and it works!

proof-it-works screenshot

Patch

Just put this script.701 patch as script.701 in your game directory, and it should work.

It’s nice that this change is specific to the copy protection script, because it gives some confidence that the overall game isn’t broken by the change I made. Also, none of the original game assets were modified. That’s the nice thing about the SCI engine… it looks for uncompressed overrides of the in-game scripts on the file-system. If you delete the patch file (or rename it), the game will resume checking your answer.