Logo Pending


How to SCI – Old vs New

To make an SCI game today, you can just grab a copy of SCI Studio and use that to import graphics and music, do the scripting, text… basically everything but drawing bitmap-based backgrounds. You get a copy of the interpreter matching the template you chose, the system scripts are all set up for you, and you can just hit Compile, watch it work, and it’ll even automatically run the game for you in DOSBox, perhaps even starting you off in a specific room.

But dear lord do we have it easy nowadays.

In the old days, making an SCI game involved several separate utilities, many of them interface-less command line tools, and a particular network setup. That is, the tools expect to be invoked from a specific hard drive letter, as they are provided from one point of the network. There’s another where the system programmers keep the latest builds of the interpreter and system scripts, and the team for a given game has a batch script file to pull the latest into that game’s working directory. Writing the actual script code is roughly the same as it is now, but instead of a dedicated script editor they mostly used Brief. To test their changes, the programmers had to invoke SC, the Script Compiler. Given that Brief was apparently pretty extensible, this could probably be done from there.

While we mostly work directly on RESOURCE.### files, Sierra’s games were developed what I call loose-leaf style. Each type of resource was stored in its own folder, and a “wherefile” specified where each of them could be found — they were basically just RESOURCE.CFG by another name, really. And that name was literally WHERE. Turns out you can specify which configuration file you want the interpreter to use.

view=../view
sound=../sound
pic=../pic
font=../system
cursor=../system
videoDrv=/system/ega320.drv
soundDrv=/system/std.drv
kbdDrv=/system/ibmkbd.drv
joydrv=/system/joystick.drv

To make the game, they didn’t use makefiles. They used batch scripts that invoked SC and compiled the .sc source files to .scr files in the SCRIPT directory, and copied over the script resources from SYSTEM.

Given how relative paths work, running another particular batch file would run the interpreter from one directory while in another, from which point the paths given above can be considered valid. Since they didn’t have the “start at the room specified in this file” feature that SCI Companion’s template game adds, we get the game-specific debug modes that ask for a room number on startup, as extensively documented elsewhere.

And then, when the game is considered fit to ship, they build a list of which resources go on which disk, pass that to yet another command-line tool, which goes through all that and produces the RESOURCE.### files. Copy the result and there you go.

That list does not need to include all resources though. Indeed, as there are things that are included in the game data but left unused, there are some things that never got on a release disk in the first place. Let’s just say some things in the Larry source assets are even raunchier than you’d expect.

[ , ] Leave a Comment

Ball Road

The dictionary for AGI and SCI games’ text parser input is stored in alphabetical order. This allows a prefix-based compression:

  • another
  • any
  • appear
  • appearance
  • apple
  • at
  • attack

Though the formats for the two engines’ dictionaries are completely different, they share this one aspect. Each of these words is then assigned a group number which is then used to store the said specs. I’ve written about that before. The thing is that when you decompile a game script, you can’t tell which synonym from a given group was originally used. And that’s why when you decompile Leisure Suit Larry 2 and look in the scripts regarding really any female character you’ll see them being called bimbos.

