Logo Pending


King’s Quest 4 Copy Protection

King’s Quest 4 – The Perils of Rosella starts with a copy protection challenge right off the bat.

If you were playing the original 1988 version you could just enter the magic word “bobalu” and be done with it, but the 1989 version removed this.

It’s a pretty simple challenge-reply system, but the interesting bit is how your answers are considered. If someone were to somehow find the challenges they would also find the answers in the same order, but there’s a catch: the answers are hashed.

Very simply so, but they are. And of course the script code containing the challenges and answer hashes is compressed in the RESOURCE.001 file, and the whole thing is script code and nobody outside of Sierra could be expected to be able to read that stuff back in 1988. Sure, maybe some people could but still good luck figuring out how this worked. Even the backdoor phrase was compressed.

But now it’s 2018, nearly 2019, and I for one have made happy use of the tools now available to us, mostly to slake my own thirst for knowledge. So here’s how it works.

The copy protection script has several local variables: a random number from 1 to 79, the challenge text, the correct answer’s hash, a buffer for the user’s input, the hash for said input, and some work variables.

On startup, the random number is chosen. Then, in a big ol’ switch statement, the correct hash is decided on:

(switch (= randomPick (Random 1 79))
  (1 (= requestSum 431))
  (2 (= requestSum 521))
  (3 (= requestSum 535))
  ;...
  (79 (= requestSum 686))
)

In another big ol’ switch (instead of doing it at once?), the matching challenge is set up:

(switch randomPick
  (1 (= requestText "On page 2, what is the fourth word of the first sentence?"))
  (2 (= requestText "On page 2, what is the fourth word of the second paragraph?"))
  (3 (= requestText "On page 3, what is the fourth word in the first paragraph?"))
  ;...
  (79 (= requestText "In the section TIPS FOR NEW ADVENTURE PLAYERS, what is the eighth word in the first paragraph of tip #2 (STAY OUT OF DANGER)?"))
)

Incidentally, I said our guess would be stored in a buffer variable, that is an array in memory large enough to contain it, but I did not say any such thing about the challenge text. That’s because it’s stored as a pointer to the text, in the place it was loaded to as part of the script. From then on these challenges don’t mutate in any way. Our input can be literally anything.

Anyway, after displaying the challenge, we have our input in a buffer. This is where the magic happens:

(= i 0)
(while (< i (StrLen @userInput))
  (= ch (& (= ch (StrAt @userInput i)) $005f))
  (StrAt @userInput i ch)
  (= inputSum (+ inputSum ch))
  (++ i)
)

Iterating through the user’s input, we read the next line inside-out. Using the StrAt function we fetch the next character and store its value in our work variable. Then we use some binary magic on that same value to turn it into UPPERCASE, and assign that to our work var. Now, as I write this I feel like this can be simplified a little bit…

(= ch (& (StrAt @userInput i) $005f))

…Yeah, that seems nice. I don’t think it’d hurt functionality to do this. Anyway, the next line shows how SCI function and kernel calls can be variadic as all get out — given three arguments, StrAt will set the character on the given spot. In the third line, we add the character’s value to our running sum.

And that’s it! We can now compare our input to the expected answer, and either continue on to the title screen or display an error and quit.

But that’s not all there is to it. First, for some reason, the input is uppercased and then stored again, character by character. This is so the 1988 release can compare it against the magic backdoor word, which is also in uppercase. This seems like an awful waste when you could compare it against a number instead. Not to mention, the 1989 release doesn’t even have the backdoor and still does all this. (For the record, that would be 437.)

Second, this is such a simple method that there are guaranteed to be words with the same hashes. For example, “voice” and “licks” are both 374.

But yeah, that’s just about all there is to know about the copy protection in King’s Quest 4 – The Perils of Rosella.

Like
[ , , , ]

7 thoughts on “King’s Quest 4 Copy Protection

  1. Amazing. Did you ever manage to get it compiled? The Original Sierra Code that is? Can you give me the URL for your Github?

  2. Thanks for the reply. I love all this stuff. I had many questions about it many years ago and until now did not know what was going on. :)

    So how are you getting the variable names? Did you just define them yourself? I’ve decompiled it using your version of SCI but mine are different.

    BTW: Whats this file on your site? Is it what I think it is? The crown jewels :O

    SCI16.zip – The original template, zipped.

    1. Variable names aren’t kept when compiling, only properties, classes, and instances.

      What the decompiler can do is see that an as-yet-unnamed variable is being assigned a reference to an instance of a particular class and infer a name from that.

      SCI16.ZIP is indeed original Sierra code for the interpreter, some time before they switched to SCI2. My own game runs on a slightly altered version that’s on my Github.

  3. Hi there. I found your Logo pending info. Great stuff. I was just playing around with the KQ4 copy protection and getting frustrated as I could not work out why I cpould not find the answers in the scritps like with LSL2. I thought they must be encrypted or something. Then I read your page :) How did you extract the script so its readable? I tried SciStudio and SciCompanion but both seem to show garble?

    1. Thanks, nice to know someone enjoyed the fruits of my boredom. I decompile these scripts with SCI Companion, a copy I built myself from source. SCI Studio has no decompiler at all, so all you’d get is a disassembly which might as well be garbled.

Leave a Reply

Your email address will not be published. Required fields are marked *