Logo Pending


On SCI Windows – Part 2 – BorderWindow

A popular style of window seen in SCI1 games like Police Quest and Space Quest is the BorderWindow. By default it’s thick and gray, but it can really have any thickness and set of five colors you want.

The default looks something like this. Your exact colors may vary as I use a custom palette instead of the standard SCI1 palette, and Space Quest 5 has somewhat tinted grays.
If one were to edit the BorderWindow script a little bit to enable title bars, and did it in a naive way, they’d get something like this. So let’s not do that.
The bevelWid and shadowWid properties can of course be changed on a per-window basis…
…as can the back and four edge colors, topBordColor et al.

But how does this work exactly? Let’s take the actual BorderWindow script code and walk through it.

(class BorderWindow of SysWindow
  (properties
    ; All other properties inherited from SysWindow
    back 5
    topBordColor 7
    lftBordColor 6
    rgtBordColor 4
    botBordColor 3
    bevelWid 3
    shadowWid 2
  )
 
  (method (dispose)
    (super dispose:)
    (SetPort 0)
  )
 
  (method (show)
    (Graph grUPDATE_BOX top left bottom right VISUAL)
  )
 
  (method (open &tmp oldPort screens)
    (SetPort 0) ; Use the entire screen.
 
    ; If we have a priority set, render to that screen too.
    (= screens VISUAL)
    (if (!= priority -1) (= screens (| screens PRIORITY)))
 
    ; Make some room in the "last seen" rect to fit the border.
    (= lsTop (- top bevelWid))
    (= lsLeft (- left bevelWid))
    (= lsRight (+ right bevelWid shadowWid))
    (= lsBottom (+ bottom bevelWid shadowWid))
 
    (= type 128) ; We are custom, as described in the
    ; previous post.
 
    (super open:) ; Let a SysWindow open itself.
    ; This actually just calls the NewWindow kernel call
    ; and saves the returned handle to our window property.
 
    (drawWindow top left bottom right back
      topBordColor lftBordColor botBordColor rgtBordColor
      bevelWid shadowWid
      priority screens
    )
 
    (= oldPort (GetPort)) ; Remember our current port.
    (SetPort 0) ; Use the whole screen again.
    ; Show what we have wrought.
    (Graph grUPDATE_BOX lsTop lsLeft lsBottom lsRight VISUAL)
    (SetPort oldPort)
  )
)

That’s not so bad. The meat of the dish is in drawWindow, obviously:

(procedure (drawWindow t l b r theColor topColor leftColor bottomColor rightColor theBevelWid theShadowWid thePri theMaps &tmp savePort i)
  ; Again, we remember the current port and use the whole screen.
  (= savePort (GetPort))
  (SetPort 0)
 
  ; Fill in the window background.
  (Graph grFILL_BOX t l (+ b 1) (+ r 1) theMaps theColor thePri)
 
  ; Extend our rect to include the bevel.
  (-= t theBevelWid)
  (-= l theBevelWid)
  (+= r theBevelWid)
  (+= b theBevelWid)
 
  ; Draw the top and bottom bevels as simple boxes.
  (Graph grFILL_BOX
    t l (+ t theBevelWid) r
    theMaps topColor thePri
  )
  (Graph grFILL_BOX
    (- b theBevelWid) l b r
    theMaps bottomColor thePri
  )
 
  ; Draw the left and right bevels line by line.
  (for ((= i 0)) (< i theBevelWid) ((++ i))
    (Graph grDRAW_LINE
      (+ t i) (+ l i) (- b (+ i 1)) (+ l i)
      leftColor thePri -1
    )
    (Graph grDRAW_LINE
      (+ t i) (- r (+ i 1)) (- b (+ i 1)) (- r (+ i 1))
      rightColor thePri -1
    )
  )
 
  ; Draw the shadow last. Unlike SysWindows,
  ; these are done as two boxes.
  (if theShadowWid
    (Graph grFILL_BOX
      (+ t theShadowWid) r
      (+ b theShadowWid) (+ r theShadowWid)
      theMaps 0 thePri
    )
    (Graph grFILL_BOX
      b (+ l theShadowWid)
      (+ b theShadowWid) r
      theMaps 0 thePri
    )
  )
 
  (SetPort savePort)
)