(if (or (Said 'call/bimbo,agent') (Said 'get,buy/ticket'))

That is of course because “bimbo” is in the same group (#42) as “woman” and “lady”, but alphabetically comes before them. “Agent” is in its own group (#50) together with various other jobs. You can call this particular woman either by gender or by profession. You can even call her a KGB agent and the game will allow it. By that same token, “call” is in the same group as the “talk” you’d expect to see here (#11).

But the decompiler has little to no idea of these things.

I have recently acquired the full source code for Larry 2, and that shows a slightly different, more sensible word choice:

(if (or (Said 'talk/girl, clerk') (Said 'get, buy/ticket'))

That is of course because these are the actual word groups being used here:

11 42 50
talk
speak
converse
call
woman
girl
lady
stewardess
blond
chick
blonde
slut
broad
bimbo
maid
receptionist
secretary
mother
mama
momma
mom
clerk
waiter
waitress
bartender
storekeeper
shopkeeper
agent
kgb
kgbishna
custom
attendant
shark

You can see how alphabetical order would mess that up.

And by that same token I can now securely say that the debug cheat code in Larry 3 is not in fact “ascot backdrop”.

“Backdrop” in Larry 3 is in group #1063 together with “put”, “drop”, “release”, “set”, “stash”, and various other “put something here” verbs. You know by now how the smart fella who discovered the debug cheat may have gone about it, and how “backdrop” would be the first word in that group. The canonical phrase however, is

Ascot Place

Because of course if I have the Larry 2 code why wouldn’t I have Larry 3 as well?

(cond
  ((Said 'ascot/place')
    (^= debugging TRUE)
    (if debugging
      (Print "Hi, Al!")
    else
      (Print "\"Goodbye.\"")
    )
  )

The question is… why is this the debug phrase?

And the answer? It’s a callback to Larry 2:

And just like that this post’s title makes a little sense.

[ , , , ] Leave a Comment

Priorities Revisited

I just spent way too long drawing little hexagons. Here’s why.

As you know by now, SCI0 up to SCI11 use vector-based priority and control screens, while SCI2 introduced bitmap-based priority screens while also removing the control screen entirely. That means when I render a background for The Dating Pool, I have to trace out everything you can stand behind, by hand, as vectors. And a while back I replaced the simple railing on the space station scenes with hexagons, so I had to re-vector the priority screen to match.

I didn’t draw any hexagons where the booths are because you can’t stand close enough for it to matter. Saves a lotta work for me. That’s still a lot of Line and Fill commands though. And that’s just one screen up there. There’s three.

Here’s what it’d look look if we targeted SCI2, hypothetically:

Much easier to produce, perhaps. I could just load it into my editor of choice, select everything that’d be closer, and cut that out onto a new layer. Rinse and repeat. And despite appearances here, the priority layers wouldn’t all be the full screen size either. They’d actually only be… 320×17 pixels for the one and 320×22 pixels for the other. Could be even smaller if I cut the booth layer into three separate pieces, perhaps.

Note that the background layer is completely missing the colors from the railing and booth layers. Why compress that twice or more after all?

[ , ] 2 Comments on Priorities Revisited

SCI Drivers, how do they work?

Quite well, thank you.

Now, before I begin I should mention that the extended memory and mouse support in SCI is baked into the interpreter itself, and in SCI32 so are the VGA and VESA video drivers. Oddly, the keyboard is not. But anyway!

An SCI DRV file is a piece of standalone code that is loaded on startup and provides a set of standardized routines for the interpreter to call, even if a given feature isn’t technically supported such as palette rotation in EGA or most things on PC speaker. Their format is actually pretty straightforward.

The first four bytes of the file are a single instruction that jumps to the entry point routine. Whenever a driver function is called for, the BP register is set to the requested function and that jump is called. The entry point routine then uses a lookup table, indexed on BP, to call the requested function, and returns. Easy as pie, really. But that leaves the format.

The next four bytes are the number 0x87654321. Yeah, backwards. I know, wild times. This would be used to check if a given DRV file really is a valid driver, but SCI11 at least seems not to care.

Following is a byte that specifies what type of driver we’re talking about. The install/setup program uses this to determine which list to show it in. It’s otherwise useless, especially with my own install program. From zero to seven, these types are: video, music, voice, input, keyboard, communication, mouse, and memory.

The rest of the data can go basically anywhere — they’re Pascal-style (length-prefixed) strings that should specify their file name and description, but some of them are named dude and one is  . Just a single space. And the descriptions? You wouldn’t be able to tell VGA320.DRV apart from VGA320BW.DRV if you followed this information ‘cos they both say they’re “VGA or IBM PS2, Models 25 & 30 – 256 Colors”. Except one of them is optimized for grayscale-only monitors. So yeah.

Next up is the EXTDRV marker, 0xFEDCBA98, directly followed by a four-byte value whose meaning depends on the type. Again, not actually used, purely for external bookkeeping.

Value Video Keyboard Sound
1 MDA IBM Speaker
2 Hercules Tandy AdLib
4 CGA NEC Sound Blaster/DAC
8 PC Jr. Creative Music System
10 Tandy Tandy 3-Voice
20 EGA Tandy DAC
40 MCGA PS/1 3-Voice
80 VGA PS/1 DAC
100 CGA two-color Sound Blaster Pro
200 CGA four-color MPU-401
400 Explorer Disney Sound Source
800 CD-Audio
1000 ProAudio FM
2000 ProAudio DAC
4000 Windows Sound Source
8000 No MIDI

These values can be combined, so MTBLAST.DRV for example reports 0x204, or “MPU-401 with Sound Blaster DAC”. Of course, this would only make sense for the sound drivers but what can you do?

After this, the actual driver code resumes with the dispatch table, a list of function pointers to each of the features the driver supports. Some may point to null functions, but none of them are themselves null. Because that’d be bad. Directly after this is all the general-purpose variable memory that the driver may need, all preset. For example VGA320.DRV has a standard palette and “wait hand” cursor of its own that it throws up initially.

There are three functions that all drivers must have. The rest depends on their type. These are Detect, Initialize, and Terminate, in that order. The first simply returns a few metrics. For video it’s the number of colors, for music it’s the device ID (to decide which channels to play) and how many voices it can handle. The Initialize function actually sets up the device, switching video modes and setting up timers or what have you. What Terminate does ought to be obvious.

And that’s how SCI drivers work.

[ ] Leave a Comment

Police Quest’s flashing siren lights

The flashing siren lights in the title screens for Police Quest 1 and 3 are sort of interesting, because they are not quite a simple matter of calling (Palette palANIMATE) once or twice. In fact it’s called eight times each frame! Here’s the final result:

And here’s the Script at the heart of it:

(instance cycleColors of Script
  (method (changeState newState)
    ; Fun fact: the switch isn't actually needed.
    ; Not in this use-case.
    (switch (= state newState)
      (0
        (Palette palANIMATE 208 213  1) ;blue in the middle
        (Palette palANIMATE 213 218  1)
        (Palette palANIMATE 218 223  1)
        (Palette palANIMATE 223 228  1) ;blue on the side
        ; Note that we're switching from 1 to -1 now.
        (Palette palANIMATE 229 234 -1) ;red in the middle
        (Palette palANIMATE 234 239 -1)
        (Palette palANIMATE 239 244 -1)
        (Palette palANIMATE 244 249 -1) ;red on the side
 
        ; Almost immediately do it all over again
        (= cycles 10000)
        (= state -1)
      )
    )
  )
)

The palette here has a very particular setup. The lowest colors, #208 to #249, are set up like this:

Each of the eight siren colors in the image has its own four-step palette, individually rotated! It looks kinda like this:

If that one black entry wasn’t in the way between blue and red, it’d line up better, but what can you do?

What’s particularly funny about this is of course that no SCI interpreter with fewer than 256 colors implements this feature.

The cycleColors script is still there and is still invoked. Just like with the chronostream animation in Space Quest 4.

[ , , , ] Leave a Comment

Save early, delete when you need

There’s one interesting tidbit missing here, which is how deletion (SCI1 and later) is implemented. Namely by manipulating the .DIR file in the script, and not – as any sane person would do – with a kernel call.

So wrote Iskovlun in a comment some time back. Let’s see exactly how insane it really is.

; First we open up the directory file.
; Confusing, I know, to call it a directory file. Perhaps
; "catalog" would be better considering a directory is
; already something else. And in SCI32, they did!
((= fd (File new:))
  name: (DeviceInfo diMAKESAVEDIRNAME @str (gGame name?))
  open: fCREATE
)
 
; The format of a save game directory is pretty straight-
; forward -- a word for the index, then the name, terminated
; with an $0A, repeat until done, end with $FFFF.
 
; (File write:) requires a pointer to the data it is to write,
; so we need to put values into variables, rather than just
; passing them immediately. Well, unless you have SCI11+ with
; the extra file kernel calls I nabbed from SCI32 and a matching
; File class, in which case you could just do (File writeByte:
; $0A) if you were so inclined!
(= ret $0A0A)
 
; Now we write the number and name of each saved game, EXCEPT
; for the one that was selected for deletion.
(for ((= i 0)) (< i numGames) ((++ i))
  (if (!= i selected)
    (fd write: @[nums i] 2)
    (fd writeString: @[names (* i COMMENTBUFF)])
    (fd write: @ret 1)
  )
)
 
; Now we write the terminating $FFFF to finish the catalog
; I mean directory off.
(= ret -1)
(fd
  write: @ret 2
  close:
  dispose:
)
 
; Now that that's done, we can safely delete the actual
; save game file.
(DeviceInfo diMAKESAVEFILENAME (gGame name?) [nums selected])
(FileIO fiUNLINK @str)

I almost feel like doing the so-called sane thing and adding a DeleteGame kernel call to SCI11+.

[ , ] Leave a Comment

VGA Versus VESA

We’re all familiar here with the classic 320×200 pixels, 256 color screen mode popularized by the VGA video card, colloquially known as Mode 13h. Most old DOS games from before a particular point in time used it. But what if you want or need bigger? Or more colors? Enter the Super VGA cards with their extended VESA modes.

These VESA modes number 100h and higher, but which exactly are available and what their specs are depends on your exact hardware. As such you can’t rightly assume a certain mode is available and will be that particular resolution and color depth. What you’re supposed to do is ask the system what VESA video modes are available, walk the list to see if you find the one you need, and note its number.

All I have is a copy of DOSBox and a copy of VirtualBox, and of vesachk.exe, available here if you want to try it out yourself. This application gives you that list. Now, the two systems yield vastly different results, mirroring differences in video hardware. DOSBox for example emulates some form of S3 card.

I’ve noticed that when an SCI32 game is made for low-res it runs in plain ol’ Mode 13h, but if it’s meant for higher it’ll use Mode 101h. That’s one of the few in the list that DOSBox and VBox agree on — it has 640×480 resolution at 8 bits per pixel, packed, starting at 0xA0000000.

On the one hand, a regular old 16-bit DOS application wouldn’t be able to address all 307,200 pixels at once the way you can in mode 13h. On the other, a 32-bit application would have direct access to the full linear frame buffer no matter its size. A 16-bit application would need trickery to reach any pixel beyond a certain point, setting the window registers to basically shift the next part of video memory into that same 64 kb block.

This is why SCI32, when switching to mode 13h, just does so:

void Vga::Set320x200()
{
	union REGS reg;
 
	reg.w.ax = 0x0013;
	int386(0x10, &reg, &reg);
	SetVideoMode(1);	// clear all video memory
	SetVideoMode(0);	// back to Normal Mode 13
	lenx = 320;
	leny = 200;
}

But when it switches to mode 101h, it jumps through several hoops. First it checks if VESA is supported at all, then it switches to mode 101h, and then it does some more checks to see if things are as they should be, bailing out if they’re not.

And that’s all good.

But what if you were to find a VESA video mode that was 320×200 with 256 colors? Is there such a thing? A redundancy with mode 13h? As a matter of fact, there is! On the S3 emulated by DOSBox, it’s VESA mode 150h, and once you switch to it things work exactly the same as in mode 13h, except the memory access timing or whatever is different.

; VGA mode 13
mov ah, 0x00
mov al, 0x13
int 0x10
 
; VESA mode 150h
mov ax, 4F02h
mov bx, 150h
int 10h

But on whatever VBox has to offer, which is a vastly longer list with about a hundred more modes, this could be 146h, 346h, 546h, or 746h. And that’s why you really should ask the system about it!

But SCI32 basically assumes 101h is what it needs to be and presumably the folks at Sierra tested a bunch of contemporary cards and found this to be true.

Fun fact: SCI16 has all its video driver code in files like VGA320.DRV, but SCI32’s VGA.DRV is practically empty. It’s technically a valid SCI driver file but it’s basically just a header. Same with its VESA.DRV. All their code is in the interpreter itself, much like the mouse driver. It’s only there so the installer can offer it and the interpreter can determine which was chosen. And even then, the interpreter for later high-res only games like Gabriel Knight 2 will happily ignore all that.

[ , , ] Leave a Comment

Menu Slowdown Mystery

Yesterday I had the idea to make a small “game” demonstrating various SCI11+ features, and instead of a main menu screen full of buttons I thought I’d use a proper menu bar. The kind you’d see in the old SCI0 games with the text parser and the low-resolution version of Leisure Suit Larry 6. Which incidentally is the only SCI11 game to have such a menu bar. The high-res SCI2 version’s menu bar is implemented entirely in script but the other one is 100% the same as in SCI0. If you were to copy the interpreter from any other SCI11 game over to LSL6 and run it you’d get an error saying AddMenu is not supported.

If you were to build your own SCI11, you’d find there are four targets; with the built-in debugger, with menu bar support, both, or neither. Most SCI11 games come with a “neither”, except for LSL6.

But this post’s title mentions slowdown. Why?

I defined two menu items, File and Topic. No Sierra logo menu here, not this time. One has About and Quit, the other lists the various features. As you do. But somehow when opening the second menu things would slow down just before it got to the last item, about the support for SCI32-style no-lookup font and color change control codes.

I thought maybe I’d messed something up when I cleaned up the source code, or when I added the thing where the menu bar is drawn in the same colors as the status line.

So I built an SCI11+ with all switchable hacks turned off. No dice, the menu was just black and white now but still slow. So that ruled out the extra features.

A “base” build that’s missing all new features? Nope.

Copy the terp from LSL6 and run that. Same deal so it’s not the cleanup either.

So I tried reorganizing the menu items. And I saw my mistake.

I had used a | to separate the Unicode and SCI32 control code items where it should’ve been :. For added insult, that’s the text formatting control code.

I wonder if AddMenu would work better as a variadic function 🤔

[ ] Leave a Comment

Perspective is a tricky thing

This topic was suggested, more or less, by Phil Fortier.

What do these screenshots from DoomLeisure Suit Larry 3, and Secret of Monkey Island have in common?

Their perspective. Every single wall is a straight line. I put Doom there to show it’s not just adventure games, and Monkey Island because the arcs end in straight lines, but otherwise they all have the same perspective. Don’t believe your eyes? Here, let me spell it out for you:

This is one-point perspective, where lines converge to a single point.

Here’s a Youtube video I picked out at random from my search results while I ensured I wasn’t pulling crap out of my ass. You’ll notice a hallway like that could do well as an adventure game background.

They’re also a pain in the ass when you render your game’s backgrounds with a program that doesn’t do 1PP, like I do. I mean, I could use this copy of 3D Studio Max that I have collecting dust over here, but all my prefabs are in Daz Studio? So I gotta fake it somehow. Very carefully align the camera so the walls point straight up.

In this old version of Alhor’s Garage in The Dating Pool, the walls are not straight. So I went back and tweaked the camera along with a few other details.

I feel much better about this version. But for other scenes, to get enough floor space in view, I have to pull back the camera drastically. Normally you’d increase the floor space by angling the point of view down. I’m sure you can agree that in Chairman Kenneth’s office, the camera is pretty far up. If I tried to reproduce that image in Daz, I’d get diagonal walls. So how do you fix that?

There’s practically no floor space here! If I used this, the main character would have a line to move along, and if other characters were to try and pass there’d be almost no space to show it. Moving the camera up mostly increases the ceiling space…

And of course you could fake it by tilting the walls back to compensate.

Or you can just say fuck it and deem the perspective distortion negligible after downsampling.

*sigh*

I seriously wish I had the means to acquire some nicely painted backgrounds, even after years of demos with rendered ones.

[ , , , , , ] Leave a Comment

File functions in SCI11 and SCI2 compared

Not to be confused with the other one.

In SCI11, we get the following FileIO functions:

(FileIO fileOpen name mode) Opens file name with the given mode and returns a handle.
(FileIO fileClose hnd) Closes the given file handle.
(FileIO fileRead hnd ptr size) Reads size bytes to the given address.
(FileIO fileWrite hnd ptr size) Writes size bytes from the given address.
(FileIO fileUnlink name) Deletes the given file.
(FileIO fileFGets ptr size hnd) Reads a string of up to size bytes to the given address.
(FileIO fileFPuts hnd ptr) Writes a string from the given address.
(FileIO fileSeek hnd where from) Seeks to the given location.
(FileIO fileFindFirst pattern ptr attr) Finds the first file matching the given pattern and copies its name to the given address.
(FileIO fileExists name) Checks if the given file exists.
(FileIO fileRename from to) Renames the given file.
(FileIO fileCopy from to buffer len)

Hmm. So, only null-terminated strings and arbitrary data blocks, huh? Surely you can do better? I wouldn’t want to have to do this every time I wanted to simply save a single 16-bit variable:

(= val gRating)
(FileIO fileWrite hnd @val 2)

Actual code from an earlier build of The Dating Pool, in case the name of the global didn’t tip you off.

Now, SCI2 added a whole bunch of extra options to the above:

(FileIO fileReadByte hnd) Simply reads and returns a single byte.
(FileIO fileWriteByte hnd val) Simply writes a single byte.
(FileIO fileReadWord hnd) Reads a single 16-bit word.
(FileIO fileWriteWord hnd val) Writes a single word.
(FileIO fileCheckFreeSpace ???) I’m not sure how this one works yet.
(FileIO fileGetCWD ptr) Was a separate call in SCI16.
(FileIO fileValidPath ptr) Checks if the given string is a valid path, I guess.

Wow. So what I did after learning these existed and looking back at the long-since scrapped persistent settings in The Dating Pool was, I added some of these to SCI11+. Specifically, the first four.

It was that or switch to using the File class exclusively and add wrapper functions for simple value reading and writing to it. But I can honestly say The Dating Pool doesn’t use File at all.

[ ] Leave a Comment