# The Sierra Creative Interpreter

## Throughout the documentation, the term SCI will be used to describe the original Sierra Creative Interpreter, in any version. SCI0 will refer to all games using the SCI version 0.xxx, except for those games who use the 'in-between' game engine referred to as SCI01 (such as Quest for Glory 2). SCI1 will refer to the interpreter version 1.xxx, and SCI2 to interpreter version 2.xxx. FreeSCI will refer specifically to either implementation details of the FreeSCI engine or to extensions of the original SCI engine specific to FreeSCI.

I would like to take this opportunity to thank the members of the FreeSCI and SCI Decoding Projects and their supporters, as well as Carl Muckenhoupt, who took the first steps of SCI decoding, for their valuable help and support.

Please note that some of the text contributions have been cut, reformatted or slightly modified in an attempt to improve the general quality of this document.

##

## The Sierra Creative Interpreter is a complete virtual machine. It cointains functionality for displaying graphics, playing sound, receiving input, writing data to the hard disk, and handling complex logical functions. In version 0.xxx of the interpreter, Sierra split the game data into nine different types of information:

- Each game may contain up to 1000 different elements of each data type; these elements are referred to as "resources". The index numbers of the various resources need not be in sequence; they are usually assigned arbitrarily.

  With several notable exceptions, used mainly by the debugger.

## Individual resources can be stored in one of two ways: Either in resource files (which, surprisingly, are called something like "resource.000" or "resource.001"), or in external patch files (not to be confused with "patch" resources). The external files are called something like "pic.100" or "script.000", and they take precedence over data from resource files.

There is also a file called "resource.map", which contains a lookup table for the individual resources, and another file, "resource.cfg", which contains configuration information; neither of those is used by FreeSCI.

Resource information stored in external patch files is not compressed and therefore easily readable. It is, however, preceeded by two bytes: The first byte contains the resource type ORed with 0x80, the purpose of the second byte is unknown (but it appears to be ignored by the original SCI version 0 engine).

As stated before, external patch files take precedence over resource resource files. Applying those external files as patches is an option since FreeSCI version 0.2.2.

The resource files, however, are more complicated. Each of them contains a sequence of resources preceeded by a header; these resources may be compressed. It is, also, quite common to find resources shared by several resource files. The reason for this obviously is that, back when hard disks were rare and hard to come by, the games had to be playable from floppy disks. To prevent unneccessary disk-jockeying, common stuff was placed in several resource files, each of which was then stored on one disk.

## The resource types of SCI0 can be roughly grouped into four sets:

- Graphics (pic, view, font, cursor)

Text resources are nothing more than a series of ASCIIZ strings; but the other resources deserve further discussion.

### The screen graphics are compromised of the four graphics resources. The background pictures are drawn using vector-oriented commands from at least one pic resource (several resources may be overlaid). The fact that vector graphics were used for SCI0 allows for several interesting picture quality improvements. Pic resources also include two additional "screens": The priority screen, which marks parts of the pictures with a certain priority, so that other things with less priority can be fully or partially covered by them even if they are drawn at a later time, and the special screen, which delimits the walking area and some special places used by the game logic. FreeSCI uses a fourth auxiliary screen for during drawing time (this is a heritage from Carl Muckenhoupt's original code).

The view resources compromise the bitmaps of the game. They are somewhat compressed, and include transparency information. Also, each view resource may contain several animation "loops" consisting of one or more "cells", each of which is an image.

View resources are used for foreground images as well as for background images (for example, the "Spielburg" sign in QfG1 (EGA) is stored in a view resource and added to the background picture after it is drawn). The cursor resource contains simple bitmaps for drawing the mouse pointer. It only allows for black, white, and transparent pixels in SCI0. The fourth graphics resource is font data. It contains bitmapped fonts which are used to draw most of the text in the games. Text is used in one of four places: Text boxes, Text input fields, the title bar menu, and (rarely) on-screen.

### SCI0 uses two types of resources for sound: Patch resources, and sound resources. Sound resources contain a rather simple header, and music data stored in a slightly modified version of the MIDI standard.

Patch resources contain device-dependant instrument mapping information for the instruments used in the sound resources. SCI0 sound resources do not adhere to the General Midi (GM) standard (which was, to my knowledge, written several years after the first SCI0 game was released), though later SCI versions may do so.

Since FreeSCI currently only takes advantage of GM output, it maps the instruments according to strings taken from patch resource 002 (Used by the IMF/FB01 sound device, whatever that may be). This works almost adequately.

### Whenever the parser needs to look up a word, it looks for it in one of the vocab resources. This is not the sole purpose of the vocab resources, though; they provide information required by the debugger, including the help text for the debugger help menu and the names of the various SCI opcodes and kernel functions.

Script resources are the heart (or, rather, the brains) of the game. They contain commands to load and display resources, to react on certain actions, and generally to find out what happens next.

To be more precise, scripts are composed of classes and class data. To run the game, scripts are instantiated on the SCI stack, where their functions are executed by a virtual machine. They use a set of 0x7d opcodes, which may take either 8 or 16 bit parameters (so, effectively, there is twice the amount of commands). The functions may refer to global data, local temporary data, local function parameter data, or object data (selectors). They may, additionally, indirectly refer to "hunk" data, which is stored outside of the SCI heap. Since the whole design is object oriented, functions may re-use or overload the functions of their superclass, which allows for a quite powerful design.

One of the most powerful things functions can do is to call a kernel command. This is the SCI equivalent to a system call as provided by traditional operating systems. These kernel commands are practically unexplored, but we can safely assume that they are used to perform tasks like playing a song, drawing an image, waiting for a key, changing the mouse pointer appearance, or loading a save game file.

## SCI01 differs only in very few respects: It uses different compression algorithms (all of which are supported since FreeSCI 0.2.1), and a different type of sound resources, which may contain digitized sound effects (samples). The basic music data, however, still resembles MIDI data. It has not been examined yet.

## SCI1, which is not covered by FreeSCI at the moment, introduces new concepts like Palettes, scaled bitmap images and several new compression algorithms.

##

###

###

###

###

##

## The graphics in SCI are generated using four resource types:

Those resources are drawn on three distinct screens:

##

Version 1.0, 6. July 1999

Note that the observations made in this document are generally based on SCI version 0.000.572 (the one that comes with LSL3), but should be valid even for SCI01 and SCI1, as well. I know already about some differences in the port system from SCI0 to SCI1, but I feel we should have an interpreter running for SCI0 before dealing with SCI1.

This article discusses a key data structure in SCI graphics handling; this data structure is called a port, and it is involved in most graphics-related operations. The port is basically a graphics state record, storing things like pen color, current font, cursor position etc. Each port also has an origin and a size. The actual port data structure has remained absolutely unchanged from SCI0 up to the latest versions of SCI1.

The port can be viewed as a rectangle in which things are drawn. Every drawing operation (even KDrawPic) is executed relative to the origin coordinates of the current port (depending on the kernel function, other parameters in the port structure are used as well), such that coordinate (0, 0) in the "picture window" (such a thing really exists in SCI!) is

At least three ports are created and managed automatically by the SCI interpreter. These are the "window manager" port, the menu port, and the picture port (which is actually a window, see later). The latter two should be fairly easy to understand. The menu bar is drawn in the menu port, and the current room is drawn in the picture port. What may be less obvious is that the window manager port is an "invisible" port, on which the window backgrounds are drawn, although the windows have a port themselves. If you are familiar with Windows programming, the term "client rectangle" may ring a bell here - SCI draws the window backgrounds, using values in the window manager port, while the window's own port controls what is drawn inside it. The window manager port covers the same bounding rectangle as the picture window, but it is transparent so it doesn't mess up the graphics.

I feel compelled to mention windows for a bit here, not in depth - they are the subject of a later article - but just to mention that the structure used to manage windows is just an extension of the port structure. Whenever an SCI system call needs a pointer to a port structure, a pointer to a window structure will do. This implicates that the SysWindow class (which implements windows) has no "port" property. Instead, its "window" property points to the extended port/window structure which can safely be passed to KSetPort. Not surprisingly, many of KNewWindow's arguments end up in the port part of the window structure.

An SCI program can't directly instantiate a port. If a program wants to access a specific part of the screen using ports, it has to instantiate a transparent window. In fact, SCI creates the picture window using RNewWindow, the same function that the kernel call KNewWindow ends up calling, asking for an untitled window with a transparent background - but more on that in a later article.

It must be stressed that ports are purely internal structures. Although a program can select different ports to draw in, the data structures themselves are absolutely off-limits to SCI code. KNewWindow fills a port structure with user-supplied data, but there is no way of changing that data, short of dis- posing the window and instantiating it again. The structure is frequently changed by SCI itself, though.

Only two kernel calls deal directly with ports:

- KGetPort (see KGetPort)
- KSetPort (see KSetPort)

These two functions are often used in pairs (also internally), like:

    var temp;

    temp=KGetPort();  /* Save the old port */
    KSetPort(...);      /* Activate some other port */
    ..                            /* Draw some stuff */
    KSetPort(temp);    /* Reactivate the old port */

## This resource stores a simple bitmap describing the shape and texture of the mouse pointer. All information stored herein is little endian in byte order.

0x00 - 0x01

- X coordinate of the mouse cursor hot spot as a 16 bit integer. This variable is not used in SCI0.

0x02 - 0x03

- Y coordinate of the mouse cursor hot spot as a 16 bit integer. Only 0x03 is used in SCI0; here, if set, the hot spot is at (8,8), if not set, it is located at (0,0).

0x04 - 0x23

- This is a list of 16 unsigned 16 bit integers constituting bitmasks for the mouse cursor's transparency map, with the MSB representing the leftmost pixel.

0x24 - 0x43

- This is another list of 16 unsigned 16 bit integers. Each of them represents another bitmask, determining whether the mouse cursor pixel should be drawn in black (not set) or white (set).

To determine whether or not to draw a pixel, and, if it is to be drawn, in which color it should be drawn in, the corresponding bits of both bitmask lists mentioned above have to be examined. In the table below, A represents a bit from the first list, and B the corresponding bit from the lower list.

## Color mapping for the SCI0 mouse pointer

AB | Result
00 | Transparent
01 | Transparent
10 | 0x00 (Black)
11 | 0x0f (White)

## Color mapping for the SCI1 mouse pointer

Since this method of doing things wastes one combination, the table was changed for SCI01 and SCI1:

AB | Result
00 | Transparent
01 | 0x0f (White)
10 | 0x00 (Black)
11 | 0x07 (Light Gray)

## In SCI0, Views are collections of images or sprites. Each View resource contains a number of groups, which, in turn, contain one or more images. Usually, those groups contain a number of consecutive animation frames. It appears to be customary to store related animations or images in a single frame. For example, the basic movements of all protagonists (four or eight animation cycles (depending on the game)) are stored inside of a single View resource. Please note that the byte order of the following data is always little endian.

##

0x00 - 0x01

- The number of image groups available.

0x02 - 0x03

- A bitmask containing the 'mirrored' flag for each of the groups, with the LSB containing the 'mirrored' flag for group 0.

0x04 - 0x07

- - unknown -

0x08...

- A list of indices pointing to the start of the cell list for each image group. The number of entries is equal to the number of cells as described in 0x00 - 0x01.

##

0x00 - 0x01

- The number of image cells available for this group.

0x02 - 0x03

- - unknown -

0x04...

- A list of 16 bit pointers indexing the start of the image cell structure for each image cell. The pointers are relative to the beginning of the resource data.

##

0x00 - 0x01

- The horizontal (X) size of the image.

0x02 - 0x03

- The vertical (Y) size of the image.

0x04

- The x placement modifier. This signed value determines the number of pixels a view cell is moved to the right before it is drawn.

0x05

- The y placement modifier. This signed value determines the number of pixels a view cell is moved downwards before it is drawn.

0x06

- The color key, i.e. the color number used for transparency in this cell.

0x07...

- A list of combined color/repeat count entries. Each byte contains a color entry (low nibble) and a repeat count (high nibble). If the color is equal to the color key from index 0x06, then no drawing should be performed, although [repeat] pixels still need to be skipped. It is not known whether this list is terminated; the FreeSCI drawing algorithm stops drawing as soon as the rectangle defined in the first two cell entries has been filled.

## by

Version 1.0, 7. July 1999

I am going to start by mentioning the menus. It has nothing to do with the material I deal with in this essay. They use different kernel calls, and such things as port management are handled internally by the kernel routines. The SCI program just sets up a menu structure using the kernel calls. Since they are irrelevant to the subject of this essay, I will not spend more time on them.

The Rect structure is important (also to ports) since it is the basis for passing a screen position to the interpreter. It looks like this:

    typedef struct
    {
      short top, left, bottom, right;
    }

It will be seen from this that rectangle coordinates in SCI are not normally represented in the usual (x,y,width,height) fashion. So pay close attention to this structure! Also, it is not passed as a pointer, but rather as the four values in order. This is particularly true of SCI objects, where the property names nsTop etc. actually form a Rect structure which can be used directly by the interpreter.

Windows are created using the KNewWindow kernel function. Each window has six attributes which are passed from the script to the kernel function:

- Of these, the type and priority are the most interesting, because they decide the appearance of the window. The type is a bit field:

  - Bit 0 specifies a transparent window. KNewWindow does not save the image behind the created window - it stays on the screen until the pic is redrawn, so windows with this style definitely can't be used as message boxes. It does have some special uses, though. If this bit is not set, KNewWindow draws a rectangle in the specified background color using the bounding rectangle coordinates (using the WM port). When this bit is set,

    Bit 1 specifies a window without a frame. The frame is the black shading you can see in the corner of a message box.

    Bit 2 tells KNewWindow to draw a grey title bar with a title printed in white. In the version I have used for this essay, it is not possible to change the title bar colors. Note that the bounding rectangle is always specified as if the window had no title bar. If this bit is set, ten pixels are reserved above the coordinates specified. Although this bit is set, the Title parameter may still be NULL. If this is the case, an empty title bar is drawn.

    Bit 7 has a special meaning; it is used only in window type 0x81, and is not tested in any other way. When this style is chosen, KNewWindow does not draw anything at all. It is the caller's responsibility to draw a window frame on the WM port. CB1 uses this style for its ornate windows, and draws the frame manually.

    The picture window which I mentioned in the last article is created using style 3 (that is, TRANSPARENT | NOFRAME). The normal message box styles used in LSL3 are 0 and 4.

    I have not been able to investigate the priority property yet, so the fol- lowing is based on suppositions. It is only used when drawing transparent windows. In this case, if priority is not -1 (which means not used), the window is drawn onto the priority map (with the specified priority value) as well as the screen.

    There is a class called SysWindow which is just a simple wrapper around the following two kernel calls. Try breaking on SysWindow::open, then type c to inspect the current object. You can change all the parameters to KNewWin- dow (the Rect is split in its fields, to nsTop, nsLeft etc.)

    To create a window structure, use KNewWindow (see KNewWindow); to remove it again, apply KDisposeWindow (see KDisposeWindow) on it.

    So how do we put stuff inside these windows? That question is a little com- plicated to answer, because it is really a shared effort between the inter- preter and the object hierarchy, and this is one case where the interpreter actually interacts with the objects itself. I will start by explaining the classes involved.

    All control types are descendants of a common class (I do not know its name, since it appears to have an invalid name property). Among other things, this common class contains a type number and a state. The type number is the only thing that distinguishes the control types from each other inside the interpreter - if a wrong type is set, the interpreter might try to change a non-existent property.

    The type numbers are laid out as follows:

    - The gauge "controls" are not really controls. I don't know how they work (yet).

      Each control also has a state value. These are laid out as follows:

      - Note that state 3 is by far the most common. With that explained, I'll move on to the kernel functions. There are three functions associated with controls - KDrawControl (see KDrawControl), KHiliteControl (see KHiliteControl) and KEditControl (see KEditControl). Note that there is a KOnControl kernel call which is entirely unrelated to window management.

        The dialogs are implemented using not one, but two classes - Dialog and Window. While the Window class maintains the window (It is derived from SysWindow), the Dialog class is just a list of controls. It is derived from the List class, but has extended functionality to tell its members to redraw etc. There is a special function, located in script 255, which allows scripts to push information about the dialog on the stack instead of creating the Dialog object manually.

        Note that the internal debugger uses the same window calls as the SCI script. That is why the screen messes up if you step through drawing code - the debugger has activated the Debug window port, and "forgets" to switch back while stepping across instructions. Thus, all graphics commands are redirected to the debug window port. Not a pretty sight.

