I’m continuing my series of posts where I patch old Sierra games to simplify their copy-protection tasks. Today, it’s the age quiz challenge to pick a raunchiness-level in LSL3. I expect this one to be a little different than the other SCI games (maybe closer to LSL1).

As a reminder: I don’t really want to bypass the age 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. Before long I spot the lists of questions in texts 141 to 160 or so. Also, it jumps out right away that the correct answer is encoded with the question:

text for a question set

You can see that a number precedes the questions, indicating which answer is correct. The code for the game obviously strips that off before displaying it.

Text 140 has the actual control logic, based on the texts:

text for room 140

So, next I’ll check out Script.140

The Protection Scheme

As with my LSL1 patch, I want the game to tell me if I get the questions right or wrong, but then give me credit anyway. So, that’s the angle I’ll be looking for as I inspect the code.

Right away, I know it will be slower going this time because the decompiler failed on a large part of this script. So, I’ll have to slog through plain disassembly instead. Just skimming through the file, I note a few things right away:

  • This code reads and/or writes to two different files, going by the names of calls like FOpen.
    • LARRY3.DRV appears to hold 3 pseudorandom numbers. Maybe their purpose is to keep from giving out the same questions twice in a row? I’m not sure.
    • RESOURCE.LL3 has a number in it, which might be related to the ultimate raunchiness level you attain. Maybe it will become clear during this investigation. (EDIT: Yep, Room 290 reads this file right after Room 140 writes it out, and stores the number in global124, which I can see is what the game tests for raunchiness level. Maybe the testers would keep a read-only file with the number they wanted to achieve in it? I don’t know.)
  • It looks like maybe local2 will ultimately hold your score… this code jumped out at me, since it keep comparing local2 to numbers and assigning the level strings based on them:
    pushi    11
    lsl      local2
    dup     
    dup     
    ldi      5
    eq?     
    bnt      code_05f4
    lofsa    {Totally Raunchiest}
    jmp      code_061e
code_05f4:
    dup     
    ldi      4
    eq?     
    bnt      code_0601
    lofsa    {Really Filthy}
    jmp      code_061e
code_0601:
    dup     
    ldi      3
    eq?     
    bnt      code_060e
    lofsa    {Pretty Dirty}
    ...etc...

So, with that clue, let’s see what else happens with local2… next up, they subtract 1 from it and write it to a file named RESOURCE.LL3. It looks like maybe they skip writing to the file if you had scored 0 (leading to 0-1 = 65535 in 16-bit unsigned arithmetic)? That’s interesting:

    lsl      local2
    ldi      1
    sub     
    sag      global124
    pushi    2
    lofsa    {RESOURCE.LL3}
    push    
    pushi    2
    callk    FOpen,  4
    sat      temp2
    push    
    ldi      65535
    ne?     
    bnt      code_068f
    pushi    4
    lea      @temp3
    push    
    pushi    140
    pushi    12
    lsg      global124
    callk    Format,  8
    pushi    2
    lst      temp2
    lea      @temp3
    push    
    callk    FPuts,  4
code_068f:
    pushi    1
    lst      temp2
    callk    FClose,  2

Still looking for local2 references, I find the part of the code which checks your answer. Being disassembly rather than a decompilation, it’s rather long, so I’ll summarize first:

if (local5 == local6) {
   // right answer
   say "Correct" (message 9)
   play "Correct" music
   increase local2   (right answer count)
   increase local3   (which picture to show)
} else {
   // wrong answer
   say "Wrong" (message 10)
   play "Wrong" music
   decrease local3 if it's not 0
}
show the new picture 
   (she has fewer clothes the higher local3 gets)

… and here’s the bytecode for all that:

    lsl      local5
    lal      local6
    eq?     
    bnt      code_055b
    pushi    2
    lsl      local5
    pushi    2
    call     localproc_0754,  4
    pushi    #number
    pushi    1
    pushi    140
    pushi    6
    pushi    1
    pushi    1
    pushi    42
    pushi    0
    lag      gTheMusic
    send     16
    pushi    8
    pushi    140
    pushi    9
    pushi    67
    pushi    190
    pushi    8
    pushi    25
    pushi    3
    pushi    88
    calle    proc255_0,  16
    +al      local2
    +al      local3
    jmp      code_0594
code_055b:
    pushi    2
    lsl      local5
    pushi    4
    call     localproc_0754,  4
    pushi    #number
    pushi    1
    pushi    141
    pushi    6
    pushi    1
    pushi    1
    pushi    42
    pushi    0
    lag      gTheMusic
    send     16
    pushi    8
    pushi    140
    pushi    10
    pushi    67
    pushi    190
    pushi    8
    pushi    25
    pushi    3
    pushi    88
    calle    proc255_0,  16
    lal      local3
    bnt      code_0594
    -al      local3
code_0594:
    pushi    #setCel
    pushi    1
    lsl      local3
    pushi    198
    pushi    0
    lofsa    aSuit
    send     10
    ldi      3
    aTop     seconds

Now to Patch It

So, having learned everything we need to know, I plan to patch that last code snippet such that:

  • it still tells me if I’m correct or not
  • the accounting in local2 and local3 will adjust as if I were correct every time

That means just changing the lal local3; bnt 0594; -al local3 sequence into a +al local2; +al local3 like the correct path had. I get SCI Companion to give me the raw disassembly with the bytecodes next to it, so I know what byte sequence to copy. And, thankfully, there is enough room to avoid changing the size of the overall code.

hex-edit screenshot

I had 3 bytes left to fill with some kind of no-op, so I tried 32 00 00 (an unconditional jump of 0 bytes)… and hoped for the best! ScummVM did not choke on it, and neither did DOSBox when running the actual Sierra interpreter, so I think I’m in the clear.

More importantly, it worked! I answered every question wrong, but the clothes kept coming off the picture on the left, and then it told me I could play at the most adult level:

game screenshot

The Patch

Just put this script.140 patch as script.140 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 answers.