Logo Pending

Little Black Book – Larry 2 Copy Protection

It’s been a while now since I last explained a Sierra game’s copy protection, so why not go at it again?

Compared to KQ4, LSL2 has only a few different options, and it involves no math.

  phoneNum     ; The correct answer.
  [input 6]    ; Our guess. 11 characters max, but 16-bit values so 12/2=6
  ; The photo parts are put in hunk space instead of heap or something.
(instance rm10 of Rm
    picture 10
  (method (init &tmp dress face hair earring)
    ; Preload the photo and logo parts
    (Load rsVIEW 60)
    (super init:)
    ; For each part of the logo, create a new view in hunk space, and addToPic it.
    ; Adding a view to the pic causes it to be disposed, so in the end none of the logo parts take space.
    ((View new:) view: 60 loop: 4 cel: 0 posn:  72  52 setPri: 1 addToPic:) ; Logo
    ((View new:) view: 60 loop: 4 cel: 1 posn: 122  36 setPri: 0 addToPic:) ; S
    ((View new:) view: 60 loop: 4 cel: 2 posn: 141  35 setPri: 0 addToPic:) ; I
    ((View new:) view: 60 loop: 4 cel: 3 posn: 161  35 setPri: 0 addToPic:) ; E
    ((View new:) view: 60 loop: 4 cel: 4 posn: 190  35 setPri: 0 addToPic:) ; R
    ((View new:) view: 60 loop: 4 cel: 4 posn: 221  35 setPri: 0 addToPic:) ; R
    ((View new:) view: 60 loop: 4 cel: 5 posn: 252  35 setPri: 0 addToPic:) ; A
    ((View new:) view: 60 loop: 5 cel: 0 posn:  13 113 setPri: 7 addToPic:) ; Left girl
    ((View new:) view: 60 loop: 6 cel: 0 posn: 306 113 setPri: 7 addToPic:) ; Right girl
    ; Now create views for the photo parts.
    ((= dressView   (View new:)) view: 60 loop: 0 cel: 0 setPri: 1 posn: 154 981 init:)
    ((= faceView    (View new:)) view: 60 loop: 1 cel: 5 setPri: 2 posn: 154 981 init:)
    ((= hairView    (View new:)) view: 60 loop: 2 cel: 1 setPri: 3 posn: 154 981 init:)
    ((= earringView (View new:)) view: 60 loop: 3 cel: 4 setPri: 4 posn: 154 981 init:)
    ; I left out most of the answers in the interest of fairplay and conciseness.
    (switch (Random 1 16)
      (1 (= dress 0) (= face 5) (= hair 1) (= earring 4) (= phoneNum "555-7448"))
      (2 (= dress 1) (= face 5) (= hair 2) (= earring 4) (= phoneNum "555-5968"))
      ; ...
      (15 (= dress 2) (= face 4) (= hair 0) (= earring 2) (= phoneNum "555-5633"))
      (16 (= dress 1) (= face 1) (= hair 3) (= earring 2) (= phoneNum "555-5834"))
    ; Now apply the traits chosen above.
    (dressView   posn: 154 81 setLoop: 0 setCel: dress)
    (faceView    posn: 154 81 setLoop: 1 setCel: face)
    (hairView    posn: 154 81 setLoop: 2 setCel: hair)
    (earringView posn: 154 81 setLoop: 3 setCel: earring)
    (Display "© 1988 by Sierra On-Line, Inc." dsCOORD 60 176 dsCOLOR 1 dsBACKGROUND 3)
  (method (doit)
    (Format @input "555-")
      "Please find this girl's picture in your little black book, then type her telephone number here:"
      #at -1 144
      #width 248
      #font gFont
      #edit @input 11 ; add a textbox, max length 11.
    ; Reminder: like in C, StrCmp returns zero if the inputs are equal, and zero is false. Hence the not.
    (if (not (StrCmp @input phoneNum))
      (gRoom newRoom: 90)
      (Print "Sorry, but you need to spend more time staring at beautiful women!
              In order to play this game, you must have the original documentation.
              If you've lost your little black book, please telephone Sierra's
              Customer Support Department at the number printed on your disks.")
      (= gQuit true)

I can think of a few ways to improve this, and in fact I already did in some places — this is not the original LSL2 copy protection! Can you think of more ways to improve this script?

[ , , , ] Leave a Comment

What do SQ5 and LSL5 have in common?

Things being typed out in their introduction.

In Space Quest 5, the introduction has a sequence where Roger Wilco dictates a captain’s log. The text appears word-for-word, though it’s pretty easy to modify the script code and make it character-by-character. But before I can explain the intricacies of that scene, I must ensure you know how a Scriptworks.

No, not a script file, a Script object. They are in essence state machines, with a state property, a changeState method, a cue method, and two timer properties, cycles and seconds. The changeState method is the beef of it all, containing the code for each state. The cue method simply takes the current state, adds one, then invokes changeState with that value. The doit method checks if either of the two timers is nonzero, decrements them accordingly, and invokes cue when time runs out. In general, you only need to implement your own changeState. Other objects may also cue a script. A good example would be waiting for an Actor to move to a particular position. They would know which Script is waiting for them and cue them at the appropriate time. Until then, the Script sits on its thumbs.

Knowing that, let’s take a look at the captain’s log:

; Earlier in the file...
  [logInput 200]
  [logWritten 200]
  ; Remember: these are 16-bit values, so 400 characters.
(instance sCaptainsLog of Script
  (method (changeState newState)
    (switch (= state newState)
        (Message msgGET 104 1 0 0 page @logInput) ; Grab the current page worth of text.
        (= length (StrLen @logInput)) ; Remember how long the line is.
        (= cursor 0) ; Reset our typing cursor
        (= cycles 1) ; Near-immediately continue -- we could self-cue though?
          ; Copy one character...
          (StrAt @logWritten cursor (StrAt @logInput cursor))
          ; ...and add a terminating zero so we don't write garbage.
          (StrAt @logWritten (++ cursor) 0)
          ; Stop copying if we find...
              (== (StrAt @logInput (- cursor 1)) 32) ; a space...
              (== (StrAt @logInput (- cursor 1)) 0) ; or the end of the text.
        ; Put what we've copied so far on screen.
        (Display @logWritten dsCOORD 40 10 dsCOLOR 254 dsFONT 1307 dsWIDTH 250)
        ; If we haven't reached the end of the text yet...
        (if (> length cursor)
          (-- state) ; rewind to state 0 so the next cue is for state 1 again!
          (= ticks 15)
        ; The rest doesn't matter here.

If one wished to write character-by-character, you’d just remove most of the repeat:

(StrAt @logWritten cursor (StrAt @logInput cursor))
(StrAt @logWritten (++ cursor) 0)
(Display @logWritten dsCOORD 40 10 dsCOLOR 254 dsFONT 1307 dsWIDTH 250)
(if (> length cursor)
  (-- state)
  (= ticks 15)

That was pretty easy, all things considered. I think the more interesting example to study would be the subtitle in Leisure Suit Larry 5 – Passionate Patti does Pittsburgh a Little Undercover Work.

First, we need a few local variables and, just to keep things orderly, a procedure:

  [subtitle 50] = "Passionate Patti does PittsbuA Little Undercover Work"
  ; Notice this: "Pittsbu" is character 22 to 29.
  [backspaces 12]
(procedure (typeAway &tmp [oneChar 2] char underbits)
  ; Grab the current character to type.
  (= char (StrAt @subtitle cursor))
  ; Put it in a string -- just that character and a terminating null.
  (Format @oneChar "%c" char)
  ; Draw it, preserving what was underneath.
  (= underbits (Display @oneChar dsCOORD typePos 160 dsCOLOR global128 dsWIDTH 7 dsALIGN alLEFT dsFONT global173 dsSAVEPIXELS))
  (if (and (< 21 cursor) (< cursor 29)) ; Are we typing "Pittsburgh"?
    (= [backspaces (- cursor 22)] underbits) ; Then put those underbits pointers in the backspaces array.
    (UnLoad 133 underbits) ; No need for these underbits, throw 'em away.
  (= typePos (+ typePos 7)) ; Advance our drawing cursor...
  (if (== 32 char) (= typePos (- typePos 2))) ; ...but make the space a little bit thinner.

Further down there’s a Script that handles the entire opening cartoon. I’ll skip the boilerplate and only show the relevant parts.

  (patti setMotion: MoveTo 335 140) ; This all happens while Patti walks past.
  (= cursor 0) ; Reset our type.
  (= typePos 3)
  (larry setScript: sLarryCartoon) ; Larry reacts on his own.
  (= cycles 1)
  (typeAway) ; Type ONE character.
  (typewriter play:) ; Make some noise...
  (if (< (++ cursor) 29) ; Increment the cursor -- if it's not at the 'u' in "Pittsbu" yet...
    (= cycles (Random 3 5)) ; ...randomly delay the next character...
    (-- state) ; ...and rewind as shown in SQ5.
    (= cycles 10) ; We've reached the 'u'. Wait and cue up state 16.
  ; Grab the right underbits from our array and restore it.
  (Display "" dsRESTORE [backspaces (- (-- cursor) 22)])
  ; Underbits include not only pixel data but also their bounds, so that's easy.
  (backSpace play:)
  (if (> cursor 22) ; Are we not yet back at the space before "Pittsbu"?
    (= cycles 4) ; Then rewind at four cycles a backspace.
    (-- state)
    (= typePos 153) ; Reset to where the 'P' was...
    (= cursor 29) ; ...and skip to the 'A' in "A Little"
    (= cycles 10)
  ; The rest is much the same as state 15...
  (typewriter play:)
  ; ...except for this check.
  (if (< (++ cursor) (StrLen @subtitle))
    (-- state)
    (= cycles (Random 3 5))
    (= cycles 10)
[ , , , ] Leave a Comment

Fanfics? Sure!

Letrune suggested via the new suggestion box that I could blog about fanfics. Well, I got a few Ranma ½ stories I guess? Let me just put up a page to quickly list and link them… there we go!

Anyway, the chronologically first one would be Ranmya (available on Archive of our Own and locally), where our hero is born and raised a girl, gets cursed to be a cat, then immediate and unwisely turns and tries to cure herself, resulting in a mixed curse. And then she hooks up with Nabiki.

I’d been sitting on the first version of this story, then unnamed, for several years before finally continuing, then rewriting the first bits for style points. Ranmya is relatively heavy on the sex compared to the others, and has a fair bit of Weird Shit™ going on especially in the dream chapter which features some lines in the conlang I developed for the Firrhna Project. Why? Because I can.


Next in line is Good and Pure (available on AO3, FFNet, and locally), which was inspired by a severe lack of Ranma/Kasumi fics. It starts out pretty much in a canon-compatible situation, with Ranma engaged to Akane, but quickly derails into a Ransumi story. Kasumi is a precious cinnamon bun, too good, too pure for this world.

The thing about pure things is that its all the sweeter to corrupt them.


There’s a one-shot about Shampoo too, I’m a Kittycat (AO3, FFNet, local), where the important change is that Shampoo is not a human who turns into a cat, but the other way around. During the competition, her instincts flare up and make her mess up the Kiss of Death.


My most recent work is yet another damn Ranma AU. I’m sorry, I should’ve title-cased and italicized that. (AO3, FFNet , local) The setting is easy enough to explain: there’s no turning back. Once you’re cursed and take on another form, that’s it. This story features Rankane shipping, a defiant lesbian tomboy and her equally defiant pet cat, no pandas, and when I get off my lazy ass and write, a very confused Ryōga.

[ , ] Leave a Comment