To visualize, a BorderWindow (sans shadow) renders like this:

Next time, we take a step back and look at King’s Quest.

[ , ] 1 Comment on On SCI Windows – Part 2 – BorderWindow

On SCI Windows – Part 1 – SysWindow

Welcome to the first of a series of posts about window styles in SCI games. In this first entry I’ll go over the most basic of window styles available, the kernel-drawn one.

A regular old SCI window is drawn by the kernel’s RDrawWindow function. It can draw or not draw various parts. The type property that you can set on a SysWindow object yields the following possibilities:

nwNORMAL (stdWindow for those of us with the original source) looks like exactly that. A filled frame with a shadow. There’s nothing particularly awesome about these.
nwTRANSPARENT (wNoSave) windows forego the filling-in, revealing that the drop shadow isn’t two lines but a full rectangle. As the proper name implies, this skips taking a little screenshot of what’s underneath so when the window is closed things can be restored quickly.
nwNOFRAME (wNoBorder) windows only have the fill.
nwTITLE (wTitled) windows extend upwards a little bit to make room for a title bar. If the title property is zero, the title bar is left empty. If it’s not, it better point to a valid string.
Besides these basic styles, you can also combine them. Surprising or not, there is a proper window in this image, with a port, location, pen color and everything.
Some combinations are a little silly to look at…
…while others are simply useless. If there’s no frame, there’s no title bar!
The fill is of course not always white (assuming it’s applied in the first place) — setting the back property to the desired color palette index handles the fill, and the color property likewise affects the contents, as demonstrated in Leisure Suit Larry 3.

One last style bit is 128/0x80 wCustom, which has no SCI Studio/Companion counterpart constant and makes the kernel not draw anything at all. This is specifically for custom-drawn window frames, as we’ll cover in the next part.

The logic for RDrawWindow goes a little like this:

  1. If we don’t have wNoSave set, save the underbits.
  2. If we don’t have wCustom set
    1. If we don’t have wNoBorder set
      1. Draw the frame and drop shadow. We’ve already raised the roof in RNewWindow.
      2. If we have wTitle set, draw the bar and (if nonzero), the title text. We now have something like style 1 or 5.
    2. Shrink the window rectangle a bit.
    3. If we don’t have wNoSave set, draw the window interior. Basically, do style 2.
  3. Show what we have wrought.¹

And that’s all there is to know about how a SysWindow is drawn to the screen.

¹: actual comment at this step.

[ ] 1 Comment on On SCI Windows – Part 1 – SysWindow

AGI versus SCI – A Comparison

AGI, which Sierra used up until around the release of King’s Quest IV – The Perils of Rosella, was a strictly linear scripting language with a fairly simple bytecode. Much simpler than SCI’s object-oriented virtual machine. But how do they compare?

Inspired by this one particular book that’s ostensibly about SCI but contains only AGI snippets from what I’ve seen, here’s the first playable room in King’s Quest IV, and how it’s initialized. I’ve left out a bunch of stuff for clarity. Because it’s so simple, AGI bytecode is pretty easy to decompile:

if (justEntered)
{
  set.horizon(84);
 
  if (!nightTime) { v152 = 1; }
  else { v152 = 101; }
  load.pic(v152);
  draw.pic(v152);
  discard.pic(v152);
 
  //Place Ego according to the previous room
  if (lastRoom == 1) { position(ego, 141, 82); }
  if (lastRoom == 2) { position(ego, 107, 82); }
  if (lastRoom == 9) { position(ego, 96, 82); }
  if (lastRoom == 10) { position(ego, 80, 82); }
  if ((lastRoom == 11 || lastRoom == 15)) { position(ego, 70, 82); }
  if ((lastRoom == 12 || lastRoom == 14)) { position(ego, 60, 82); }
 
  //Add some waves in the water.
  animate.obj(o3);
  load.view(55);
  set.view(o3, 55);
  set.loop(o3, 4);
  set.priority(o3, 5);
  ignore.objs(o3);
  cycle.time(o3, 3);
  position(o3, 64, 152);
  draw(o3);
 
  draw(ego);
  show.pic();
}

