From the AGDS documentation, translated by Vassili Bykov (Last update: 27 January 1998).
The AGI interpreter contains:
Some variables (0 - 26) and flags (0 - 15) are reserved by the interpreter, all others are free to be used by the programmer.
The interpreter provides a common variable and flag space for all programs simultaneously loaded in the memory. The number of objects and things is determined by the OBJECT resource.
Interpreter's actions are described using the commands of the interpreter's language. For example, there are commands to manage objects, load and unload resources, etc. Further we shall consider the commands in detail.
Note: Any variable, flag, object, string, word, message, etc. has a unique ID number, and numbering of different data types is independent (for example, there can be a variable number 5, a string number 5, and a flag number 5).
When we develop a game, we invent the plot, create objects of the game, animate them, develop scenery and a dictionary of words for the dialogue with the player. To describe all of these, resources are used. To create some of the resources, we use utilities included with AGDS, in this case the input of the utilities are resources. (Sounds weird, but that's literally what it says. --VB)
Here is a list of all the existing resources. Resources are used to represent:
Let us now consider the interpreter algorithm and the purpose of reserved variables and flags.
When interpreter starts, LOGIC resource number 0 is loaded in memory. It stays there during the whole play time and determines all the interpreter's actions related to the overall control of the game. The interpreter works in a loop, i.e. all its actions are described by the interpreter work cycle shown in the block diagram below.
In each cycle the interpreter performs the following basic actions:
new_room
command has been issued;then the cycle is repeated.
All logics (programs and subroutines) simultaneously loaded in memory operate on a common set of variables, flags, and strings, each identified by a unique for each data type ID number.
The fact that the interpreter runs in a loop influences the general programming principles and style when programming for AGDS. This makes programming a little unusual and takes a certain time to get used to. For example, many cyclic activities requiring explicit loops in ``conventional'' programming languages are executed in the interpreter programs by default, provided the program has a proper structure.
General hints on how to reduce the time to adapt to the interpreter's language are given below, using an educational program Thunderstorm as an example. However, this does not reduce the usefulness of analyzing the game programs of Sierra On-Line, Inc.
+---------------------------+
| 1. delay time |
+---------------------------+
|
V
+----------------------------+
|2. clear the keyboard buffer|
+----------------------------+
|
V
+---------------------------+
| Flag (2) - > 0 |
| Flag (4) - > 0 |
+---------------------------+
|
V
+-------------------------------------+
| 3. poll keyboard and joystick |
+-------------------------------------+
|
V
+-------------------------------------+
| If the current mode is |
| - program_control, the direction of |
| Ego motion <-- var(6). |
| - player.control, var (6) --> dir. |
| of Ego motion. |
+-------------------------------------+
|
V
+---------------------------------------------------------------+
| For all objects for which command animate.obj, start_update |
| and draw were carried out, the recalculation of the direction |
| of movement is performed. |
+---------------------------------------------------------------+
|
V
+---------------------------------------------------------------+
| If the score has changed (var (3)) or the sound has been |
| turned on or off (Flag (9)), the status line is updated. |
+---------------------------------------------------------------+
|
+---------+
V
+--------------------------+
+---------------->| 4 Logic 0 is executed |
| +--------------------------+
| |
| V
| +--------------------------------------+
| | - Dir. of motion of Ego <-- var (6) |
| | - If score (var (3)) or Flag (9) |
| | have changed their values - update |
| | the status and score (on Var (3)); |
| | - Var (5) - > 0; |
| | - Var (4) - > 0; |
| | - Flag (5) - > 0!!!! |
| | - Flag (6) - > 0; |
| | - Flag (12) - > 0. |
| +--------------------------------------+
+----------------------------+ |
| Execute: | V
| ~~~~~~~~~~~~~~~~~~~~~~~~ | +------------------------+
| - stop.update; | | Update all controlled |
| - unanimate.all; | | objects on the screen. |
| - destroy all logic | +------------------------+
| resources except | |
| for logic 0; | V
| - player.control; | +--------------+
| - unblock; | | new.room n |
| - set_horizon 36; | | or |
| - var (1) = var (0); | | new.room.v n |
| - var (0) = n | Var(n); | | was issued? |
| - var (4) = 0; | | |
| - var (5) = 0; | +-------+------+
| - var (9) = 0; |<-----------+ Yes | No |
| - var (16) = number of | +-------+--+---+
| view assoc. w/Ego; | |
| - Ego coords from var (2); | |
| - var (2) = 0; | |
| - flag (2) - > 0; | V
| - flag (5) - > 1!!!! | +--------------+
| - score < - var (3); | | Go to step 1 |
+----------------------------+ +--------------+
Written by Lance Ewing, with additions/modifications by Claudio Matsuoka (Last updated: 22 May 1999).
There is a number of data types used as AGI command parameters, listed below:
This is an unsigned eight-bit variable type equivalent of a byte, or unsigned char. Its values range from 0 to 255. There are 256 variables and in the LOGIC code (listed in section Variables used by the interpreter); they are numbered from 0 to 255 and are indentified by their number. (The original LOGIC source code that Sierra's programmers wrote would have had textual identifiers for these variables, but when the LOGIC source was compiled into the LOGIC codes, the original variable names were lost. To the interpreter, the variables are known by their index into the variable table.)
Variables are the most commonly used type. They feature in arithmetic commands such as addition and multiplication, and a lot of AGI commands have a version that has variable paramaters as an alternative to the normal constant parameter versions.
Flags are the boolean type of the AGI system. Their value can be either 1 or 0 (true or false). There are 256 flags that are numbered 0 to 255. (In the original LOGIC source code, they would have had textual identifiers, but in the compiled LOGIC code they are known only by their index into the interpreters flag table.)
Flags are used to indicate when certain things have taken place.
According to another source, there are only 12 strings available. I don't know if this is true, but it agrees with the minimum amout of space set aside for strings that I have seen in examining memory usage during a game. However, the majority of AGI games have enough room for exactly 24 strings: AGI interpreter versions 2.089, 2.411, 3.002.107 and 3.002.149 have room for 12 strings, the remaining versions have room for 24 strings.
Whether the versions that have enough space for 24 strings do infact
support 24 strings is not known. Strings are 40 characters long which
includes the zero terminator. String number zero is usually the input
prompt (e.g. ">"
or "]"
).
Words are the words that the user types in. An input sentence is
composed of a number of words. The important words (e.g. for the
sentence ``look at the tree'', ``look'' and ``tree'' are important) are
assigned to the words variables corresponding to their place in the
sentence once unimportant words and punctuation has been taken out.
For example, in the earlier example word(1)
would be ``look'' and
word(2)
would be ``tree''. Words can be converted to strings.
There are a number of AGI commands that refer to inventory items (e.g.
get()
, drop()
). One of the arguments to these commands will
represent an inventory item number. In the original LOGIC source text,
the programmer would have written things like get(dagger)
but the
interpreter knows them only as an index into the OBJECT table.
There can be a bit of confusion between this type and the inventory
item because of the name of the object
file. The object
file
has almost nothing to do with what the interpreter generally calls
objects. There are a large number of AGI commands that deal with
objects. For example,
move.obj
animate.obj
set.view
set.cel
set.loop
draw
In fact the interpreter calls its usage of the VIEW resource ``objects''. An object is one usage of a VIEW resource. It is essentially an entry in the object table (or VIEW table/VIEW list). Many objects can use the same VIEW resource for its appearance which can be seen in KQ1 and BC with the crocodile filled moats.
So when an AGI command has an object as a parameter to it, the value of the parameter is an index number into a table of objects that the interpreter is currently controlling.
At the end of every LOGIC file is a message section. There need not be
any messages in it, but it will still exist. Messages in logic.0
are global messages whereas all other messages can only be accessed from
their own LOGIC code. AGI commands that have messages as parameters
refer to a message number in their own LOGIC file. I say that those in
logic.0
are global because messages and strings can contain format
codes one of which is used to display messages from logic.0
.
Example:
print ("Message 34 in logic.0 is %g34.");
Therefore messages in logic.0
can be displayed by any LOGIC in this
way.
On interpreter startup all variables are set to 0.
1 8 | 2 \ | / \ | / 7 ------------- 3 0 - the object / | \ is motionless / | \ 6 | 4 5
On the interpreter startup all flags are set to 0.
said
command has accepted the user input.restart_game
command has been executed.ENTER
or ESC
key
are pressed.
If Var(21) is not 0, the window is closed automatically after 1/2 *
Var(21) secondsWritten by Lance Ewing (Last updated: 31 August 1997).
The following information gives a rough guide as to how Sierra's AGI interpreter uses its memory. You can view this in operation in MS-DOS by using a memory resident program like Game Wizard.
Length of first data area (2 bytes) Game signature (8 bytes) Variables (256 bytes) Flags (256 bits, 32 bytes) Timers, blocks, and other special AGI variables Strings (12*40 bytes or 24*40 bytes) unknown "Press ENTER to quit" etc Script command jump table "Avis Durgan" encryption string Rest of agidata.ovl is in here unknown words.tok file object file VIEW object table logic.0 Other loaded resources
Written by Lance Ewing with additions/modifications by Peter Kelly and Anders M. Olsson (Last updated: 3 March 1998).
Since the data formats for the different AGI interpreter versions are mostly identical or easily convertible to each other, we should expect to be able to run one games data with anothers interpreter. This sounds like a reasonable assumption but when you try it, the interpreter rejects the new data. The reason behind this is game IDs.
Every interpreter has got a game ID coded into it and it expects to be
given data for that game. Somewhere in the initialization code for
each game is a set.game.id()
. When this command is encountered, the
interpreter checks the given game ID and compares it with its own. If
they are not the same, it quits immediately. Presumably the reason for
this was to stop people running games with the wrong interpter
version, which can cause problems (unless you know what you're doing).
Method 1: The hard way.
Well, basically we have to find the game ID signature in the AGI interpreter file and change it to the ID of the game whose data we wish to be executed.
Here are a few examples of some game ID's and the data following them:
'P' 'Q' 0x00 'e' 'I' 'D' 'X'
'M' 'G' 0x00 'e' 'I' 'D' 'X'
'M' 'H' '2' 0x00 'I' 'D' 'X'
'X' 'M' 'A' 'S' 0x00 'D' 'X'
'L' 'L' 'L' 'L' 'L' 0x00 'X'
The game ID itself is the null terminated string that ends at the 00h.
The text that follows it is of no significance, it is simply to fill
in the gap although it is useful when searching for the game ID
because, as you can see, this text is always "eIDX"
> (presubably
from ``gameIDX'' before being overwritten by the actual ID) or a suffix
of it (i.e. "IDX"
, "DX"
, or "X"
). For most games,
the game ID is two or three characters which means that you will be able
to rely on the "IDX"
string being there for these games.
Method 2: The easy way
Using the above method will allow you to run a different game with an
interpreter, but you will still only be able to run the game that has
the specified game ID. There is another way around this, which
involves patching the logic source. This can be accomplished using
a program to edit the logics such as AGI Studio (for the Windows platform).
What you can usually do is look at the top part of logic 0 and find
out what the initialization logic is (usually around 90--100 -- you
might have to look at a few logics before you find it). Then simply
go to that logic, find the set.game.id
command, remove it,
and recompile the logic. Since the command is not used, the
interpreter will not try and compare it with it's own ID, and it won't
quit.
The only drawback to using this method is that the saved games no
longer have the game ID in their name (so, for example, a savegame
would be called sg.1
instead of kq2sg.1
), but this is not
a major hassle.
This is because a lot of the AGI files themselves are encrypted. See section Encrypted AGI data for further information.
What this means is that with a few useful AGI utility programs, it is possible to run any set of game data with a compatible AGI interpreter. For example, games that use AGI versions 2.915, 2.917, and 2.936 should be able to be converted into AGIv3 format and run with an AGIv3 interpreter.
``Compatible'' as it is used above refers not only to the data differences but also to some AGI command descrepencies. There are about four AGI commands that have changed the number of arguments passed to them as the interpreter developed. This sort of thing is the only real obstacle to running data on another interpreter.
Written by Lance Ewing, with additions/modifications by Peter Kelly and Anders M. Olsson (Last updated: 3 March 1998).
Many AGI files are encrypted. This was probably to give some protection to their product which was quite unique at the time. You can tell the difference between an encrypted AGI file and a non-encrypted AGI file by the first two characters. If they are ``MZ'' (the MS-DOS executable file header), then it not encrypted.
The AGI file is decrypted by the loader program. This is usually
called sierra.com
in MS-DOS but can also be named after the game
(eg. kq1.com
). In AGI version 1, the loader was called load
.
If an AGI game doesn't have a loader, then it shouldn't be encrypted.
If an AGI game does have a loader, it does not necessarily mean that
the AGI file is encrypted.
The decryption key was not originally embedded in the loader file. If you find a game where the key is embedded in the loader, it is because that game has had copy protection removed. There are several utilities to do that. Anders M Olsson's SUP is one of them. The CD re-releases have been unprotected by Sierra in exactly the same fashion.
The loader would read the decryption key from track 6 of the disk, load the executable file, decrypt and run it. Track 6 had a special format that was supposedly impossible to exactly reproduce by a standard PC floppy disk controller.
An interesting note is that when a copy-protected Sierra game asked for the original disk one, you could insert disk one from any protected Sierra game. The contents of track 6 were always the same.
But even though track 6 was the same, all games didn't use exactly the
same encryption key. The two bytes in the loader, immediately
following the string "keyOfs"
, gave an offset on track 6 from where
the key would be loaded.
So, if the decryption string consists of 128 `k' characters, it can actually mean one of two things: Either the AGI file is not encrypted, or the game is still copy-protected.
The loader contains a 128 byte string called the decryption key. Here's the process of decryption:
The start of a loader will look something like this:
LOADER v3.0 (c) Copyright Sierra On-Line, Inc. 1987 keyOfs
There are two bytes in between the "keyOfs"
string and the start of
the description string. If the decryption key consists of 128 `k'
characters, then the AGI file is not encrypted (or the game is still
copy-protected). If it consists of a whole lot of random looking
characters, then it is encrypted.
As an aside, the decryption string is followed immediately by the stack and is usually marked with a whole string of `s' characters. Thus we have `k' for key and `s' for stack. The stack is usually 256 bytes long.
Decrypting the AGI file is simply a matter of writing a program to read the loader to get the decryption string, and then applying the process mentioned above to the AGI file. Once this has been done, the game ID can be located.
From the AGDS documentation, translated by Vassili Bykov (Last update: 31 August 1998).
Note: This section is an excerpt from the description of the
said
command from section
Reference of LOGIC commands.
Here is how the input is matched. After the player types a message and
presses ENTER
, the input line is processed by the interpreter in
the following way:
If the search is unsuccessful, v9
is assigned the number of
the word in the message that failed to match and the processing
ends. If all the words have been assigned some codes:
f2
(the user has entered an input line) is set to 1f4
(said
command accepted the user input) is set to 0.If the sequence of code produced by the interpreter is
V(1), V(2),...V(m)
The test is performed as follows:
Iff2
== 0 orf4
== 1, return FALSE.
Compare parameters W(i) and codes V(i) as follows:
Otherwise W(i) should be equal to V(i).
If all elements match, f4
(said
accepted the user input) is set
to
1 and the command returns TRUE. Otherwise, FALSE is returned.
Written by Jeremy Hayes, with modifications by Claudio Matsuoka (Last update: 22 May 1999).
Game Ver. Int Int. Ver. Date
------- ------- ------- --------------- ---------
AGID ?.? AGI 2.915 ??/??/??
BC 2.00 AGI 2.439 06/14/87
BC 2.10 AGI 3.002.098 11/10/88
GR 2.01 AGI 3.002.149 12/22/88
KQ1 1.0U AGI 2.272 Unknown
KQ1 2.0F AGI 2.425 ??/??/87
KQ1 2.0F AGI 2.917 Unknown
KQ2 2.1 AGI 2.411 Unknown
KQ2 2.2 AGI 2.426 Unknown
KQ2 2.2 AGI 2.917 ??/??/87
KQ3 1.01 AGI 2.272 11/08/86
KQ3 2.00 AGI 2.435 05/25/87
KQ3 2.14 AGI 2.936 03/15/88
KQ4 2.0 AGI 3.002.086 07/27/88
KQ4D ?.?? AGI 3.002.102 ??/??/??
LSL1 1.00 AGI 2.440 06/01/87
LSL1 1.0 AGI 2.917 06/01/87
MG ?.?? AGI 2.915 Unknown
MH1 1.22 AGI 3.002.102 08/30/88
MH1 1.22 AGI 3.002.107 08/31/88
MH2 3.02B AGI 3.002.149 07/26/89
MH2 3.03 AGI 3.002.149 08/17/89
PQ1 2.0G AGI 2.917 12/03/87
SQ1 1.0X AGI 2.089 Unknown
SQ1 2.2 AGI 2.426 ??/??/??
SQ1 2.2 AGI 2.917 ??/??/??
SQ1 2.2 AGI 2.917 ??/??/87
SQ2 2.0C AGI 2.915 ??/??/87
SQ2 2.0C AGI 2.917 Unknown
SQ2 2.0D AGI 2.936 Unknown
SQ2 2.0F AGI 2.936 Unknown
XM86 ?.?? AGI 2.272 Unknown
Game Ver. Int Int. Ver. Date
------- ------- ------- --------------- ---------
SQ1 1.0X AGI 2.089 Unknown
KQ1 1.0U AGI 2.272 Unknown
KQ3 1.01 AGI 2.272 11/08/86
XM86 ?.?? AGI 2.272 Unknown
KQ2 2.1 AGI 2.411 Unknown
KQ1 2.0F AGI 2.425 ??/??/87
KQ2 2.2 AGI 2.426 Unknown
SQ1 2.2 AGI 2.426 ??/??/??
KQ3 2.00 AGI 2.435 05/25/87
BC 2.00 AGI 2.439 06/14/87
LSL1 1.00 AGI 2.440 06/01/87
AGID ?.? AGI 2.915 ??/??/??
MG ?.?? AGI 2.915 Unknown
SQ2 2.0C AGI 2.915 ??/??/87
KQ1 2.0F AGI 2.917 Unknown
KQ2 2.2 AGI 2.917 ??/??/87
LSL1 1.0 AGI 2.917 06/01/87
PQ1 2.0G AGI 2.917 12/03/87
SQ1 2.2 AGI 2.917 ??/??/??
SQ1 2.2 AGI 2.917 ??/??/87
SQ2 2.0C AGI 2.917 Unknown
KQ3 2.14 AGI 2.936 03/15/88
SQ2 2.0D AGI 2.936 Unknown
SQ2 2.0F AGI 2.936 Unknown
KQ4 2.0 AGI 3.002.086 07/27/88
BC 2.10 AGI 3.002.098 11/10/88
KQ4D ?.?? AGI 3.002.102 ??/??/??
MH1 1.22 AGI 3.002.102 08/30/88
MH1 1.22 AGI 3.002.107 08/31/88
GR 2.01 AGI 3.002.149 12/22/88
MH2 3.02B AGI 3.002.149 07/26/89
MH2 3.03 AGI 3.002.149 08/17/89
Written by Lance Ewing (Last updated: 27 January 1998).
There are a number of different versions of the AGI interpeter but generally the data formats are the same or can easy be converted between each other. The following table is a list of AGI interpreter versions that I know of:
AGI Interp. Agidata Num of Object file LZW
version size size commands encrypted
---------- -------- -------- ---------- ------------ ----
2.089 34305 6656 155 No No
2.272 34816 6656 161 No No
2.411 38400 7680 169 Yes No
2.435 38400 7680 169 Yes No
2.439 38400 7680 169 Yes No
2.440 38400 7680 169 Yes No
2.915 39424 8192 173 Yes No
2.917 39424 8192 173 Yes No
2.936 39424 8192 175 Yes No
3.002.086 40866 8064 177 Yes Yes
3.002.098 40898 8080 181 Yes Yes
3.002.102 40898 8080 181 Yes Yes
3.002.107 40962 8080 181 Yes Yes
3.002.149 40520 7488 181 Yes Yes
This table illustrates a number of things:
object
file with the
``Avis Durgan'' string.There are four commands that have changed the number of arguments that are passed to them. All this information is based on observations made of the above interpreter versions.
quit
command had no arguments for version 2.089 whereas
all the others above have one argument.print.at
and print.at.v
commands had only three
arguments for versions 2.089--2.400 and four for the other versions.There may be some differences in the number of strings supported by some interpreters as well. All interpreters have at least 12 strings. Most interpreters have space for 24 strings but I don't know if the extra space is used for strings or not.