Simplifying "Police Quest 2"'s Copy Protection
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:

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…

… and it works!

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.