Hmm. Compare that to its SCI equivalent. SCI bytecode is much harder to decompile. You can disassemble it, but until SCI Companion came out it wasn’t possible to decompile it. Still here we are:

(instance Room1 of Room
  (properties
    picture 1
    north 25
    south 7
    west 31
    east 2
    horizon 100
  )
 
  (method (init)
    (if gNightTime (= picture 101))
    (super init:)
    (self setRegions: 503 501 504 506)
    (wave1
      isExtra: 1
      view: 665
      loop: 0
      cel: 0
      posn: 203 76
      setPri: 0
      ignoreActors:
      cycleSpeed: 3
      init:
    )
    ; Other waves left out for clarity
    (waves add: wave1 wave2 wave3)
    (wave1 setScript: waveActions)
 
    ; This part is simplified significantly.
    (switch gPreviousRoom
      (south (gEgo posn: 225 188))
      (north (gEgo x: 225 y: (+ horizon 1)))
      (0 (gEgo x: 220 y: 135))
      (east (gEgo x: 318)) ; Y stays the same.
    )
    (gEgo init:)
  )
)

You might notice that there’s no equivalent to draw.pic and discard.pic and such. The Room class handles that by itself the moment Room1 calls (super init:).

[ , , , ] Leave a Comment

Going in-depth on SCI map files

One thing that stood out to me when I look at my collection of Sierra SCI games is that basically all the 16-color and early 256-color games (that is, SCI0, SCI01, SCI1) have multiple resource volumes, but the SCI11 games do not. Not because a lot of them came on CD either, the diskette versions, once installed, had a single resource volume too.

Why do you suppose that is? Why do the diskettes for SCI11 games contain a single resource.000 file that’s been split across them all for the install script to merge back together on the hard drive?

Because the map format doesn’t allow it.

The resource.map file specifies which resources can be found on which disks. For SCI0, it’s a list of six-byte entries. The first two encode the type (upper five bits) and number (the lower 11 bits), the next four have the volume number (upper six) and absolute offset in the volume (the rest). If a resource appears on multiple disks, it’s listed once for every appearance. In SCI01, there are only four bits for the volume number, trading amount for space.

In the later versions, the map file starts off with a list of offsets for each type listed in the map. With this list and a contract that the map entries are sorted by type, the interpreter can look up a given type of resource much faster. Since we already know that this part of the list only contains resources of a given type, we can use the full 16 bit range for the numbers. In SCI1, the next four bytes are just like in SCI01. In SCI11 however there’s only three, and there’s no volume number. Then in SCI2 it’s a straight-up plain 32 bit offset value.

The trick with SCI11 having only a 24 bit range for its offsets is that the value is shifted. They must be aligned on a two byte boundary so that the offset range is effectively doubled again.

As a practical example, let’s look at The Dating Pool. If I open its resource.map in my favorite hex editor and try to look up the second view, it’d go like this.

The first few bytes are 80 2B 00 81 EE 00 82 BB 01. We know the format, so this means that views (type 80) start at offset 002B and background pictures (81) start at offset 00EE, which means the views end there. Skipping ahead to the given offset, we see this: 00 00 00 00 00 05 00 02 26 00. Obviously the first resource (view 0) is at offset zero. Splitting this up into handy chunks, 0000 000000 0005 002602, we see that view 5 (which is the second one in the game data) is at offset 2602 << 1 = 4C04. Open up resource.000 and go there, and the first thing we see is confirmation: 80 05 00 36 44 36 44 00 00. View number 5, 4436 bytes in size both unpacked and packed, no compression, followed by the actual view data. Then there’s a single null byte for padding, and view 10 begins.

[ ] Leave a Comment

Animation Shop

December 23rd last year, I was working on some example material for my seqmaker tool, a converter/player that turns image sequences into SEQ files that SCI11 can play. The DOS versions of King’s Quest 6 and Gabriel Knight 1 used them extensively. At first I wanted to use a GIF sequence from Prince of Persia, the original one, because it has very few colors (32) and the only animated elements in that sequence are the Prince and a touchplate.

