Simplifying "King's Quest IV"'s Copy Protection
I had so much fun in my last post simplifying The Colonel’s Bequest that I thought I would do somthing similar for my other SCI games.
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 produced 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 decompiled all the game scripts, and searched through the text
for hints as to were the copy protection scheme was. In this case, none
of the text resources were helpful, because the script had all the strings
baked in. Fortunately, the script had a very obvious name: copyProtect.
It’s room 701.

The Protection Scheme
The copy protection scheme is set up as follows:
(switch (= local0 (Random 1 79))
(1 (= local2 431))
... elided the rest ...
)
(switch local0
(1
(= local1
{On page 2, what is the fourth word of the first sentence?}
)
)
... elided the rest ...
)
So, local0 has the random question’s id, local2 has a hash of the
correct answer, and local1 has the question string.
Let’s see how they are used…
First, it calls a procedure to put up the dialog box, and place the
user input into an array local3:
(proc255_0
(Format @global300 701 0 local1)
134
7
15
66
global23
140
290
82
@local3
30
)
Then, as an interesting aside: if global215 is set, the user can
just type a room number to jump to. At least, that’s what it looks
like to me. Debugging aid, I guess:
(if
(and global215 (= local33 (ReadNumber @local3)))
(TheMenuBar draw:)
(SL enable:)
(self newRoom: local33)
(return)
)
Next, we get to the hashing section. It looks like kernel_102 does
the heavy lifting, and the hash is accumulated in local35:
(= local33 0)
(while (< local33 (StrLen @local3))
(= local34
(& (= local34 (kernel_102 @local3 local33)) $005f)
)
(kernel_102 @local3 local33 local34)
(= local35 (+ local35 local34))
(++ local33)
)
That might be of interest, but I’m hoping I can just bypass the check itself rather than manipulating the hash. Here’s the check, which holds a fun surprise:
(cond
((not (StrCmp @local3 {BOBALU})) (global2 newRoom: 700))
((== local35 local2) (global2 newRoom: 700))
(else (proc255_0 701 1) (= global4 1))
)
Do you see the BOBALU string constant? It looks like that just lets
you through regardless of the hash. I tried it, and it works! I’m sure
people knew about this, but it was news to me. Ok, so then we check
the hash in the second line. The third line is the failure case.
Perhaps, let’s just change this entire cond expression into the
code for (global2 newRoom: 700). No check at all against what I
typed, then. Here’s the disassembly of that section:
code_0a0f
0a0f:7a push2
0a10:5b 02 03 lea 2 3
0a13:36 push
0a14:72 1776 lofsa $218d // BOBALU
0a17:36 push
0a18:43 49 04 callk StrCmp 4
0a1b:18 not
0a1c:30 000e bnt code_0a2d
0a1f:38 01bc pushi 1bc // $1bc newRoom
0a22:78 push1
0a23:38 02bc pushi 2bc // $2bc sel_700
0a26:81 02 lag
0a28:4a 06 send 6
0a2a:32 0025 jmp code_0a52
code_0a2d
0a2d:8b 23 lsl local35
0a2f:83 02 lal local2
0a31:1a eq?
0a32:30 000e bnt code_0a43
0a35:38 01bc pushi 1bc // $1bc newRoom
0a38:78 push1
0a39:38 02bc pushi 2bc // $2bc sel_700
0a3c:81 02 lag
0a3e:4a 06 send 6
0a40:32 000f jmp code_0a52
code_0a43
0a43:7a push2
0a44:38 02bd pushi 2bd // $2bd sel_701
0a47:78 push1
0a48:46 00ff 0000 04 calle ff procedure_0000 4 //
0a4e:35 01 ldi 1
0a50:a1 04 sag
code_0a52
0a52:48 ret
0a53:00 bnot
I’m just going to take the newRoom 700 part and follow it with a ret
instruction, and see what happens…

… and it works! I can type whatever:

The 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.