Simplifying "Leisure Suit Larry 1"'s Age Protection
I’m continuing my series of posts where I simplify the copy protection schemes on my old Sierra games. This entry is different in two ways. First, this one is an AGI game: the original Leisure Suit Larry. Second, this isn’t a copy-protection, but rather an age-protection. The game half-heartedly attempts to make you prove you are old enough to play.
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.
A patch of my final solution is provided at the end of the article.
Getting Started
As this is an AGI game, I’m using my own disassembler. By the way, in that repository is a Go version and a Clojure version in different branches. Soon there will be a Java version, because I’m a little crazy.
Anyway, I am looking for the “How old are you?” quiz that the game gives the player. I want it to issue questions and tell you if you are right or wrong, just like it always has… except I want it to let you through to the game no matter what happens.
So, to get started, I extracted all the logic scrips. Searching for
strings revealed that the quiz appears to be run in logic.006.
The Protection Scheme
The questions themselves appear to live in logic.002 and logic.003, but
are called from logic.006. Here’s the relevant part, where it
grades your answer, in cleaned-up pseudocode:
IF (answer was correct) {
say "Correct";
v95 = 5;
v66 = 8;
} else if(this is the first wrong answer) {
say "You blew that one"
remember the loss (in flag 200);
v95 = 2;
v94--;
} else {
say "You are a kid!";
quit;
}
… and here’s the actual disassembly:
(N.B.: if you are accustomed to the SCI decompilations from my last few posts, these will look extremely primitive. That’s partly because the AGI interpreter is much more primitive, and partly because this is more of a disassembly than a decompilation).
IF( ;; FF
OR( ;; FC
equalv(%v92, %v93) ;; 025C5D
NOT( ;; FD
greatern(%v93, 0) ;; 055D00
) ;; (FD)
) ;; FC
) { ;; FF
set(%f15) ;; 0C0F
;; flg 15 = <'print'/'print_at'>
print(%m8) ;; 6508
;; msg 8 = <Correct.>
assignn(%v95, 5) ;; 035F05
assignn(%v66, 8) ;; 034208
} else {
IF( ;; FF
NOT( ;; FD
isset(%f200) ;; 07C8
) ;; (FD)
) { ;; FF
assignn(%v95, 2) ;; 035F02
print(%m5) ;; 6505
;; msg 5 = <Oops. You blew that one! ... >
set(%f200) ;; 0CC8
decrement(%v94) ;; 025E
} else {
print(%m6) ;; 6506
;; msg 6 = <You're a kid!! ...>
quit(1) ;; 8601
}
}
So immediately I can see the most important thing is not to set flag 200.
That may even be enough. There are differences in v95, v66, and
v94 between the right and wrong paths, too, which I can smooth over if
I need to.
Attempt One
So, let’s just avoid setting flag 200. Setting a flag is opcode 0x0C, while
resetting a flag is 0x0D. We’ll just reset the flag instead of setting it.
I find the opcodes above in VOL.1, and adjust it:

… and it kind-of works, but it keeps asking question-after-question as you get them wrong.
Attempt Two
So much for the minimal adjustment! Now let’s change all the variables so they match the correct-answer path. Of course it would be easier to just force the game to think all the answers are correct, but I’d rather get the right feedback from the game as I go.
So, this time I set v66 and v95 just like the correct-answer path.
This byte sequence is 1-byte shorter than it was before, so I pull the
FE 04 unconditional jump over one and update the distance to 05.

… and it works! I can get as many wrong as I want, and it just keeps giving me credit despite warning me not to miss another one.
Patch
Unfortunately AGI games don’t look for out-of-band patches like SCI games
do. So, I had to patch VOL.1 directly. Here is an IPS patch file
for it: vol1.ips. There is IPS patching
software all over the internet (like here).
Alternatively, you can just use a hex-editor and patch the file as shown in the screenhot above. Or you can just concentrate and answer the questions correctly!