The only tool I had available to me to manipulate the GIF was ffmpeg. It wasn’t very helpful as after I’d made the SEQ file and watched it in examine mode, which blanks out everything outside of the changed region, I found that there was very subtle dithering.

One would expect, when the Prince starts to turn around, that he’d be the only thing stored, much like this:

“No. Fuck you and the horse you dithered in on.”

This is simply not acceptable!

To think I’d already spent too long manually undithering the solid black background! So in the end I went with the first shot from Hotel Mario instead. Worked out great.

Now, years ago I used to have a program called Jasc Animation Shop. By the creators of Paint Shop Pro, which I use to this day. The old 8.0 version from Jasc, before Corel bent it over. Importantly, Animation Shop was meant for GIFs, but also supported AVI and could import from sequences. Even more importantlier, it could export to GIF with full quality control. Or at least full enough to ensure it wouldn’t add dither noise. Today I found a copy and tried the thing again for a laugh.

Beautiful.

[ ] Leave a Comment

What even is an adventure game?

That’s a question SCI doesn’t even bother asking.

Weird, I know, considering it’s an adventure game engine.

Or is it? Turns out it’s really not. The “adventure game” part is almost entirely a matter of scripts.

In SCI proper, there is no concept of a player, of a room, of basically anything concrete. The engine only cares about a few data types like List, for which it provides function calls to use them, that the Cast list’s contents are View objects in a duck-typing sense, and that this list is global variable #5. Also, script zero export zero is the starting point. From there, you’re mostly on your own.

A room in SCI is just a particular kind of object that sets up the things you can find inside, handles room-specific inputs, and… that’s basically it. The SCI engine doesn’t even know the difference between an abstract View (some possibly-animated non-background element), an atmospheric effect, an NPC, or the player character. Note that all of those things are still View, in a class-inheritance sense.

The biggest blow to the idea that SCI is an adventure game engine has to be the board games though. Between Jones in the Fast Lane and the Hoyle series, I don’t think anyone could claim otherwise for long.

Contrast that with a certain other engine or two.

[ ] Leave a Comment

On fonts

SCI, being rooted in MS-DOS and from a time before Unicode (fun fact, the first draft proposal dates back to 1988, when King’s Quest 4 came out as the first SCI game), SCI uses an 8-bit string format. That is, each character in a string is one byte, and that’s all it can be. Making strings one of very few standard data types in SCI that aren’t 16-bits and requiring a dedicated kernel call to manipulate (as seen in KQ4 Copy Protection) but that’s not the point here.

American releases of SCI games would normally have font resources ranging only up to 128 characters, with the Sierra logo at 0x01 and ~ at 0x7E. Only caring about newlines, all other characters are considered printable. European releases would include usually not 256 but 226 characters, up to ß at 0xE1, basically copying code page 437 but leaving out the graphical elements among others. This means, of course, that a Russian translation of such a game would require another custom font copying code page 866 instead.

And then there’s the whole thing where SCI Companion uses the Win-1252 code page (it’s not exactly a Unicode application) which makes translated games look pretty wild:

Ich glaube Dir gern, daá Du das tun m”chtest!

That doesn’t look quite right. That’s supposed to be “Ich glaube Dir gern, daß Du das tun möchtest!” And indeed, comparing things between DOS-437 and Win-1252, we see that á and ß are both encoded in the same byte value.

That’s the kind of bullshit Unicode was made for, isn’t it?

So what I did for my SCI11+ project, of which one version is used in The Dating Pool, is to add optional basic Unicode support, so you can write text data in UTF-8 and not have to worry about things all that much. There are however two major problems with this idea. One of them is that SCI Companion is not a Unicode-aware program, so you can’t use that to write the text data. That’s easily solved with external resource editors that are. The second is more insidious — the fonts.

What I found out about SCI font resources is this: their header fields are way too wide.

typedef struct
{
  word lowChar;
  word highChar;
  word pointSize;
  word charRecs[0];
} Font;

lowChar is always zero, but the interpreter does acknowledge it. highChar is, as discussed above, always 128, 226, or 256. The fact that it’s exclusive is basically the only reason to have it not be a char-type value.