## By

A pic in SCI consists of three layers (called maps - they are unrelated to the map resources found in SCI1 gamers). The visual map, used for the picture which appears on the user's screen. The priority map which tells the interpreter which things go in front of which in the three-dimensional room. Without the priority map, a room would just be a flat, painted surface. The control map decides where game characters (called actors) can walk and where special events occur. These special events are triggered by a game character walking on a particular spot. Where the visual map is almost always very complex and using dithered fills etc., the latter two consist of large areas of solid color.

Many functions which need to access these maps do so by using a bit-field. The bits are laid out as follows (but don't set more than one at a time!)

- It is important to understand that, although being represented as colors on the screen, a priority/control "color" should be considered a number. The colors map to values according to the standard EGA color values.

  Every animated object in SCI has a priority. As the object moves, its pri- ority changes according to the so-called priority bands, explained next (it is, however, possible for a script to lock the priority of a view). The picture window is divided vertically into 16 priority bands. The priority of an animated object is determined by the position of its "base rectangle" in one of these bands. Things are drawn in order of ascending priority, so objects with priority 15 are guaranteed to be in front of everything else. The default priority mapping gives priority 0 a fairly large space, the 42 topmost rows (including the menu bar which AFAIK is 10) in the picture. All other priority bands have the same size. A script can choose to alter this mapping, specifying the amount of space to assign to priority 0, and the number of the last row to include in the mapping calculation.

  In most rooms, it is desirable to limit actor movement, confining the actor to a specific part of the screen. In other cases, special events are triggered by movement into a specific screen area. On some occasions, even room switches are implemented using control polygons. While the meaning of priorities is determined by the kernel, the meaning of control values is entirely up to the script. It is more or less a standard, however, that actors can't walk on white control areas.

  As the control map is not consulted by the interpreter itself (except in a few cases), scripts need a way to do so. That way is called OnControl, and it is a kernel call. Supplied with a point or a rectangle, it returns a bit mask containing the control values of all the pixels in the desired region. If a specific control value is encountered, it is used as a bit number, and that bit is set in the output mask.

  This bit mask system is also used in another place, namely the illegalBits selector of the Act (actor) class. The illegalBits selector determines in which areas the actor may not walk.

  The OnControl() system call is explained in KONCONTROL.

##

## by

Revision 7, Jan. 7, 2000

## Sierra's SCI0 sound resources contain the music and sound effects played in the game. With the introduction of SCI, the company took advantage of new sound hardware which allowed for far better music than the traditional PC speaker could ever create. Sierra chose two devices to specifically target: the MT-32, and the Adlib. The MT-32 is a MIDI synth while the Adlib is a less expensive card based around the OPL2, a non-MIDI chip. Anyone interested in Sierra music and its history can find information at the Sierra Soundtrack Series (http://www.queststudios.com).

Music is stored as a series of MIDI events, and the sound resource is basically just a MIDI file. Much of what I write here comes from outside documents, and I would direct anyone seeking more information to a MIDI specification. The MIDI resource page (http://www.midi.org/resource.htm) on midi.org has some good links as well as tables with status and control information.

Some people prefer the one-based numbering system for channel and program numbers. I personally prefer the zero-based system, and use it here. If you're familiar with channels 1-16, be aware that I will call them 0-15. My intention is not to be deviant from other programs but to be more accurate in representing the way information gets stored. The same is true for programs 0-127 as opposed to 1-128. For whatever reason, convention already holds that controls be numbered 0-127, so nothing in my treatment of them should be abnormal.

Sierra changed its sound file format in the switch to SCI1. I refer only to SCI0 sound files in this specification. Hybrid interpreters such as the one used for Quest for Glory II are also excluded. Finally, SCI games written for non-DOS systems may have different formats. This document applies to Sierra's IBM games.

Please post comments or questions to the SCI webboard: http://www.InsideTheWeb.com/mbs.cgi/mb173941

You can contact me personally at ravi.i@softhome.net, but I would prefer that SCI messages be posted on the webboard so everyone can see them.

## A gamer's sound hardware greatly affects how the music will sound. Devices used by SCI0 can be broken into 3 categories:

MIDI Synths

- These will generally give the best sound quality. MIDI synths are polyphonic with defineable instruments through patch files and full support for MIDI controls. The General MIDI standard had not been written when Sierra began writing SCI games, and as far as I know no SCI0 game uses a GM driver or includes a GM track. This means that synths had to be individually supported.

Non-MIDI Synths

- Generally not as good as MIDI synths, but also less expensive. The OPLx family of chips are still very common among home PC users thanks to the Adlib and SoundBlaster cards. Synths are polyphonic with defineable instruments through patch files, but drivers must be written to interpret MIDI events and turn them into commands the hardware will recognize. Support for most sound controls gets lost in the process. Furthermore, drivers must map logical, polyphonic MIDI channels to physical, monophonic hardware channels. A specific control was introduced for this purpose and will be discussed later. There is no common way of accessing these devices, so they must be individually supported. These days, however, most people have an Adlib compatible card.

Beepers

- Beepers produce very poor music and don't support instrument definitions, but all PC users have one so supporting them covers people without special sound hardware. The most common device is the PC speaker, which is monophonic. Another is the Tandy speaker with 3 channels. Drivers must interpret MIDI events, but need only concern themselves with basic functionality. Interpreting the MIDI events is also made easier because each channel is monophonic. To play a chord on the Tandy, for example, each voice must be put in a separate MIDI channel.

With such a diverse group of devices to support, Sierra put a lot of the work on the shoulders of the drivers. Functions for loding patch files, handling events, pausing, etc. are all in the drivers. The interpreter calls them as needed but does not concern itself at all with how they get implemented.

Listed here are devices supported by the SCI0 interpreter with a little information about each. There could very well be other hardware not listed here, so please send in any missing information. Also, since the interpreter is made to be device independent, this list could be easily expanded. The process would require that a new driver be written and a patch file created if appropriate. Since the driver is responsible for choosing which patch file to use and then entering it into the device, the second step would be quite easy.

(thanks to Shane T. for providing some of this). Blank fields are unknown, not unused.

- The driver field is the file name of the driver without the drv extension. Patch specifies which patch file the driver requests. Poly is the maximum number of voices which can be playing at once as reported by the driver. The play flag specifies which channels the device will play and gets explained in the header section.

## Sound files follow the same format as all extracted SCI0 resources: the first two bytes of the file contain a magic number identifying the resource type and the rest of the file contains a dump of the uncompressed data. The identifier is the resource type (04h for sound) OR-ed with 80h and stored as a word. The result will be 84h 00h in extracted sound files.

The sound resource data itself is a header with channel initialization followed by a series of MIDI events.

### The header provides the sound driver with 2 pieces of information about each channel. The first is a byte which specifies how many voices each logical MIDI channel will be playing. For MIDI synths, this information is not really necessary and is probably ignored. The same goes for beepers. This byte is only useful for non-MIDI synths which must know how many hardware channels each logical MIDI channel will need. This value is only an initial setting. Sound files can request changes to the mapping later with control changes.

It's worth mentioning here that previous revisions of this specification said that some of the MIDI controls could be initialized using the first byte. I now believe this is a bug in the Adlib driver which happens when the value gets too large and causes overflows into other areas of memory. There should not be more requested channels than the device has hardware channels.

The second byte describes how the user's sound harware should treat the channel. It is the combination of bit flags which may be OR-ed together. If the appropriate bit is set for the currently selected sound device, the channel will be played. If it is not, the channel will be silent. The driver decides which bit it will use as the play flag, and the table under Sound Devices lists the flag used by each driver. Drivers ignore the first byte (used to request hardware channels) on MIDI channels it won't play.

Before the channel initialization is a byte which specifies whether the header holds information for 15 or 16 channels. A value of 0 means that there will be 16 channels and a value of 2 means that there will be 15 channels. All other values are undefined and will render sound files unplayable.

The header format:

- Notice that the space for channel 15's initialization will always be present. If the number of channels code is 2, the last two bytes of the header will be ignored, but they are stil in the resource. The header is always 33 bytes in length.

### The actual music is stored in a series of events. The generic form for an event is:

- Delta time is the number of ticks to wait after executing the previous event before executing this event. Standard MIDI stores this as a variable length value. In sound resources, it will generally be one byte and the most significant bit is in fact used as part of the value. In cases where a single byte cannot hold a high enough delta time, sound resources use 0xF8 one or more times in a row. The 0xF8 byte causes a delay of 0xF0 (240 decimal) ticks before continuing playback. The sequence F8 F8 78 FC waits 600 ticks then stops the sequence because of the FCh status. There are 60 ticks per second, which is about 16667 microseconds between ticks.

  The status byte is basically a command. The most significant bit is always set. This feature is important because the status byte will not always be present. If you read a byte expecting it to be a status byte but the most significant bit is not set, that byte is actually a paramater and you should repeat the last status used. This is known as running status mode and appears to get used relatively often.

  There generic form for a status byte is (in bits) 1xxxcccc - The lower nibble designates which channel the message affects except when the upper nibble is 15. The upper nibble is the command, but as stated earlier, the most significant bit must be 1. That leaves space for 8 messages, most of which require at least one paramater. Paramaters will never have their most significant bit set.

###

Control: Set control c to s on channel x. This can be confusing because there isn't just one meaning. Changing the settings on different controls will, of course, have different outcomes.

Controls which handle any value are continuous controllers. They have a continuous range. Controls which are only on/off are switches. Their defined range is only 01h (OFF) and 7Fh (ON). However, in order to respond to all values, 01h-3Fh is treated as OFF and 40h-7Fh is treated as ON. While in practice they may only use bit 6 as a flag, my personal opinion is that values between 01h and 7Fh should be avoided for the sake of clarity.

Listed in this reference are the non-standard MIDI controls I've found in Sierra SCI0 sound files. Not all drivers support all controls.

Control Refrence

Reset on StopSound: An on/off switch where a value of zero is off and a non-zero value is on. Note that this is not the same as for standard MIDI control switches. When this control is on, calling the sound driver's StopSound subfunction will reset the sound position to the beginning. The initial value is set to off when a sound gets loaded.

Program change: Set program (patch / instrument / ect.) to p for channel x. This is a simple instrument change.

Channel 15, however, includes two special cases of this status. If the new program is less than 127 the Sound object's signal property is set to the new program, making a non-cumulative cue. If the new program is equal to 127, then that exact position (not the start of the current tick) is set as the loop point. Normally, the driver loops to the beginning of the sound. If an explicit loop point is set, the sound will be replayed from the marked time instead.

The actual time of the loop point is better explained with a short diagram:

- In both situations (p < 127 and p = 127), no actual program change takes place. Channel 15 is used for control, not playing music.

Begin SysEx: Starts a system exclusive data block. The block must terminate with F7h.

End SysEx: Ends a system exclusive data block. Normal sound data resumes at this point.

Stop Sequence: This is a system real-time message which tells the sound driver to stop the current sound. The sound object's signal property gets set to FFFFh and the position moves to the loop point, which defaults to the beginning. Drivers allow this message to occur without a delta time, but I haven't seen any examples.

##

##

## As Ravi describes in his description of the patch resources (which have not been included here), one of the major problems with SCI sound support is the lack of General Midi (GM) support in the earlier games. Since those were written before GM was conceived, this can hardly be considered to be Sierra's fault; but this fact doesn't help when it comes to supporting the games in a portable manner.

Unfortunately, almost every SCI0 game uses an individual instrument mapping scheme. This means that there are only two options to generate GM music from the original SCI sound resources: Either create a manual mapping for each game, or abuse existing data from the game for this purpose. Obviously, the latter way would be either impossible or much easier.

So, the solution would be to use an existing instrument mapping scheme. Those mapping schemes are stored in the patch resources, and, as such, easily accessible to an SCI engine. As those patch files are driver dependant (which, in turn, are hardware dependant), most of the patch data is unusable. The Adlib data, for example, will only work for an OPL-2 FM synthesizer chip or one of its successors, the MT-32 data (which consists of one massive sysex block) won't help anyone without an MT-32 or LAPC-1, and so on. So, to recycle this hardware-dependant data, two new possibilities remain: Either extract and interpret the patch data using a portable software synthesizer (such as timidity), or extract instrument names and map those to GM instruments. The first approach would, of course, yield the better results (at the cost of computation power); but the only software emulator for a specific sound system I've seen so far was an OPL-2 emulator. So the alternative, extracting a text ID of each instrument and using it to map this instrument to a GM instrument, looks much more promising.

Now, most SCI0 games come with a patch.002 resource, which is used by the IBM Music Feature card and Yamaha FM-01 sound synthesizers (both of which appear to use frequency modulation). This is the only patch file that includes text descriptions of most of its instruments. Note this, not all instruments have name representation. This means that some of them can't be mapped and have to be silenced; but those instruments are either used for sound effects only or not used at all, so this isn't critical.

Using those 7-letter instrument names, it is now possible to build a small database of instruments, which, subseqently, can be mapped to GM instruments.

The file structure is relatively simple (for this purpose): Every patch.002 consists of either one or two instrument banks carrying 48 instruments each. Every instrument has a fixed block size of 0x40 bytes; each block starts with the 7-letter description of the instrument or seven blanks if none is available.

If two banks are present, the second bank is separated from the first one by a two-byte sequence (0xab, 0xcd). Keeping this in mind, it is trivial to extract the instrument names of the 48 or 96 instruments.

## Percussion instruments are treated specially in the MIDI standard. MIDI channel 10 (or 9, if you count from 0 to 15 like most people do) is reserved for percussions and some special effects; each key for this channel represents either nothing or one fixed percussion instrument.

At first glance, this might lead to an additional problem of mapping those percussion instruments. Fortunately, the General Midi standard extends on the MT-32 percussion mappings, which are used in SCI0, so that channel 9 can be left completely untouched in the process of instrument mapping.

##

##

## Like any processor, the SCI virtual machine is useless without code to execute. This code is provided by script resources, which constitute the logic behind any SCI game.

In order to operate on the script resource, those first have to be loaded to the heap. The heap is the only memory space that the VM can work on directly (with some restrictions); all other memory spaces have to be used implicitly or explicitly by using kernel calls. The heap also contains a stack, which is heavily used by SCI bytecode.

Each script resource may contain one or several of various script objects, listed here

:

- The code blocks contain the SCI bytecode that actually gets executed. The export block (of which there may be only one (or none at all)) contains script-relative pointers to exported functions, which can be called by the SCI operations ``

  But the most important script members are Objects and Classes. As in the usual OOP terms, Classes refer to object prototypes, and Objects are instantiated Classes. However, unlike most OOP languages, SCI treats the base class very similar to objects, so that they may actually get called by the SCI bytecode. Therefore, they also have their own space for selectors (see below). Also, each object or class knows which class it inherits from and which class it was instantiated from (in the case of objects).

  Also of note is the relocation table (type 8). It contains script-relative pointers to other script-relative pointers that have to be translated to absolute (heap-relative) pointers during script loading

  .

  ## Selectors are very important in SCI. They can be either methods or object/class-relative variables, and this makes the interpretation of SCI operations like `

  Each class comes with two two-dimensional tables. The first table contains selector values and selector indices

  Those can be used as an index into vocab.997, where the selector names are stored as strings.

  for each variable selector. The second table contains selector indices and script-relative method offsets. Objects look nearly identical, but they do not contain the list of selector indices for variable selectors, since those can be looked up at the class they were instantiated from (their "species", which happens to be one of the variable selectors).

  Now, whenever a selector is sent for, the engine has to determine the right action to take. FreeSCI first determines whether the selector is a variable selector, by looking for it in the list of variable selector indices of the species class of the object that the "``

  . If it is, the selector value is either read (if no parameter was provided to the ``

  `

  ## SCI provides three distinct ways for invocating a function

  :

  - Calling exported functions (``
  - Calling selector methods (``
  - Calling PC-relative addresses (``

  Exported functions are called by providing a script number and an exported function number (which is then looked up in the script's Type 7 block). They use the object they were called from to look up local variables and selectors for ``

  Selector methods are called by providing an object and a selector index. The selector index gets looked up in the object's selector tables, and, if it is used for a method, this method gets invocated. The provided object is used for local references.

  PC-relative calls only make sense inside scripts, since they jump to a position relative to the ``

  ## SCI bytecode can address four types of variables (not counting the variable selectors). Those variable types are:

  These are the variables stored in Type 10 script blocks. They are shared between the objects and classes of each script.

These variables are the local variables of script 0.

Those variables are stored on the stack. They are relative to the stack frame of the current method, so space for them must be allocated before they can be used. This is commonly done by using the ``

Parameters are stored on the stack below the current stack frame, as they technically belong to the calling function. They can be modified, if neccessary.

## By Version 1.0, 7. July 1999

When the interpreter initializes, it sets up a timer for 60 hertz (one that "ticks" 60 times per second). This timer does two things: it lets the so-called servers execute (most notably, the sound player and input manager) and it "feeds" the internal game clock. This 60 hz. "systick" is used all over the place. For example, it is accessible using the KGetTime kernel function. Some graphic effects depend on it, for example the "shake screen" effect. In SCI1, it is also used for timing in the palette fades. And naturally, it is used in the KWait kernel call.

Basically, the initialization proceeds as follows:

- Save the machine state for restarting the game later on

  This is quite interesting, the KRestartGame kernel call is implemented using a simple setjmp/longjmp pair.

- The right game object is found by looking in the "dispatch table" of script 0. The dispatch table has block type 7, and is an array of words. The first entry is a pointer (script relative) to the game object, for instance SQ3. If the game was restarted, the interpreter executes the replay method, play otherwise.

  After looking up the address of the method in the object block, execution is started. It can be viewed as a huge switch statement, which executes con- tinuously. When the last ret statement (in the play or replay method) is met, the interpreter terminates.

  The ExecuteCode function, which contains the mentioned switch statement, is called recursively. It lets other subroutines handle the object complexity, all the ExecuteCode function has is a pointer to the next in- struction. Thus, it is easy to terminate the interpreter; just return from all running instances of ExecuteCode.

  So, how does an SCI program execute? Well, the play method is defined in the Game class, and it is never overridden. It consists of a huge loop which calls Game::doit continuously, followed by a pause according to the selected animation speed. That is, the script, not the interpreter, handles animation speed. Notice how the debugger very often shows the statement sag $12 upon entering the debugger? This instruction resides in Game::play, and the break occurs here because of a KWait kernel call which is executed right before that instruction. This wait takes the most execution time, so therefore the debug break is most likely to be A game programmer would then override Game::doit and place the game specific main loop here (still, Game::doit is almost identical from game to game). Execution of the Game::play main loop stops when an event causes global variable 4 to be non-zero. The last ret instruction is met, and the interpreter terminates.

## SCI0 (and probably SCI1 as well) uses a heap consisting of 0xffff bytes of memory; this size corresponds to the size of one i386 real-mode memory segment minus one.

## The original heap starts with 200 separate entries with a size of four bytes. Each of those entries appears to be a pointer to "hunk" memory, which is separate from the heap and not covered here. The actual heap base pointer points to the first byte that is not part of these pointers.

## A memory handle consists of two consecutive unsigned 16 bit integers:

- The memory block size

- The heap address of the next memory handle

in this sequence.

Memory handles are stored inside of the heap; they delimit the holes in the heap by indexing each other, with the exception of the last handle, which points to zero.

## The list is initialized to 0. Memory handle #0 is set to contain 0xffff minus the size required by the memory handles (800 bytes) to a total of 0xfcdf, the pointer to the next free index is set to 0x0.

## The memory allocation function takes one parameter; this is the requested allocation block size. If it is 0, the function aborts. Otherwise, the size is increased by 2 (and then again by 1, if it is odd, for alignment purposes).

After the memory allocation algorithm finds a sufficiently large memory hole, it allocates its memory by splitting the memory hole and allocating the lower part (or by swallowing the upper part if its size would be less than four). It adjusts the previous memory handle (which used to point to the start of the now allocated part of the heap) to point to the next hole, and then goes on to write its size to the first two bytes of its newly allocated home.

If no sufficiently large memory hole can be found, the function returns 0; otherwise, it returns a heap pointer to the start of the allocated block (i.e. to the two bytes that carry the block's size).

Memory deallocation does this process in reverse; it also merges adjacent memory holes to prevent memory fragmentation.

## , and

Version 1.0, 6. July 1999

This document describes thee design of the Sierra PMachine (the virtual CPU used for executing SCI programs). It is a special CPU, in the sense that it is designed for object oriented programs.

There are three kinds of memory in SCI: Variables, objects, and stack space. The stack space is used in a Last-In-First-Out manner, and is primarily used for temporary space in a routine, as well as passing data from one routine to another. Note that the stack space is used bottom-up by the original in- terpreter, instead of the more usual top-down. I don't know if this has any significance for us.

Scripts are loaded into the PMachine by creating a memory imagee of it on the heap. For this reason, the script file format may seem a bit obscure at times. It is optimized for in-memory performance, not readability. It should be mentioned here that a lot of fixup stuff is done by the interpreter. In the script files, all addresses are specified as script-relative. These are converted to absolute offsets. The species and superClass fields of all objects are converted into pointers to the actual class etc.

There are four types of variables. These are called global, local, temporary, and parameter. All four types are simple arrays of 16-bit words. A pointer is kept for each type, pointing to the list that is currently active. In fact, only the global variable list is constant in memory. The other pointers are changed frequently, as scripts are loaded/unloaded, routines called, etc. The variables are always referenced as an index into the variable list. I'll explain the four types below - the names in parantheses will be used occasionally in the rest of the text:

## This variable type is called "local" because it belongs to a specific script. Each script may have its own set of local variables, defined by script block type 10. As long as the code from a specific script is running, the local variables for that script are "active" (pointed to by the mentioned pointer).

## These, like the local variables, reside in script space (in fact, they are the local variables of script 0!). But the pointer to them remains constant for the whole duration of the program.

## These are allocated by specific subroutines in a script. They reside on the PMachine stack and are allocated by the link opcode. The temp variables are automatically discarded when the subroutine returns.

## These variables also reside on the stack. They contain information passed from one routine to another. Any routine in SCI is capable of taking a variable number of parameters, if need be. This is possible because a list size is pushed as the first thing before calling a routine. In addition to this, a frame size is passed to the call* functions.

## While two adjacent variables may be entirely unrelated, the contents of an object is always related to one task. The object, like the variable tables, provides storage space. This storage space is called properties. Depending on the instructions used, a property can be referred to by index into the object structure, or by property IDs (PIDs). For instance, the name property has the PID 17h, but the offset 6. The property IDs are assigned by the SCI compiler, and it is the "compatible" way of accessing object data. Whereas the offset method is used only internally by an object to access its own data, the PID method is used externally by objects to read/write the data fields of other objects. The PID method is also used to call methods in an object, either by the object itself, by another object, or by the SCI inter- preter. Yes, this really happens sometimes.

## The PMachine can be said to have a number of registers, although none of them can be accessed explicitly by script code. They are used/changed implicitly by the script opcodes:

- IP - the instruction pointer.

  Points to the currently executing instruction

- The PMachine, apart from the actual instruction pointer, keeps a record of which object is currently executing.

## The PMachine CPU potentially has 128 instructions (however, a couple of these are invalid and generate an error). Some of these instructions have a flag which specify whether the opcode has byte- or word-sized operands (I will refer to this as variably-sized parameters, as opposed to constant parameters). Other instructions have only one calling form. These instructions simply disregard the operand size flag. Ideally, however, all script instructions should be prepared to take variably-sized operands. Yet another group of instructions take both a constant parameter and a variably-sized parameter. The format of an opcode byte is as follows:

### Certain instructions (in particular, branching ones) take relative addresses as a parameter. The actual address is calculated based on the instruction after the branching instruction itself. In this example, the bnt instruction, if the branch is made, jumps over the ldi instruction.

            eq?
            bnt +2
            ldi byte 2
            push

### The callb and calle instructions take a so-called dispatch index as a parameter. This index is used to look up an actual script address, using the so-called dispatch table. The dispatch table is located in script block type 7 in the script file. It is a series of words - the first one, as in so many other places in the script file, is the number of entries.

### In every call instruction, a value is included which determines the size of the parameter list, as an offset into the stack. This value discounts the list size pushed by the SCI code. For instance, consider this example from real SCI code:

         pushi 3 ; three parameters passed
         pushi 4 ; the screen flag
         pTos x ; push the x property
         pTos y ; push the y property
         callk OnControl, 6

Notice that, although the callk line specifies 6 bytes of parameters, the kernel routine has access to the list size (which is at offset 8)!

### These are internal errors in the interpreter. They are usually caused by buggy script code. The PErrors end up displaying an "Oops!" box in the original interpreter (it is interesting to see how Sierra likes to believe that PErrors are caused by the user - judging by the message "You did something we weren't expecting"!). In the original interpreter, specifying -d on the command line causes it to give more detailed information about PErrors, as well as activating the internal debugger if one occurs.

### The key to finding a specific class lies in the class table. This class table resides in VOCAB.996, and contains the numbers of scripts that carry classes. If a script has more than one class defintion, the script number is repeated as necessary. Notice how each script number is followed by a zero word? When the interpreter loads a script, it checks to see if the script has classes. If it does, a pointer to the object structure is put in this empty space.

### The instructions are described below. I have used Dark Minister's text on the subject as a starting point, but many things have changed; stuff explained more thoroughly, errors corrected, etc. The first 23 instructions (up to, but not including, bt) take no parameters.

These functions are used in the pseudocode explanations:

- The following rules apply to opcodes:

  - - - op 0x00: bnot (1 byte)
      - op 0x01: bnot (1 byte)
      Binary not:

    - - op 0x02: add (1 byte)
      - op 0x03: add (1 byte)
      Addition:

    - - op 0x04: sub (1 byte)
      - op 0x05: sub (1 byte)
      Subtraction:

    - - op 0x06: mul (1 byte)
      - op 0x07: mul (1 byte)
      Multiplication:

    - - op 0x08: div (1 byte)
      - op 0x09: div (1 byte)
      Division:

          acc = pop() / acc;

      Division by zero is caught => acc = 0.

    - - op 0x0a: mod (1 byte)
      - op 0x0b: mod (1 byte)
      Modulo:

           acc = 0.

    - - op 0x0c: shr (1 byte)
      - op 0x0d: shr (1 byte)
      Shift Right logical:

          > acc;/

    - - op 0x0e: shl (1 byte)
      - op 0x0f: shl (1 byte)
      Shift Left logical:

    - - op 0x10: xor (1 byte)
      - op 0x11: xor (1 byte)
      Exclusive or:

    - - op 0x12: and (1 byte)
      - op 0x13: and (1 byte)
      Logical and:

    - - op 0x14: or (1 byte)
      - op 0x15: or (1 byte)
      Logical or:

    - - op 0x16: neg (1 byte)
      - op 0x17: neg (1 byte)
      Sign negation:

    - - op 0x18: not (1 byte)
      - op 0x19: not (1 byte)
      Boolean not:

    - - op 0x1a: eq? (1 byte)
      - op 0x1b: eq? (1 byte)
      Equals?:

    - - op 0x1c: ne? (1 byte)
      - op 0x1d: ne? (1 byte)
      Is not equal to?

    - - op 0x1e: gt? (1 byte)
      - op 0x1f: gt? (1 byte)
      Greater than?

           acc);/

    - - op 0x20: ge? (1 byte)
      - op 0x21: ge? (1 byte)
      Greater than or equal to?

          = acc);/

    - - op 0x22: lt? (1 byte)
      - op 0x23: lt? (1 byte)
      Less than?

    - - op 0x24: le? (1 byte)
      - op 0x25: le? (1 byte)
      Less than or equal to?

    - - op 0x26: ugt? (1 byte)
      - op 0x27: ugt? (1 byte)
      Unsigned: Greater than?

           acc);/

    - - op 0x28: uge? (1 byte)
      - op 0x29: uge? (1 byte)
      Unsigned: Greather than or equal to?

          = acc);/

    - - op 0x2a: ult? (1 byte)
      - op 0x2b: ult? (1 byte)
      Unsigned: Less than?

    - - op 0x2c: ule? (1 byte)
      - op 0x2d: ule? (1 byte)
      Unsigned: Less than or equal to?

    - - op 0x2e: bt W relpos (3 bytes)
      - op 0x2f: bt B relpos (2 bytes)
      Branch relative if true

    - - op 0x30: bnt W relpos (3 bytes)
      - op 0x31: bnt B relpos (2 bytes)
      Branch relative if not true

    - - op 0x32: jmp W relpos (3 bytes)
      - op 0x33: jmp B relpos (2 bytes)
      Jump

    - - op 0x34: ldi W data (3 bytes)
      - op 0x35: ldi B data (2 bytes)
      Load data immediate

    - - op 0x36: push (1 byte)
      - op 0x37: push (1 byte)
      Push to stack

    - - op 0x38: pushi W data (3 bytes)
      - op 0x39: pushi B data (2 bytes)
      Push immediate

    - - op 0x3a: toss (1 byte)
      - op 0x3b: toss (1 byte)
      TOS subtract

    - - op 0x3c: dup (1 byte)
      - op 0x3d: dup (1 byte)
      Duplicate TOS element

    - - op 0x3e: link W size (3 bytes)
      - op 0x3f: link B size (2 bytes)

    - - op 0x40: call W relpos, B framesize (4 bytes)
      - op 0x41: call B relpos, B framesize (3 bytes)
      Call inside script.

    - - op 0x42: callk W kfunct, B kparams (4 bytes)
      - op 0x43: callk B kfunct, B kparams (3 bytes)
      Call kernel function (see Kfunctions)

    - - op 0x44: callb W dispindex, B framesize (4 bytes)
      - op 0x45: callb B dispindex, B framesize (3 bytes)
      Call base script

    - - op 0x46: calle W script, B dispindex, B framesize (5 bytes)
      - op 0x47: calle B script, B dispindex, B framesize (4 bytes)
      Call external script

    - - op 0x48: ret (1 byte)
      - op 0x49: ret (1 byte)
      Return: returns from an execution loop started by call, calle, callb, send, self or super.

    - - op 0x4a: send B framesize (2 bytes)
      - op 0x4b: send B framesize (2 bytes)
      Send for one or more selectors. This is the most complex SCI operation (together with self and class).

      Send looks up the supplied selector(s) in the object pointed to by the accumulator. If the selector is a variable selector, it is read (to the accumulator) if it was sent for with zero parameters. If a parameter was supplied, this selector is set to that parameter. Method selectors are called with the specified parameters.

      The selector(s) and parameters are retreived from the stack frame. Send first looks up the selector ID at the bottom of the frame, then retreives the number of parameters, and, eventually, the parameters themselves. This algorithm is iterated until all of the stack frame has been "used up". Example:

    - - op 0x4c
      - op 0x4d
      - op 0x4e
      - op 0x4f
      These opcodes don't exist in SCI.

    - - op 0x50: class B function (3 bytes)
      - op 0x51: class W function (2 bytes)
      Get class address. Sets the accumulator to the memory address of the specified ``

    - - op 0x52
      - op 0x53
      These opcodes don't exist in SCI.

    - - op 0x54: self B stackframe (2 bytes)
      - op 0x55: self B stackframe (2 bytes)
      Send to self. This operation is the same as the send operation, except that it sends to the current object instead of the object pointed to by the accumulator.

    - - op 0x56: super W class, B stackframe (4 bytes)
      - op 0x57: super B class, B stackframe (3 bytes)
      Send to any class. This operation is the same as the send operation, except that it sends to an arbitrary ``

    - - op 0x58: &rest W paramindex (3 bytes)
      - op 0x59: &rest B paramindex (2 bytes)
      Pushes all or part of the ParmVar list on the stack. The number specifies the first parameter variable to be pushed. I'll give a small example. Suppose we have two functions:

      function a(y,z) and function b(x,y,z)

      function b wants to call function a with its own y and z parameters. Easy job, using the the normal lsp instruction. Now suppose that both function a and b are designed to take a variable number of parameters:

      function a(y,z,...) and function b(x,y,z,...)

      Since lsp does not support register indirection, we can't just push the variables in a loop (as we would in C). Instead this function is used. In this case, the instruction would be &rest 2, since we want the copying to start from y, the second parameter.

    - - op 0x5a: lea W type, W index ( bytes)
      - op 0x5b: lea B type, B index ( bytes)
      Load Effective Address

      The variable type is a bit-field used as follows:

      the number of the variable list to use

      Because it is so hard to explain, I have made a transcription of it here:

          short *vars[4];

          int acc;

          int lea(int vt, int vi)
          {
            return &((vars[(vt >> 1) & 3])[vt & 0x10 ? vi+acc : vi]);
          }

    - - op 0x5c: selfID (1 bytes)
      - op 0x5d: selfID (1 bytes)
      Get 'self' identity: SCI uses heap pointers to identify objects, so this operation sets the accumulator to the address of the current object.

    - - op 0x5e
      - op 0x5f
      These opcodes don't exist in SCI.

    - - op 0x60: pprev (1 bytes)
      - op 0x61: pprev (1 bytes)
      Push prev: Pushes the value of the prev register, set by the last comparison bytecode (eq?, lt?, etc.), on the stack.

    - - op 0x62: pToa W offset (3 bytes)
      - op 0x63: pToa B offset (2 bytes)
      Property To Accumulator: Copies the value of the specified property (in the current object) to the accumulator. The property is specified as an offset into the object structure.

    - - op 0x64: aTop W offset (3 bytes)
      - op 0x65: aTop B offset (2 bytes)
      Accumulator To Property: Copies the value of the accumulator into the specified property (in the current object). The property number is specified as an offset into the object structure.

    - - op 0x66: pTos W offset (3 bytes)
      - op 0x67: pTos B offset (2 bytes)
      Property To Stack: Same as pToa, but pushes the property value on the stack instead.

    - - op 0x68: sTop W offset (3 bytes)
      - op 0x69: sTop B offset (2 bytes)
      Stack To Property: Same as aTop, but gets the new property value from the stack instead.

    - - op 0x6a: ipToa W offset (3 bytes)
      - op 0x6b: ipToa B offset (2 bytes)
      Incement Property and copy To Accumulator: Increments the value of the specified property of the current object and copies it into the accumulator. The property number is specified as an offset into the object structure.

    - - op 0x6c: dpToa W offset (3 bytes)
      - op 0x6d: dpToa B offset (2 bytes)
      Decrepent Property and copy to Accumulator: Decrements the value of the specified property of the current object and copies it into the accumulator. The property number is specified as an offset into the object structure.

    - - op 0x6e: ipTos W offset (3 bytes)
      - op 0x6f: ipTos B offset (2 bytes)
      Increment Property and push to Stack Same as ipToa, but pushes the result on the stack instead.

    - - op 0x70: dpTos W offset (3 bytes)
      - op 0x71: dpTos B offset (2 bytes)
      Decrement Property and push to stack: Same as dpToa, but pushes the result on the stack instead.

    - - op 0x72: lofsa W offset (3 bytes)
      - op 0x73: lofsa B offset (2 bytes)
      Load Offset to Accumulator:

          acc = pc + offset

      Adds a value to the post-operation pc and stores the result in the accumulator.

    - - op 0x74: lofss W offset (3 bytes)
      - op 0x75: lofss B offset (2 bytes)
      Load Offset to Stack:

          push(pc + offset)

      Adds a value to the post-operation pc and pushes the result on the stack.

    - - op 0x76: push0 (1 bytes)
      - op 0x77: push0 (1 bytes)
      Push 0:

    - - op 0x78: push1 (1 bytes)
      - op 0x79: push1 (1 bytes)
      Push 1:

    - - op 0x7a: push2 (1 bytes)
      - op 0x7b: push2 (1 bytes)
      Push 2:

    - - op 0x7c: pushSelf (1 bytes)
      - op 0x7d: pushSelf (1 bytes)
      Push self:

    - - op 0x7e
      - op 0x7f
      These operations don't exist in SCI.

    - - op 0x80 - 0xfe: [ls+-][as][gltp]i? W index (3 bytes)
      - op 0x81 - 0xff: [ls+-][as][gltp]i? B index (2 bytes)
      The remaining SCI operations work on one of the four variable types. The variable index is retreived by taking the heap pointer for the specified variable type, adding the `

      Used as with all other opcodes with variably-sized parameters:

      The type of variable to operate on:

      Whether to use the accumulator or the stack for operations:

      Whether to use the accumulator as a modifier to the supplied index:

      The type of execution to perform:

      Always 1 (identifier for these opcodes)

      `

Example: "sagi 2" would Store the Accumulator in the Global variable indexed with 2 plus the current accumulator value (this rarely makes sense, obviously). "+sp 6" would increment the parameter at offset 6 (the third parameter, not counting the argument counter), and push it on the stack.

##

In SCI0, calls to the SCI kernel are initiated by using the `callk` opcode. `callk` has the opcode `0x42` or `0x43`; `0x42` takes one 16 bit little endian and one 8 bit paramter, ``

Opcode summary:

- op 0x42: callk W kfunct, B kparams (4 bytes)
- op 0x43: callk B kfunct, B kparams (3 bytes)

The number of parameters passed to the kernel function are determined by kparam. A total number of (kparams+2) bytes are removed from the local stack and passed on to the kernel function. The first two of those bytes are apparently always created by pushing the number of following bytes. For example, if Load(view, 10) is called, then we've got two word parameters, "view" (0x0080) and "10" (0x000a). So the callk function would have kparams set to 4; this value would be pushed to the stack first, followed by the two parameters. So the stack would look like this (left means lower address, byte ordering little endian):

``

before calling Load().

Return values are returned into the accumulator, unless stated otherwise. If return type is stated as (void), then the accumulator is not modified.

## SCI0 uses only little endian 16 bit integer values for parameters. However, this document distinguishes between different uses of those integers by defining the following variable types:

- ``
- ``
- ``
- ``
- ``
- ``
- ``
- ``

Parameters in brackets (like "[foo]") are optional.

Most functions exit gracefully if either a NULL HeapPtr or DblList is provided.

##

## kfunct 0x00: Load word ResType, word ResNr

- `(word)` ``
- `(word)` ``

Returns: `(&FarPtr)` : A ``

Loads a resource. The returned HeapPtr points to a special point on the heap where a pointer (32 bits) to the memory location of the specified resource is located. If the resource type equals sci_memory, the resource number is interpreted as a memory size instead; the specified number of bytes is allocated dynamically, and a handle returned.

## kfunct 0x01: UnLoad word ResType, word ResNr

- `(word)` ``
- `(word)` ``

Returns: `(void)`

This function unloads a resource identified by its ResType and ResNr, NOT by the HeapPtr it has been loaded to, except for sci_memory resources, where the parameters are the memory resource type and the handle.

## kfunct 0x02: ScriptID word ScriptNr, word DispatchNr

- `(word)` ``
- `(word)` ``

Returns: `(HeapPtr)` : The address pointed to by the specified element of the dispatch/exports table (script block type #7)

This function returns the address pointed to by an element of a script's dispatch table.

## kfunct 0x03: DisposeScript word ScriptNumber

- `(word)` ``

Returns: `(void)`

Disposes a script. Unloads it, removes its entries from the class table, and frees the associated heap memory.

##

    kfunct 0x04:Clone

    HeapPtr object

- `(HeapPtr)` ``

Returns: `(HeapPtr)` The address of the clone

This function clones a Class or Object by copying it as a whole and mofifying the -info- selector so that it contains 1. Objects with -info- set to 0x8000 (Classes) are stripped of their selector name area, and both Objects and Classes are stripped of the function selector area.

##

    kfunct 0x05: DisposeClone

    HeapPtr clone

- `(HeapPtr)` ``

Returns: `(void)`

Frees all memory associated with a cloned object (as produced by Clone()).

##

    kfunct 0x06: IsObject

    HeapPtr suspected_object

- `(HeapPtr)` ``

Returns: `(int)` 1 if there is an object at the specified address, 0 if not.

This function checks whether the supplied heap pointer is valid and returns 0 if not, then proceeds to testing whether an object is at the indexed heap position. If it is, 1 is returned, 0 otherwise.

##

## kfunct 0x08: DrawPic word PicNr[, word Animation, word Flags, word DefaultPalette]

- `(word)` ``
- `(word)` `
  - The animation is executed when kAnimate() (see KAnimate) is invoked. If not specified, it is assumed to be the same animation as last time.
  - `(word)` `
    - ``
    - ``
    If not specified, it defaults to 1.

    `
  - `(word)` ``

  Returns: `(void)`

  The second parameter does not appear to affect anything. In QfG1, it appears to be set to 0x64 constantly. DefaultPalette is used to differentiate between day and night in QfG1. Palette 1 is used for "night" pictures, Palette 0 for "day" pictures there. The picture is drawn to the background image (which is used for restauration of everything with the exception of the mouse pointer). To bring it to the foreground, Animate() must be used.

  `

## kfunct 0x09: Show

Returns: `(void)`

Sets the PicNotValid flag to 2.

## kfunct 0x0a: PicNotValid [(word) NewPicNotValid]

- [`(word)` ``

Returns: `(word)` : The previous value of the "PicNotValid" flag

This sets the PicNotValid flag that determines whether or not the current background picture should be considered "valid" by the other kernel functions.

## kfunct 0x0b: Animate [DblList ViewList], [word cycle]

- [`(DblList)` ``
- `(word)` <unknown>

Returns: `(void)`

This function draws a background picture plus some views to the foreground. If the background picture had not been drawn previously, it is animated with the animation style set during kDrawPic (see KDrawPic). Drawing the views is a rather complex issue. Refer to LarsAnim for its description.

## ?? kfunct 0x0c: SetNowSeen DblList ViewList

- `(DblList)` ``

Returns: `(void)`

##

    kfunct 0x0d: NumLoops

    HeapPtr object

- `(HeapPtr)` ``

Returns: `(word)` The number of loops in the view

This function looks up the view selector in the specified object, loads the view resource associated with it, and checks for the number of animation loops in the view.

##

    kfunct 0x0e: NumCels

    HeapPtr object

- `HeapPtr` ``

Returns: `(word)` The number of cels in the loop

This function looks up one specific loop in a specific view (both are taken from selectors with the same name from the object pointed to by the parameter) and returns the number of cels (animation frames) in it.

##

    kfunct 0x0f: CelWide

    HeapPtr object

- `(HeapPtr)` ``

Returns: `(word)` The cel's width

This function looks up the width of a specific cel. cel, frame, and view are retreived from the selectors of the specified object.

##

    kfunct 0x10: CelHigh

    HeapPtr object

- `(HeapPtr)` ``

Returns: `(word)` The height of the cel

View, loop, and cel number are retreived from the object passed as parameter.

## kfunct 0x11: DrawCel word view, word loop, word cel, Point pos, word priority

- ``
- ``
- ``
- ``
- ``

Returns: `(void)`

Explicitly draws a cel, specified by the complete tuple (view, loop, cel), to a specified position. Invalid loop/cel values are assumed to be 0.

##

    kfunct 0x12: AddToPic

    DblList picviews

- `(DblList)` ``

Returns: `(void)`

This function stores the list of PicViews for later use by the Animate() syscall. See KAnimate for more details.

## kfunct NewWindow Rect Boundaries, HeapPtr Title, word Flags, word Priority, word FGColor, word BGColor

- `(Rect)` ``
- `(HeapPtr)` ``
- `(word)` `
  - `(word)` ``
  - `(word)` ``
  - `(word)` ``

  Returns: `(HeapPtr)` : The position of the window structure on the heap

  This function creates a window (see also LarsWindows), sets this window as the active port, draws the window (if neccessary), and returns with the window's heap address.

  `

## kfunct 0x14: GetPort

Returns: `(HeapPtr)` : A pointer to a record with the internal representation of the currently active port.

Returns a heap pointer to a port structure.

## kfunct 0x15: SetPort HeapPtr NewPort

- `(HeapPtr)` ``

Returns: `(void)`

This selects the new port which many kernel functions will draw to.

If 0 is passed, the window manager port is selected. The picture window is not accessible using this call. Only other kernel calls like KDrawPic may activate the picture window - and they always save the old port and restore it before they return.

## kfunct 0x16: DisposeWindow HeapPtr Window

- `(HeapPtr)` ``

Returns: `(void)`

Destroys a window and frees the associated heap structure.

## kfunct 0x17: DrawControl HeapPtr Control

- `(HeapPtr)` ``

Returns: `(void)`

This function draws a Control (see LarsWindows for details). Please note that the correct port must be selected beforehand.

## kfunct 0x18: HiliteControl HeapPtr Control

- `(HeapPtr)` ``

Returns: `(void)`

This function is used to highlight a control by drawing it with an inverted color scheme. It requires the correct port to be set beforehand. See LarsWindows for details on the windowing/Control system.

## kfunct 0x19: EditControl HeapPtr Control

- `(HeapPtr)` ``

Returns: `(void)`

This function will give control to the kernel and allow the editing of a type 3 (Edit window) Control (see LarsWindows for a description of the control system). If it is called to edit a Control which is not of type 3, it returns without error. Please note that the correct port (usually the window which the Control was drawn in) must be selected beforehand.

##

    kfunct 0x1a: TextSize

    HeapPtr dest, HeapPtr src, word font[, word maxwidth]

- `(HeapPtr)` ``
- `(HeapPtr)` ``
- `(word)` ``
- `(word)` ``

Returns: `(void)`

This function calculates the width and height the specified text will require to be displayed with the specified font and the specified maximum width. The result will be written to the (you guessed it) specified destination on the heap. The result is a rectangle structure: The first four bytes equal to zero, the next word is the height, and the last word is the width.

##

    kfunct 0x1b: Display

    String text, word commands...

- `` (String) ``
- `

  Returns: `(void) or (&FarPtr)` (see above)

  This function executes the specified commands, then draws the supplied text to the active port (unless command 108 was executed).

  `

## kfunct 0x1c: GetEvent word Flags, HeapPtr Event

- `
  - ``

  Returns: ``

  This function fills an Event object with data from the event queue. The results are written to the "type", "message" and "modifiers" selectors. See LarsEvents for details.

  `

## kfunct 0x1d: GlobalToLocal HeapPtr Event

- ``

Returns: ``

This function converts a screen-relative event to a port-relative one, using the currently active port.

## kfunct 0x1e: LocalToGlobal HeapPtr Event

- ``

Returns: ``

This function converts a port-relative event to a screen-relative one, using the currently active port.

## kfunct 0x1f: MapKeyToDir HeapPtr Event

- ``

Returns: ``

This function converts a keyboard event to a movement event, if possible. Otherwise, the function returns without error. See LarsEvents for details.

##

    kfunct 0x20: DrawMenuBar

    word mode

- `(word)` ``

Returns: `(void)`

Either draws or clears (overdraws with black) the menu bar.

## kfunct 0x21: MenuSelect HeapPtr event[, word flag]

- ``
- ``

Returns: `(word)` The menu index of a selected option, -1 if no menu option was selected, or 0 if the event passed through all of the menu system's filters.

This function interprets the event passed to it by running several checks. First, it tries to determine whether the menu system was activated by pressing the ESC key or clicking on the menu bar. In this case, the interpreter takes over and waits for the player to select a menu option. It then returns the menu option selected (menu number, starting at 1, in the upper 8 bits, item number, starting at 1 as well, in the lower part) or -1 if no active menu item was selected. In any case, the event is claimed. If the menu system was not activated by the event, it checks the event against the key commands or Said Blocks associated with each menu entry. If there is a match, the menu coordinate tuple is returned and the event is claimed, otherwise, 0 is returned.

##

    kfunct 0x22: AddMenu

    HeapPtr title, HeapPtr content

- `(HeapPtr)` ``
- `(HeapPtr)` ``

Returns: `(void)`

This function adds a menu to the menu bar. The menu title is passed in the first parameter, the second parameter contains a heap pointer to the menu entries. They are contained in one single string; the following special characters/character combinations are used:

## kfunct 0x23: DrawStatus HeapPtr text

- `(HeapPtr)` ``

Returns: `(void)`

Draws the specified text to the title bar

##

    kfunct 0x24: Parse

    HeapPtr event, HeapPtr input

- `(HeapPtr)` ``
- `(HeapPtr)` ``

Returns: `(word)` 1 on success, 0 otherwise

This function parses the input line and generates a parse event (type 0x80). See LarsDMParser and LarsEvents for details.

##

    kfunct 0x:25: Said

    HeapPtr said_block

- `(HeapPtr)` ``

Returns: `(word)` 1 if the line last parsed meets the criteria of the supplied said_block, 0 otherwise.

This function is only invoked after Parse() was called, and works on output generated by this function. See LarsDMParser and LarsEvents for details.

## kfunct 0x26: SetSynonyms HeapPtr list

- ``

Returns: `(void)`

This function sets the synonyms used by the parser. Synonyms are used to replace specified word groups with other word groups. The list contains a collection of script objects; all synonyms defined by the corresponding script (which can be identified by evaluating the 'number' selector of the script object) are added to the list of active synonyms.

##

    kfunct 0x27: HaveMouse

Returns: `(word)` 1 if a mouse is available, 0 if not.

This function simply returns a flag containing the availability of a pointing device.

##

    kfunct 0x28: SetCursor

    word resource, word visible[, Point coordinates]

- `(word)` ``
- `(word)` ``
- `(Point)` ``

Returns: `(void)`

This function can change the appearance and position of the mouse pointer. If no position is provided, the position remains unchanged.

## kfunct 0x29: FOpen String fname, word mode

- ``
- ``

Returns: `(word) a file handle on success, 0 on error`

Tries to open or create a file in the CWD with the specified file name. The following modes are valid:

## kfunct 0x2a: FPuts word filehandle, String data

- ``
- ``

Returns: `(void)`

Writes a zero-terminated string to a file

## kfunct 0x2b: FGets String dest, word maxsize, word handle

- ``
- ``
- ``

Returns: `(void)`

## kfunct 0x2c: FClose word filehandle

- ``

Returns: `(void)`

Closes a previously opened file.

## kfunct 0x2d: SaveGame String game_id, word save_nr, String save_description, String version

- ``
- ``
- ``
- ``

Returns: `(word)` 1 on success, 0 if an error occured while saving

This function saves the game state (heap, windows, call stack, view list, sound state etc.) to the savegame with the numeric id ``

## kfunct 0x2e: RestoreGame String game_id, word save_nr, String version

- ``
- ``
- ``

Returns: `(void)`

This function restores a previously saved game. It should only return if restoring failed.

## kfunct 0x2f: RestartGame

Returns: `never`

If this function is invoked, the following things happen:

- After this is done, the engine restarts at a certain point (see LSExecLoop), re-initializes the stack, and executes the replay method of the game object.

##

    kfunct 0x30: GameIsRestarting

Returns: `(word)` 1 if the game is restarting, 0 if not

## kfunct 0x31: DoSound word action, ...

- ``

Returns: (see below)

'action' may be one of the following:

- See individual descriptions below for more information.

## kfunct 0x31: DoSound word 0, Object sound_obj

- ``
- ``

Returns: `(void)`

Initializes the specified sound object. This will set the 'status' selector of the object to 1 ('initialized'), and load the sound indicated by the 'number' selector into the sound driver.

## kfunct 0x31: DoSound word 1, Object sound_obj

- ``
- ``

Returns: `(void)`

Starts to play the song represented by the specified sound object. This will also set the 'status' selector of the object to 2 ('playing').

## kfunct 0x31: DoSound word 2

- ``

Returns: `(void)`

No action appears to be associated with this subfunction call.

## kfunct 0x31: DoSound word 3, Object sound_obj

- ``
- ``

Returns: `(void)`

Removes the song indexed by a sound object from the sound server song list

## kfunct 0x31: DoSound word 4, word state

- ``
- ``

Returns: `(void)`

This function completely mutes or un-mutes the sound subsystem.

## kfunct 0x31: DoSound word 5, Object sound_obj

- ``
- ``

Returns: `(void)`

Stops playing the song represented by the specified sound object. This will set the object's 'state' selector to 0 ('stopped').

## kfunct 0x31: DoSound word 6, Object sound_obj

- ``
- ``

Returns: `(void)`

Suspends the song associated with the specified sound object. Its state is buffered, so that it can be resumed later on. The sound object's 'state' selector is set to 3 ('suspended').

## kfunct 0x31: DoSound word 7, Object sound_obj

- ``
- ``

Returns: `(void)`

Resumes a previously suspended song. The 'state' selector is set to 2 ('playing').

## kfunct 0x31: DoSound word 8[, word volume]

- ``
- ``

Returns: `(word) The currently set sound volume (0 to 0xf)`

This subfunction retrieves and returns the current sound volume. If a second parameter is supplied the volume will be set to the value of this parameter.

## kfunct 0x31: DoSound word 9, Object sound_obj

- ``
- ``

Returns: `(void)`

Notifies the sound server that a sound object was modified. The song priority and number of loops (stored in the 'priority' and 'loop' selectors, respectively) are re-evaulated by the sound system.

## kfunct 0x31: DoSound word 0xa, Object sound_obj

- ``
- ``

Returns: `(void)`

Fades the specified song. Fading takes approximately two seconds. The song status is set to 'stopped' (0) afterwards.

## kfunct 0x31: DoSound word 0xb

- ``

Returns: `(word)` 1 if the sound driver was installed successfully, 0 if not

## kfunct 0x31: DoSound word 0xc

- ``

Returns: `(void)`

Stops all music and sound effects.

##

    kfunct 0x32: NewList

Returns: `(DblList)` The address of a new node list on the heap

This function allocates and initializes a node list containing no elements.

##

    kfunct 0x33: DisposeList

    NodeList list

- `(NodeList)` ``

Returns: `(void)`

Frees all memory associated to a list

##

    kfunct 0x34: NewNode

    word key, word value

- `(word)` ``
- `(word)` ``

Returns: `(Node)` A new node

This function allocates a new node and initializes it with the key and value passed as parameters.

##

    kfunct 0x35: FirstNode

    DblList list

- `(DblList)` ``

Returns: `(Node)` The first node of the list, or 0 if the list is empty

##

    kfunct 0x36: LastNode

    DblList list

- `(DblList)` ``

Returns: `(Node)` The last node of the list, or 0 if the list is empty

## kfunct 0x37: EmptyList DblList list

- ``

Returns: `(int)` 1 if ``

##

    kfunct 0x38: NextNode

    Node node

- `(Node)` ``

Returns: `(Node)` The node following the supplied node, or 0 if none is available

##

    kfunct 0x39: PrevNode

    Node node

- `(Node)` ``

Returns: `(Node)` The supplied node's predecessor, or 0 if the node has no predecessor

##

    kfunct 0x3a: NodeValue

    Node node

- `(Node)` ``

Returns: `(word)` The value associated with the specified node

## kfunct 0x3b: AddAfter DblList list, Node ref_node, Node new_node

- ``
- ``
- ``

Returns: `(void)`

This function inserts ``

##

    kfunct 0x3c: AddToFront

    DblList list, Node node

- `(DblList)` ``
- `(Node)` ``

Returns: `(void)`

This function adds a node to the beginning of a doubly linked list.

##

    kfunct 0x3d: AddToEnd

    DblList list, Node node

- `(DblList)` ``
- `(Node)` ``

Returns: `(void)`

This function adds the specified node to the end of the specified list.

##

    kfunct 0x3e: FindKey

    DblList list, word key

- `(DblList)` ``
- `(word)` ``

Returns: `(Node)` The node containing the key, or 0 if no node contains it

This function searches for a specific key in the nodes of a doubly linked list.

##

    kfunct 0x3f:

    DblList list, word key

- `(DblList)` ``
- `(word)` ``

Returns: `(void)`

This function searches in the supplied list for the specified key and removes the node containing it, if any can be found.

##

    kfunct 0x40: Random

    word min, word max

- `(word)` ``
- `(word)` ``

Returns: `(word)` A random number between min and max (inclusive)

##

    kfunct 0x41: Abs

    word value

- `(word)` ``

Returns: `(word)` The absolute value of the specified parameter

This function interprets the supplied value as a signed value and returns its absolute value.

##

    kfunct 0x42: Sqrt

    word value

- `(word)` ``

Returns: `(word)` The square root of the supplied value

##

    kfunct 0x43: GetAngle

    Point origin, Point destination

- `(Point)` ``
- `(Point)` ``

Returns: `(word)` A positive angle between the two points, relative to the screen coordinate axis.

This function returns approximately the following value: -(180.0/PI * atan2(destination.y - origin.y, destination.x - origin.x)) + 180; Where atan2(double, double) is the libm function.

##

    kfunct 0x44: GetDistance

    Point foo, Point bar

- `(Point)` ``
- `(Point)` ``

Returns: `(int)` The euklidian distance between the points foo and bar

##

    kfunct 0x45: Wait

    word ticks

- `(word)` ``

Returns: `(word)` The time passed in between the finish of the last Wait() syscall

##

    kfunct 0x46: GetTime

    word mode

- `(wrod)` ``

Returns: `(word)` Either the time of day in seconds, or the elapsed number of ticks since the interpreter started.

This function is somewhat strange, because it determines its behaviour not by the value of a parameter passed, but by its presence instead. Please note that the time of day in this case does not distinguish between am and pm.

##

    kfunct 0x47: StrEnd

    HeapPtr string

- `(HeapPtr)` ``

Returns: `(HeapPtr)` The address of the null terminator of the indexed string

##

    kfunct 0x48: StrCat

    HeapPtr dest, HeapPtr source

- `(HeapPtr)` ``
- `(HeapPtr)` ``

Returns: `(HeapPtr)` dest

This function concatenates two strings on the heap.

##

    kfunct 0x49:

    HeapPtr foo, HeapPtr bar[, word length]

- `(HeapPtr)` ``
- `(HeapPtr)` ``
- `(int)` ``

Returns: `(word)` -1 if foo is less than bar, 0 if both are equal, 1 if foo is greater than bar

This function simply encapsulates the libc ``

##

    kfunct 0x4a: StrLen

    HeapPtr string

- `(HeapPtr)` ``

Returns: `(word)` The length of the specified string.

##

    kfunct 0x4b: StrCpy

    HeapPtr dest, HeapPtr src[, word length]

- `(HeapPtr)` ``
- `(HeapPtr)` ``
- `(word)` ``

Returns: `(HeapPtr)` dest

Copies a string, plus the trailing \0 terminator. The length of the string may be reduced with the optional length parameter. This function simply encapsulates the libc ``

##

    kfunct 0x4c: Format

    HeapPtr dest, String format, parameters...

- `(HeapPtr)` ``
- `(String)` ``
- `(misc)` ``

Returns: `(HeapPtr)` dest

This syscall acts as a frontend to the libc ``

##

    kfunct 0x4d: GetFarText

    word resnr, word stringnr, HeapPtr dest

- `(word)` ``
- `(word)` ``
- `(HeapPtr)` ``

Returns: `(HeapPtr)` dest

Retreives a string from a text resource and puts it on the heap.

##

    kfunct 0x4e: ReadNumber

    HeapPtr src

- `(HeapPtr)` ``

Returns: `(word)` The numeric value of the supplied string

This function acts as a frontend to the libc ``

## kfunct 0x4f: BaseSetter HeapPtr view_obj

- ``

Returns: `(void)`

This method is used to set the bounding rectangle of a view. The bounding rectangle is specified by the set {brLeft, brRight, brTop, brBottom} of selectors, which indicate the window-relative boundary points of the object's bounding rectangle. The rectangle defined here is used for colission detection, among other things.

The algorithm employed by FreeSCI to determine these values appears to be either identical or very close to the original algorithm; it depends several of the object's selectors (x, y, z, ystep, view) the width and height of the view indicated by its (view, loop, cel) selectors, and that view's horizontal and vertical pixel offset modifyers (xmod, ymod). The algorithm works as follows:

    	brLeft := x - xmod - width / 2
    	brRight := brLeft + width
    	brBottom := y - z - ymod + 1
    	brTop := brBottom - ystep

## kfunct 0x50: DirLoop HeapPtr object, word angle

- ``
- ``

Returns: `(void)`

This function sets the loop selector of the specified object to a value implied by the 'angle' parameter, according to the following table:

`` |

## kfunct 0x51: CanBeHere HeapPtr obj [, DblList clip_list]

- ``
- ``

Returns: `(int)` 1 if ``

This function first retreives ``

- illegalBits bitwise-AND the disjunction of all elements of { 2
- ¬∃pic,pic∈clip_list. (pic::signal & (DONT_RESTORE | IGNORE_ACTOR)) = 0 ∧ pic::brRect ∩ obj::brRect ≠ ∅
If both conditions are met, 1 is returned. Otherwise, 0 is returned.

## kfunct 0x52: OnControl word map, Point|Rect area

- ``
- ``

Returns: ``

This function scans the indicated point or area on the specified `.`

## kfunct 0x53: InitBresen HeapPtr mover, word step_factor

- ``
- ``

Returns: `(void)`

Initializes a mover object for bresenham movement from the object's client's coordinates to the coordinates specified by its own pair of (x,y) selectors. To do this, it retreives the mover's client, and calculates the result values according to the algorithm for determining the initial values for iterative line drawing according to the Bresenham line algorithm:

            client := mover::client
            δx := mover::x - client::x
            δy := mover::y - client::y

            vx numsteps

##

## kfunct 0x55: DoAvoider

Returns: `(void)`

This function is a no-op.

##

## kfunct 0x57: SetDebug

Returns: `(void)`

This function forces the interpreter to enter debug mode. It is equivalent to pressing LShift-RShift-PadMinus.

##

##

##

##

## kfunct 0x5c: word mode word mode

- ``

Returns: `(word)` The amount of free memory on the heap, in bytes

This function returns the total amount of free memory on the heap if mode == 0. If mode equals 1, the total size of the largest chunk of heap memory is returned. In mode 2, the size of the largest available hunk memory block is returned, and mode 3 returns the total amount of free hunk memory, shiftet to the right by 4 bits.

##

##

##

##

## kfunct 0x61: GetSaveFiles String game_id, String strspace, HeapPtr *ptrs

- ``
- ``
- ``

Returns: `(word)` The number of savegames for the specified game_id.

Returns an array of strings describing the existing save games for game_id. The strings are put into strspace one by one, and heap pointers to each of them are put into the ptrs array. The number of saved games is returned in the accumulator.

## kfunct 0x62: GetCWD HeapPtr address

- ``

Returns: ``

This function retreives the current working directory (CWD) and stores its string representation at the location pointed to by the supplied parameter.

FreeSCI returns a sub-directory of the user's home directory, if applicable, instead of the cwd.

## kfunct 0x63: CheckFreeSpace String path

- ``

Returns: `(word)` 1 if saving is possible, 0 otherwise

Returns TRUE if there would be enough space left on the specified path to save the current game (but doesn't actually save).

##

##

## kfunct 0x66: StrAt String src, word offset[, char replacement]

- ``
- ``
- ``

Returns: `(char)` The character requested

This function retreives a single character from a string. Optionally, if ``

## kfunct 0x67: DeviceInfo word sub_function, String string1[, String string2]

- ``
- ``
- ``

Returns: See below

Depending on the value of sub_function, this system call executes one of four defined actions:

- See the specific function definitions below for more information.

## kfunct 0x67: DeviceInfo word GET_DEVICE, String input, String output

- ``
- ``
- ``

Returns: `(HeapPtr)` Points to the terminating zero character of output

GET_DEVICE returns the drive/device on which "input" resides in output (and a pointer to the terminating NULL in the accumulator).

## kfunct 0x67: DeviceInfo word GET_CURRENT_DEVICE, String output

- ``
- ``

Returns: `(HeapPtr)` Points to the terminating null character of output

GET_CURRENT_DEVICE returns the drive/device that contains the current working directory (and a pointer to the terminating NULL in the accumulator)

## kfunct 0x67: DeviceInfo word PATHS_EQUAL, String path1, String path2

- ``
- ``
- ``

Returns: `(word)` 1 if path1 and path2 point to the same physical location, 0 otherwise.

PATHS_EQUAL returns TRUE if the two supplied paths point to the same place.

## kfunct 0x67: DeviceInfo word IS_FLOPPY, String path

- ``
- ``

Returns: `(word)` 1 if ``

PATHS_EQUAL returns TRUE if the two supplied paths point to the same place.

## kfunct 0x68: GetSaveDir

Returns: `(String)`

This function returns the heap position allocated to store the string representation of the save game directory. This heap space is allocated automatically during startup.

## kfunct 0x69: CheckSaveGame String game_id, word game_nr[, String version]

- ``
- ``
- ``

Returns: `(word)` 1 if the savegame is loadable, 0 otherwise

Returns TRUE if the specified save game is valid and loadable (i.e., not for another game/interpreter/version).

##

##

##

##

##

##

##

## kfunct 0x71: Joystick word subfunction, word param

- ``
- ``

Returns: `(void)`

##

## By

This article will deal with the event manager in SCI. Like several other key parts of the interpreter, this one actively communicates with the SCI application. It directly writes to objects of the Event class, but more on that later.

The different input devices are polled differently:

- The keyboard is typically polled at each timer tick (which is 60 hz).

- SCI sets up a callback for the PC mouse driver, meaning that the mouse driver "polls itself" and sends information to the interpreter. On non-MS-DOS platforms, this would probably be done in the timer handler.

  The default FreeSCI event mechanism uses libgii, which is completely event-based.

- The joystick is only polled when the script wants to.

Some parts of the event mechanism (in particular, keyboard management) are very PC specific, and a conversion will no doubt have to take place on other platforms.

## There are three types of events, distinguished by their "type" property. The possible values are listed below; they are laid out as a bitfield to allow for selective event retrieval, see later.

- This type is returned to the SCI event managers by the input device drivers along with a "message" and a set of "modifiers". This is the basic event structure, although some event types contain extra information. The latter field is a direct copy of the BIOS shift flags, laid out as follows:

  - It is obvious, then, that these keys by themselves don't generate any keyboard events. They can, however, be combined with other keys or mouse clicks to produce "shift-click" events, for instance.

    ### These are generated when a script wants to see an event, but there isn't one to give. The current tick count and mouse position. The tick count, as explained in another document, is the time passed since the interpreter started, measured in 1/60ths of a second. It doesn't seem to be copied into the event object, however.

    ### The mouse position is returned in extra fields in the event record.

    If the middle or right button is pressed, this is reflected by the modifiers, in addition to the mouse event. The middle button is translated to the Ctrl key (i.e. set modifiers bit 2), the right button "holds down" both shift keys (setting bits 1 and 0). Every SCI interpreter (at least from 0.000.572 and up) does this, but to my knowledge it is used only in QfG2, where either a shift-click or a right-click is equivalent to typing "look ...".

    ### The keyboard driver also generates events. When a key is pressed, a keyboard event is generated, with the message field set to the scan code of the pressed key. It should be simple enough, right? Not quite so. The script may want to know if a direction key was pressed, and if so, which. It may call the KMapKeyToDir kernel function for this. KMapKeyToDir takes a keyboard event as input and converts it to a movement event, which is described next.

    ### The movement event is only generated by the joystick driver. However, on request, the keyboard driver can convert keyboard events into movement events as described above. The message field is just a direction code, mapped as follows:

    That is, the direction code starts at straight up (code 1), increasing with clockwise movement.

##

## By

Version 1.0, 30. July 1999

## The file begins with a list of 26 offsets. Each index corresponds to a letter in the (English) alphabet, and points to the first word starting with that letter. The offset is set to 0 if no words start with that letter. If an input word starts with an alphabetical letter, this table is used to speed up the vocabulary searching - though not strictly necessary, this speeds up the lookup process somewhat.

After the offset table are the actual words. A word defition consists of two parts: The actual text of the word, compressed in a special way, and a 24-bit (yes, three bytes) ID. The ID divided in 2 12-bit quantities, a word class (grammatically speaking) mask, and a group number. The class mask is used, among other things, for throwing away unnecessary words. "Take book", for instance, is a valid sentence in parser'ese, while it isn't in English.

The possible values are arranged as a bit field to allow for class masks, see later. Only one bit is actually tested by the interpreter. If a word class equals to 0xff ("anyword"), the word is excluded (allowing for parser'ese). The values go like this:

- The group number is used to implement synonyms (words with the same meaning), as well as by the Said instruction to identify words. There is also a way of using synonyms in code, see the appropriate document.

  The compression works in this way: Each string starts with a byte-sized copy count. This many characters are retained from the previous string. The actual text comes after, in normal low-ascii. The last character in the text has its high bit set (no null termination!).

  Here is an example of the compression scheme:

  - The byte count is 0 because we assume that "apple" is the first word beginning with an a (not likely, though!). 0xE5 is 0x65 (the ascii value for 'e') | 0x80. Watch now the next word:

    - Here, the initial letter is identical to that of its predecessor, so the copy count is 1. Another example:

##

The suffix vocabulary is structurally much simpler. It consists of variably-sized records with this layout:

The suffix vocabulary is used by the interpreter in order to parse compound words, and other words which consist of more than one part. For instance, a simple plural noun like "enemies" is reduced to its singular form "enemy", "stunning" is converted to "stun" etc. The point is that the interpreter gets a second chance at figuring out the meaning if the word can not be identified as entered. A word which changes its class does might end up as a different word class, the correct class is always retained. Thus, "carefully", an adverb, is reduced to its adjectival form "careful", and found in the vocabulary as such, but it is still marked as an adverb.

The suffix vocabulary consists of variably-sized records with this layout:

An asterisk (*) represents the word stem. Taking the above example with "enemies", the interpreter finds this record:

- word class 1 being a noun.

  The interpreter then tries to replace "enemies" with "enemy" and finds that word in the vocabulary. "Enemy" is a noun (class 1), which it is also supposed to be, according to the suffix vocabulary. Since we succeeded, the word class is set to the output value (which is, incidentally, also 1).

  ### If the word turns out to be a number (written with numbers, that is), and that number is not listed explicitly in the vocabulary, it gets an ID of 0xFFD, and a word class of 0x100.

## This vocabulary is used solely for building parse trees. It consists of a series of word values which end up in the data nodes on the tree. It doesn't make much sense without the original parsing code.

## By

Version 0.1, 30. July 1999. Incomplete!

This document describes the process of parsing user input and relating it to game actions. This document does not describe the process of the user typing his command; only the "behind-the-scenes" work is described, hence the title.

The process of parsing is two-fold, mainly for speed reasons. The Parse kernel function takes the actual input string and generates a special "said" event (type 0x80) from it. This function is only called once per line. Parse can either accept or reject the input.

Even if Parse accepts the sentence, it does not need to make sense. Still, syntax checks are made - see later.

Assuming that the parsing succeeded, the User object (which encapsulates the parser) then goes on to call the relevant event handlers. These event hand- lerrs in turn call the Said kernel function. This function is potentially called hundreds or even thousands of times, so it must execute as quickly as possible. Said simply determines from the pre-parsed input line whether or not a specific command is desired.

The Parse function must always work on an internal copy of the actual string, because the user must be able to recall his exact last input using the F3 key. The parser's first step is to convert the input line to pure lower case. This is because the vocabulary words are entered in lower case. The parser then searches the main vocabulary (VOCAB.000), hoping to find the word.

This doesn't necessarily happen yet. Consider, for example, the meaning of the word "carefully", which does not appear in the vocabulary, but is found anyway. This is due to the so-called suffix vocabulary, which is discussed in another document.

If the word still can't be found, the interpreter copies the failing word into a buffer temporarily allocated on the heap (remember, the interpreter operates on its own local buffers). It then calls the Game::wordFail method which prints an appropriate message. The interpreter then deallocates the buffer and exits (it does, however, still return an event. The claimed property of that event is set to TRUE to indicate that the event has already been responded to (error message printed)).

If the interpreter succeeds in identifying all the words, it then goes on to check the syntax of the sentence - it builds a parse tree. See the appropri- ate document.

If the syntax of the sentence is invalid, the interpreter calls Game::syntaxFail, passing the entire input line. As for the error situation, the event is claimed.

As mentioned in the beginning of this text, this function generates an event. This event, apart from its type id, does not contain any data. Rather, all pertinent data is kept in the interpreter.

The Said kernel function is called for each command which the game might respond to at any given time. Its only parameter is a pointer to a said information block which resides in script space. This block is described below (see CRSaidSpec).

The Said function first does some sanity checking on the event pointer which Parse stored earlier. It must be a said event (type property), and it must not have been handled by an earlier call to Said (claimed property).

It then word-extends the passed said block into a temporary buffer (command codes are byte-sized, remember?). This is supposedly just for convenience/speed, and not really needed.

##

Most of the information explained here was gathered by Lars Skovlund, and, before that, Dark Minister.

After tokenizing, looking up, and finally aliasing the data found in the parsed input string, the interpreter proceeds to build a parse tree T

I := w where

> wj ∈ W γj ∈ Γ μj ∈ 2C wj := (γj, μj)

with W being the set of all words, &Gamma being the set of all word groups, C being the set of all class masks {1, 2, 4, 8, 10, 20, 40, 80, 100}, γj being the word group wj belongs to, and μj being its class mask, as described above.

For the following sections, we will define

> group: W → Γ group((γ, μ)) := γ classes: W → C classes((γ, μ)) := μ Cx := { w | w ∈ W. x ∈ class(w) }

To do that, it uses the class masks M as input for a pushdown automaton (PDA) A built from a parser grammar; if M was accepted by A, the parse tree T

The PDA is defined by a grammar G=(V, &Sigma, P, s), most of which, along with its semantics, is stored in vocab.900. This resource contains a parser rule at every 20 bytes, starting with a non-terminal symbol v (one word) and a null-terminated list of up to five tuples (σ

σ |
Match on class mask: Matches if (m
Match on word group: Matches if (m

With the notable exception of the first rule, these rules constitute P. V := { x | ∃R, R ∈ P. x ∈ R }; typically, V := {0x12f ... 0x13f}. s := m

## Parse grammar example

> G := ({12f ... 13e}, {C

In addition to this grammar, each right-hand non-terminal mi, which is not relevant for constructing a syntax tree, but must be considered for the semantic tree TΠ. These values were omitted in the example above. As in the example above, the grammar is a context-free (type 2) grammar, almost in Chomsky Normal Form (CNF) in SCI; constructing a grammar with CNF rules from it would be trivial.

FreeSCI constructs a GNF (Greibach Normal Form) representation from these rules for parsing.

## Parser example

Parse is called with "open door".

> "open" ∈ (842, {C80}) (an imperative word of the word group 0x842) "door" ∈ (917, {C10}) (a substantive of the word group 0x917) I := (842, {C80}), (917, (C10)

I is clearly accepted by automatons based on the grammar described above. Here are two possible derivations:

> D0: 13c (13c → 13b 134) |- 13b 134 (13b → 131) |- 131 134 (131 → C80) |- C80 134 (134 → C10) |- C80 C10 D1: 13c (13c → 13b) |- 13b (13b → 131 134) |- 131 134 (131 → C80) |- C80 134 (134 → C10) |- C80 C10

Obviously, G is an ambiguous grammar. In SCI, rule precedence is implied by rule order, so the resulting left derivation tree is well-defined (in the example, it would be defined by D0)

In FreeSCI, you can use the "parse" console command to retreive all possible left derivation trees

.

## Semantics

This is important, since the parser does much more than just accept or discard input. Using the semantic tags applied to each non-terminal on the right-hand side of a rule, it constructs what I will call the semantic parse tree TΠ, which attempts to describe what the input means. For each non-terminal rule

> r := v0 → v1 v2 ... vn

there are semantic tags σr,1, σr,2 ... σr,n ∈ S, as explained above. TΠ is now constructed from the resulting derivation and the semantic tags assiociated with each non-terminal of the rule used. The construction algorithm is explained below with TΠ being constructed from nodes, which have the following structure:

> Nodes := {♦} ∪ S × V × (Nodes ∪ Γ)∗

Where S is the set of possible semantic values, and V is the set of non-terminals as defined in the grammar. We will also use the sequence γ0, γ1, γ2 ... γk-1, which will represent the word groups the input tokens belonged to (in the exact order they were accepted), and the sequence r0, r1, r2 ... rl-1, which will be the list of rules used to create the left derivation tree as described in the previous section.

    	      Helper function sci_said_recursive: S × V × (V ∪ Σ)&lowast → Node
    	      Parameters: s &isin S, Rule r ∈ V × (V ∪ Σ): v0 → v1 v2 ... vi
    	      cnmr = cnr
    	      Node n := s, vcnγ
    	      			cnγ := cnγ + 1
    	      		ELSE
    	      			cnoldr := cnr
    	      			cnr := cnr + 1
    	      			n := n, sci_said_recursive(σrmr,j, rcnoldr)
    	      		FI
    	      ROF
    	      RETURN (n)

    	      Helper function get_children: Node → Nodes∗
    			      get_children((s, v, n0, n1 ... nm)) := n0, n1 ... nm

    	      Algorithm SCI-SAID-TREE
    	      cnγ := 0;
    	      cnr := 1;
    	      ntemp := ntemp, SCI-SAID-RECURSIVE(0, r0)
    	      root(TΠ) := (141, 13f, get_children(ntemp))

Here is an example, based on the previous one:

##

k = 2: γ0 = 842, γ1 = 917 l = 4 r0 = 13c → 13b 134 σr = 141, σr = 142 r1 = 13b → 131 σr = 141 r2 = 131 → C80 r3 = 134 → C10 The resulting tree would look like this: (141 13f (141 13b (141 131 842) ) (142 134 917) )

## To test what the player wanted to say, SCI compares TΠ with a second tree, TΣ, which is built from a so-called Said spec. A Said spec is a variable-sized block in SCI memory which consists of a set of byte-sized operators and special tokens (stored in the range 0xf0 to 0xf9) and word groups (in big-endian notation, so that they don't conflict with the operators); it is terminated by the speial token 0xff. The meanings of the operators and special tokens are as follows:

Operator | Byte representation | Meaning
, | f0 | "OR". Used to specify alternatives to words, such as "take , get".
& | f1 | Unknown. Probably used for debugging.
/ | f2 | Sentence part separator. Only two of these tokens may be used, since sentences are split into a maximum of three parts.
( | f3 | Used together with ')' for grouping
) | f4 | See '('
[ | f5 | Used together with '[' for optional grouping. "[ a ]" means "either a or nothing"
] | f6 | See '['.
# | f7 | Unknown. Assumed to have been used exclusively for debugging, if at all.
< | f8 | Semantic reference operator (as in "get < up").
> | f9 | Instructs Said() not to claim the event passed to the previous Parse() call on a match. Used for successive matching.

This sequence of operators and word groups is now used to build the Said tree TΣ. I will describe the algorithm used to generate T&Sigma by providing a grammar GΣ, with L(GΣ) containing all valid Said specs. The semantics will be provided under each rule with a double arrow:

    	GΣ = ({saidspec, optcont, leftspec, midspec, rightspec, word, cwordset, wordset, expr, cwordrefset, wordrefset, recref}, Γ, P, saidspec)

    	P := {
    	  saidspec →	 leftspec optcont
    				⇒ (141 13f leftspec optcont)
    			| leftspec midspec optcont
    				⇒ (141 13f leftspec midspec optcont)
    			| leftspec midspec rightspec optcont
    				⇒ (141 13f leftspec midspec rightspec optcont)

    	  optcont →	e
    				⇒
    			| >
    				⇒ (14b f900 f900)

    	  leftspec →	e
    				⇒
    			| expr
    				⇒ (141 149 expr)

    	  midspec →	 / expr
    				⇒ (142 14a expr)
    			| [ / expr ]
    				⇒ (152 142 (142 14a expr))
    			| /
    				⇒

    	  rightspec →	 / expr
    				⇒ (143 14a expr)
    			| [ / expr ]
    				⇒ (152 143 (143 14a expr))
    			| /
    				⇒

    	  word →	 γ ∈ Γ
    				⇒ (141 153 &gamma)

    	  cwordset →	 wordset
    				⇒ (141 14f wordset)
    			| [ wordset ]
    				⇒ (141 14f (152 14c (141 14f wordset)))

    	  wordset →	 word
    				⇒ word
    			| ( expr )
    				⇒ (141 14c expr)
    			| wordset , wordset
    				⇒ wordset wordset
    			| wordset , [ wordset ]
    				⇒ wordset wordset

    	  expr →		 cwordset cwordrefset
    				⇒ cwordset cwordrefset
    			| cwordset
    				⇒ cwordset
    			| cwordrefset
    				⇒ cwordrefset

    	  cwordrefset →	 wordrefset
    				⇒ wordrefset
    			| [ wordrefset ]
    				⇒ (152 144 wordrefset)

    	  wordrefset →	< wordset recref
    				⇒ (144 14f word) recref
    			| < wordset
    				⇒ (144 14f word)

    	  recref →	< wordset recref
    				⇒ (141 144 (144 14f wordset) recref)
    			| < wordset
    				⇒ (141 144 (144 14f wordset))
    	}

## The exact algorithm used to compare TΠ to TΣ is not known yet. The one described here is based on the approximation used in FreeSCI, which is very similar to the original SCI one.

First, we need to describe a set of functions for traversing the nodes of TΣ and TΠ, and doing some work. They will be operating on the sets N := { 0, i | (i - 1) &isin N } (all non-negative integral numbers), B := {tt, ff} (the boolean lattice with two elements), and Nodes (which we defined earlier).

> first: Node → S first((s, v, n0, n0, n0, n0, n1 ... ni } ∧ m∈Node } all_children : Node → Node∗ all_children(n) := children(n) ∪ { m | ∃l.l∈all_children(n).m∈l } is_word : Node → B is_word((s, v, n0, n0 ∈ Γ contains_word : Node × S × Γ → B contains_word(n, s, γ) = tt ⇔ ∃m.m∈all_children(n).(s = second(m)) &and (is_word(m) ∧ (word(m) = &gamma)) verify_sentence_part_elements : Node × Node → B verify_sentence_part_elements(n

With these functions, we can now define an algorithm for augmenting TΠ and TΣ:

> Algorithm SCI-AUGMENT matched := tt claim_on_match := tt FOREACH n ∈ root(T&Sigma) IF ((first(n) = 14b) ∧ (second(n) = f900)) THEN claim_on_match := ff ELSE IF ¬verify_sentence_part_brackets(n, root(TΠ)) THEN matched := ff HCAEROF

Augmenting succeeded if matched = tt; in this case, TΠ is one of the trees accepted by the description provided by T&Sigma. In this case, Said() will return 1. It will also claim the event previously provided to Parse(), unless claim_on_match = ff.

## by

Version 0.1, 8. August 1999

This chapter deals with a rather complex subject within SCI. The subsystem described here is one of the "bad boys" in SCI, since it calls functions in user space, as well as changing the value of various selectors. This document is not necessarily complete. There are several things I have not covered - because they are better off in a separate document, or simply because I haven't yet figured that part out. IOW, this stuff is incomplete. Things may change.

After drawing a pic on the screen (which is DrawPic's job, that doesn't surprise now, does it?), some views have to be added to it. There are two ways of doing this; the AddToPic and the Animate call. While AddToPic is used for static views, Animate lets each animated view in the cast list perform an "animation cycle".

An animation cycle is done entirely in SCI code (with the aid of some kernel calls). It involves two other objects; the mover and the cycler. The mover is responsible for controlling the motion of an actor towards a specific point, while the cycler changes the image of the actor, making him appear to walk, for instance.

The behaviour of a view is controlled by its signal property. This property contains a bitfield which describes a lot of animation-related stuff. The bits can be roughly divided into two groups; the script and interpreter bits (I had called them Option and State bits at first, but that is not entirely accurate). The first group allows the script to influence the drawing pro- cess somewhat, the other are used internally by the interpreter. The two groups overlap a bit, though.

- The unlisted bits are probably all interpreter bits. They don't seem to have an effect when set. Many bits seem to be involved in the decision whether to display a view or not. I have not completely figured this out.

  The bit names I have written come from some debug information I got from QfG2 - type "suck blue frog" then Ctrl-W to save the cast list!

  Animate (see KAnimate) can be called in two ways:

  - If the second syntax is used, the two parameters are assumed to be zero.

    The cast list is just a list of the views to draw. Animate creates a backup of this list for updating purposes. However, this backup cast list isn't just a normal copy. The interpreter copies some selectors from the view (view, loop, cel, nsRect) and places them in a special data structure. This indicates to me that there is a possibility that the view objects may be deleted even though an update is anticipated.

    The general pseudocode for Animate goes as follows:

          1. Kill the backup cast list.
          2. If the cast pointer is NULL, just redraw the current map and exit.
          3. For each non-frozen view (bit 8 clear), let it run an animation cycle
             (by calling View::doit)
          6. For all views, perform steps 7-9.
          7. If the loop or cel property has overflowed, it is reset to 0
          8. Recalculate the priority of the view (only if signal bit 4 is clear).
          9. If the signal property indicates that we need to, increase (not set)
             the PicNotValid flag (several bits are involved in this;
             the exact combinations are a mess to figure out, I'll do it on
             request).
          10. If PicNotValid != 0, draw all the static views (PicViews).
          11. Draw all non-PicViews, saving their rectangles first and storing a
              memory handle in the underBits selector.
          12. Create a backup of the cast data.
          13. Remove the appropriate views from screen.
          14. Dispose the views which have their signal bit 15 set (calling
              View::delete)

    The algorithm for drawing PicViews is basically several loops: (repeat all of the steps for each view)

          1. If signal bit 2 is clear, jump to 4
          2. If bit 0 is set, clear it and set bit 2 instead.
          3. Continue with the next view.
          4. If signal bit 7 is set, jump to 8.
          5. If PicNotValid == 1, jump to 7.
          6. Restore the view rectangle using the memory handle stored in UnderBits
             (must be != 0, of course).
          7. Free the memory used by underBits and zero out the selector afterwards.
          8. Clear bit 6 of the signal
          9. If bit 1 is set, clear it and bit 2.

          1. Load and draw the view
          2. Clear signal bits 0, 1, 2, 6.
          3. If signal bit 14 is set, skip to the next rectangle
          4. Copy the view nsRect into a temporary variable.
          5. Find the start of the priority band specified by the priority selector.
          6. If it is greater than the nsRect top, set the top of our temp rectangle
             to the nsRect top.
          7. If the top is now below the bottom, set the top = the bottom.
          8. Draw the temp rectangle onto the control map using white color.

    The ReAnimate subfunction (0x0D) of the Graph kernel call redraws parts of the maps using the cast list created by Animate, whereas the ShowBits call (0x0C) copies parts of the active map to the physical screen.

##

## Sierra's SCI engine, written back in the late 80s, was designed and built to be fast and efficient. Some evil compromises were made (especially in the animation cycle) that sacrificed cleanness for extra cycles. Also, it was designed to use only a very limited amount of memory, which led to more compromises.

The primary design goal of FreeSCI, on the other hand, was Portability. Written in the late 90s, memory constraints were practically nonexistant, since all game data could easily be stored in memory

This is not true for the speech support some of the later SCI1 and SCI32/SCIWin come with, of course. At the time of this writing, SCI1 support is still non-existant, but later versions of FreeSCI will have to allow for dynamical loading of cdaudio resources.

. Thus, resource loading and hunk memory management is of no importance to FreeSCI. The kernel call "Load", which is used to load a resource to hunk space, simply returns the resource identifier of the resource it is supposed to load, as opposed to a pointer to a pointer to hunk memory.

Apart from that, FreeSCI simply abuses the fact that SCI was designed to be used by various different graphics adapters and sound devices. The graphics and sound commands each had to be interpreted by the currently active sound and graphics drivers, and FreeSCI does nothing more than to interpret them in its own way.

Of course, FreeSCI has to accomodate for versions differences between different SCI builds. These are generally minor issues (like the default alignment of text), but they have to be taken care of in one single program, as opposed to several builds as in the case of Sierra's SCI (some SCI games still ship with old versions of the interpreter, because they assume default values that were changed later on).

Finally, there is the built-in debugger. Sierra SCI used a quick and efficient design, while FreeSCI provides a Command-line interface to the debugger, and several additional commands.

The FreeSCI debugger still lacks some features of Sierra's debugger, like editing variables. These can be refitted easily, though.

##

## The built-in debugger takes advantage of a built-in command interpreter (which is not to be confused with the SCI command interpreter). It's appearance is going to vary in between versions (at the time of this writing, it runs on the terminal FreeSCI was started on, in text mode; later versions will likely integrate the debugger to the graphics screen), but all versions of FreeSCI will come with a working debugger

. Consult the documentation of your specific release for details on how to invoke it, if it is not activated automatically.

If activated, the debugger is called in between operation fetching and operation execution. It will show the command that is to be executed next, predicting the action done by send, super, and self calls where possible, and displaying any parameters to calling operations. It will also display the current register values and the number of operations that have been executed. It then waits for user input.

In order to simply execute the next operation, execute the "``

Speaking of functions, the FreeSCI interpreter also keeps a list of the call stack. This is similar to what the Sierra SCI interpreter provides as the "send stack", but it also includes call, calle and callb commands. Please note that callk commands are not included (some kernel functions actually call functions in user space). To display this list, invoke "``

This is note quite correct: The object listed is, in fact, the object which is used as the base object for execution. This only makes a difference if the ``

and selector (where applicable).

Selectors are not only used for functions, of course, they are also used as variables. To inspect the selectors of the current object, use the "``

For a complete listing of debugging commands, refer to the next chapter.

## The FreeSCI built-in debugger provides the following commands:

## The send operation requires a target object, which needs to be stored in the accumulator. This operation makes it possible to check if there is an object at the location indexed by acc, and, if it is, dump the type of object (Class, Object, or Clone), the object's name, and some other interesting stuff (selector names and values, funcselector names and addresses).

## Deletes a breakpoint from the specified index of the list of active breakpoints.

## bpe (script, index)

Add a breakpoint terminating when the specified exported function of a script is called

## Lists all active breakpoints.

## Adds a breakpoint to the specified method.

## Backtrace: Shows the execution stack, bundled with call parameters and selector names where appropriate.

## One of the nice things about FreeSCI is that it doesn't hide its class table as Sierra SCI appears to do. With this command, you have the power to unravel the mysteries of classes and superclasses at your fingertips.

## FreeSCI doesn't take Clone()ing lightly. It carefully notes which clone was created and tracks its current position. This function allows you to find them all, and in the darkness bind them.

## FreeSCI keeps an internal list of flags for specific areas of the game that should be watched more closely. The 'debuglog' command activates or deactivates debug output for each of those areas. Each area is described by a letter; to activate debugging for that area, use "debuglog +x", where x is the area you want to debug. "debuglog -x" deactivates debugging for that area. To activate or deactivate multiple areas, concatenate their single-letter descriptions. Run "debuglog" without parameters to get a listing of all active modes. The modes and describing letters are listed below.

## Exits the interpreter ungracefully.

## The debugger is able to disassemble code parts on the fly. Just give it an ``

## Dumps a script resource (with the specified number) and examines it. Lists classes, static objects, relocation tables, and all the other stuff contained in scripts.

## Displays a hex dump of the specified resource.

## Lists up to ``

## Shows a list of all dynamic views which are currently active, plus their underBits.

## Lists the rules of the GNF grammar used internally in FreeSCI to parse input.

## Deactivates debug mode and runs the game. Debug mode can be re-activated in the usual ways.

## Invoking this function will spit out ``

## Dumps a list of the free heap space (free, not gratis).

## This is the same as ``

## Searches for a list of hexadecimal numbers inside a single resource (if specified like "script.042"), or in a set of resources (if specified like "pic").

## Lists all kernel function names

## If called without parameters, it lists all things it can list. Among these are:

## Lists all parser rules in their normal almost-CNF representation.

## So FreeSCI doesn't have an interactive list debugger as in Sierra SCI. But it has something better

: A list dumper, which lists all list elements, keys, and heap positions.

## Shows a short descriptive message to the command.

## Prints information about heap and hunk memory allocation.

## This is, in essence, the same function as ``

## Attempts to parse a single string, and displays the word groups, word classes and the resulting parse tree, if successful.

## Displays information about all currently active picture views (picviews).

## Prints the contents of one global interpreter variable.

## Exits the interpreter gracefully, by shutting down all resources manually.

## This function retrieves the background picture, puts it on the foreground, and redraws everything. It's not inherently useful, though.

## This should never do anything. If it does, it's a bug. Refreshing the screen calls the graphics_callback function to update the whole screen from the foreground picture. Both are supposed to be identical, though.

## This function will show the current values of the program counter, the accumulator, the frame pointer, the stack pointer, the prev register, and the &rest modifier. It will also print the addresses of the current base object, of the global variables, and of the stack.

## Tries to restore a game state from the specified directory. See freesci-savegames for details about this.

## FreeSCI packs resource type and number into the usual resource id combination. Use this little helper function to unpack it.

## Forces a restart of the current game. The string parameter is meaningless now.

## This function will execute ``

## Saves the current game state to a directory with the specified `for details about this.`

## Lists all scripts that have been loaded, their positions in memory, and the position of their local variables and exports.

## Displays the names of all selectors.

## Sets the specified variable to a new value. Most commonly used to set color_mode.

## Frobbing the accumulator is not recommended, but it may be fun at times. Use this command to set your favourite register to an arbitrary value and watch things blow up.

## Sets the nodes of the parse tree, and shows the result in list representation. Useful to display information gathered from a certain hacked version of Sierra's SCI interpreter in a more readable fashion.

## Sets the visual display map. Mapnr can be any of the following:

- 4: Auxiliary map

This function does not automatically redraw the screen.

## This function will show all characters contained in a font and wait for a key or mouse button press.

## Simulates a keypress of a key with the specified key number. Modifiers are not applied.

## Displays the total byte size of one single resource.

## Another step command: Step until the interpreter hits a callk command. If you're hunting for a very specific kernel call, just add its number as a parameter. Syscall hunting has never been so easy.

## "Steps over" one instruction, i.e. continues executing until that instruction has been completed (useful for send, call, and related functions)

## Step until RETurning. If you're bored of the function you're debugging, just invoke this command. It will step forward until the current function returns.

## Can't remember what you pushed on that stack, and in which order? This command will display as many stack elements as you want, starting at the TOS.

## Displays the interpreter and SCI game versions

## Examines the specified view resource and displays the number of loops it has, the number of cels for each loop, and the size for each cel.

## Lists the heap positions of the current global, local, parameter, and temporary variables.

## Reads or sets a global, local, temporary, or parameter value. Type must be any of 'g', 'l', 'p', 't', to select global, local, parameter or temporary variables (respectively), while index represents the variable index. If value is not provided, that variable will be displayed; otherwise, it will be set to value.

## FreeSCI attempts to store savegames portably; for this reason, most of the game data is saved as plain text, while the graphics are written to png

files.

## The general FreeSCI directory policy is simple: If there is a $HOME, use ~/.freesci/[game name]/ as your playground, if there is no home, use the current working directory. Save games are true to that policy. Each save game has a directory associated with itself, and this directory is relative to the directory mentioned above. For example, if you execute "save_game frobnitz" in SQ3 on your *BSD box while your $HOME is set to /home/rogerw, the save game files would be written to /home/rogerw/.freesci/SQ3/frobnitz/.

##

## This is the main save file. It contains huge amounts of text data, which are an almost-complete replication of the game internal state_t structure. The code used to read and write this file is automatically generated by a script called cfsml.pl, and it is believed to be rather flexible; i.e. you should be able to insert blank lines, comment lines, (Using the hash ('#') sign), move assignements around, and change values. The identifiers used in this file are identical to the identifiers used in the c code.

## This is a binary copy of the heap data. Heap data is internally structured to be identical to SCI heap data (little endian, 16 bit), so it is portable to all platforms.

## These are png files containing the various graphical buffers used in the game. buffer_x.1 is the visual buffer, buffer_x.2 is the priority buffer, and buffer_x.4 is the control buffer. Any combination of these three buffers is possible.

Control and priority buffers contain a grayscale gradient palette.

## These files contain raw hunk data. SCI code may allocate raw hunk data, but it can't do anything with it (except unallocate it again). It is unlikely that you are going to encounter a hunk file in normal SCI code. This may change for later SCI versions.

## The four maps of the main picture are stored in four separate png files:

- The meanings of those files should be rather obvious.

  visual_map.png contains regular palette or color information, so it is, in fact, a screenshot of the game (the mouse pointer is not shown, since it is not stored in the display maps). The other three png files each contain a greyscale gradient palette.

## FreeSCI's file saving and restoration functionality isn't perfect. Please be aware of the following flaws and limitations before you dig out your flame thrower:

## Open file handles are NOT stored or loaded. If you try to save the game with the built-in debugger while file handles are still open, you will be warned about this and saving will abort, unless you preceed your save directory name with an underscore ('_').

## The state of the audio subsystem is not written to disk or restored. This is only temporary, though.

## SCI kernel functions are able to call the virtual machine. In practice, this means that you may have two or more vm function calls on your system stack; it is not easily possible to store the game state in this case. FreeSCI does not allow it, and, as far as I know, no Sierra SCI code ever tries to do that.

To determine whether or not this applies to you, run "bt" in the debugger; the "base" number in the first line must be zero, or you won't be able to save the game (restoring should work, though).

## Kernel functions are the bridge between the abstract virtual machine, and the world of real programs. The VM may be able to solve RPN equations in the blink of an eye, but what good is this if it can't read input or produce output?

All of the kernel functions are stored in src/core/kernel.c. Since kernel function mapping is done during runtime by string comparison, each kernel function and its name have to be registered in the array kfunct_mappers[]. Note that each version of the SCI interpreter (at least each pre-1.000.000 version) comes with one unidentified kernel function, which is handled by ``

## Each kernel function is declared like this:

                  void
                  kFooBar(state_t *s, int funct_nr, int argc, heap_ptr argp);

So this is how you should start. The four parameters (think of them as the Four Accessories of a kernel function) mean the following:

- "s" contains a lot of important and interesting data. Have a look at src/include/engine.h for a complete description. What you will probably need mostly will be the heap, (s->heap), a unsigned char pointer, and the accumulator (s->acc), a word (guint16), which is used to return values to the SCI program.

  Some kernel functions don't even need to refer to the heap. However, most of them are passed at least one, if not more parameters. This may sound shocking to you, but there is an easy way to work around the neccessity of peeling them off the heap manually: Use the PARAM macros. They are used as follows:

  - Several kernel functions assume default values if a specific parameter is not present. Use the U?PARAM_OR_ALT(x, y) macros to detect this case, and you'll rarely have to care about using argc directly.

## Accessing the heap for both reading and writing is surprisingly important for the kernel, especially when it has to deal with functions that would usually belong into user space, like handling of doubly-linked lists. To ease this, three macros are available:

- Some kernel functions, especially graphical kernel functions, additionally require the usage of what Sierra referred to as "hunk space". This is dynamically allocated memory; it can even be allocated and unallocated manually from SCI scripts by using the Load() and UnLoad() system calls (this is the sci_memory resource). To allow usage of this kind of memory, three functions have been provided:

## Error handling and debugging probably are the most important aspects of program writing. FreeSCI provides three macros for printing debug output:

- The difference between SCIkwarn and SCIkdebug is that the latter can be easily removed (by commenting out the #define SCI_KERNEL_DEBUG on or about line 39). In practice this means that SCIkwarn should be used for warning or error messages in cases where it is likely that the vm or the kernel function are doing something wrong; e.g. if the program refers to a non-existant resource file, if a node list command does not come with a pointer to a node list, or if the number of parameters is insufficient. These messages are important and may point to misperceptions of details of the SCI engine. SCIkdebug, on the other hand, is your every-day "flood me with information until I'm blind" debug macro.

  Sometimes it may happen that something goes wrong inside the kernel; e.g. a kernel function runs out of memory handles, or an internal variable somehow was set to an invalid value. In this case, ``

## Selectors are very important for some of the kernel functions. BaseSetter(), Animate(), Display(), GetEvent() and others take data from or write directly to selectors of a specified object (passed as a parameter or retreived from a node list), or even call object methods from kernel space

To prepare the usage of selectors, a variable has do be declared (in src/include/vm.h, selector_map_t). This variable will carry the numeric selector ID during run time. Now, the selector has to be mapped- this is happens once during initialization, to save time. It is performed by script_map_selectors(), which is located at the end of src/core/script.c (just use the "FIND_SELECTOR" macro).

If everything went right, accessing selectors should be really easy now. Just use the GET_SELECTOR(obj, selector) and PUT_SELECTOR(obj, selector, value) macros, where obj is a heap_ptr pointing to the object you want to read from or write to, and selector is the name of the selector to use.

## void kSwapXY(state_t *s, int funct_nr, int argc, heap_ptr argp) { int posx, posy; heap_ptr obj = PARAM(0); posx = GET_SELECTOR(obj, x); posy = GET_SELECTOR(obj, y); /* x and y are defined in selector_map_t */ PUT_SELECTOR(obj, y, posx); PUT_SELECTOR(obj, x, posy); }

Also, it may be neccessary to invoke an actual method. To do this, a varargs macro has been provided: INVOKE_SELECTOR(obj, selector, argc...). In theory, this macro can be used to set and read selectors as well (it would even handle multiple sends correctly), but this is discouraged for the sake of clarity.

INVOKE_SELECTOR works very much like the other macros; it must be called directly from a kernel function (or from any function supplying valid argc, argp and s).

## INVOKE_SELECTOR(obj, doit, 0); /* Call doit() without any parameters */ INVOKE_SELECTOR(s->game_obj, setCursor, 2, 999, 1); /* Call game_obj::setCursor(999, 1) */