.if bx < es:[si].highChar && bx >= es:[si].lowChar

See? Exclusive. But the takeaway here is obvious. SCI font files can contain up to 65,535 characters. That’s enough to cover the Basic Multilingual Plane. As such, I’ve added handling of double- and triple-byte UTF-8 sequences to SCI11+. I’ve tested it, too:

Switching to another, non-extended font, I expected to see "Tend ", and that’s exactly what I got. The routine I linked to would decode an ō and dutifully pass it on down to StdChar, which would see that 0x14D is way higher than 226 and simply draw a blank.

(Now, between the first draft of this post and its publication, I’ve further enhanced this system to not decode anything if the font has fewer than 256 characters, falling back to code page 437 or whatever, just not doing anything special.)

That leaves one last issue, which is mostly a matter of wasted space. I like my quotation marks to be proper curly, and in Win-1252 as The Dating Pool uses (because why shouldn’t it?) this is easy — just draw a  and in the font at 0x93 and 0x94,  and be done with it. But in Unicode, these two characters are part of the General Punctuation block, which starts all the way at U+2000. That would mean defining up to that many dummy characters. A two-byte pointer, two size bytes, and a single byte with at best one bit set per character.

That’s bullshit.

As such, I’d propose to cheat like hell and move the General Punctuation block so it covers the much earlier Combining Diacritical Marks block. It’d be way too much of a nightmare to support those. So while measuring and drawing, detect if you’re in the 0x2000 to 0x206F range, subtract 0x2000, add 0x300, and use that character instead. Or have the custom resource tools  that we’d need anyway do it.

(Again, just before publication, I came up with an idea to have a new font generation tool that takes a bitmap of the font and converts it. The trick for space-saving is that it would recognize graphics it’d already processed and simply place a pointer to the first one. Instead of five bytes per dummy, it’d use only two for all but the first. Savings!)

(Update: I made the thing.)

At any rate, your input is appreciated.

Except for yours, Covarr.

[ , ] Leave a Comment

Musings on iMuse

One of the things I think LucasArts got Sierra beat in with regards to their old point-and-click adventure games has to be the music. I don’t mean that the music itself is better, but the underlying technology.

In SCI, a given music track can have cue points and a loop. These cue points can increase or set a value visible to the game engine, and let the game time things accordingly. Notable examples off the top of my head include the singalong text and images in Freddy Pharkas Frontier Pharmacist, the selection highlight following the beat in Quest For Glory, the singalong in Leisure Suit Larry 6… yeah.

But that’s just one way. The song can nudge the game, and that’s about it. A harmless bit of Mickey Mousing at best.

iMuse, on the other hand? The DirectMusic of its day. Remember DirectMusic? Me neither. Anyway, an iMuse song (near as I understand it) has queued triggers and a sense of beat, allowing the game to say “hey, switch to the cartographer’s version of Woodtick” and the song would wait for the current beat to finish, play a little flourish, and seamlessly transition into a different song. You could cancel them too, if you were fast enough. And let’s not get into the most triumphant example I’ve heard so far, X-Wing/Tie Fighter.

I figure if you give an SCI track beat markers, preload a fill riff, and have a script listen for requests, you might be able to approximate the basics. If you’re playing variations of the same song that only differ in instruments, you might be able to mute specific tracks, or send raw “Program Change” messages if you’re really adventurous…

But yeah. That’s why you can have proper MIDI dumps of SCI games, but you can’t quite hack it with most SCUMM games.

[ , ] Leave a Comment

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.

[ , , , ] 7 Comments on King’s Quest 4 Copy Protection

Palette Cycling in Space Quest 4

It’s a followup.

Here’s Roger falling through the chronostream in the introduction:

And here’s the EGA release’s take on that shot:

I immediately thought, of course, the EGA release wouldn’t support palette cycling effects. That’s the official EGA release, with an ega320.drvnot the later ones with ega640.drv. Totally different.

But what if I were to make SQ4 VGA use ega640.drv? What would happen? Would the background remain static?

Keeping the answer under the fold for all you epileptic viewers out there.

Continue reading “Palette Cycling in Space Quest 4”

[ , , ] Leave a Comment