Difference between revisions of "AGI Specifications: Chapter 6 - Logic Resources"
(12 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
[[Adventure Game Interpreter Specifications|Table of Contents]] | [[Adventure Game Interpreter Specifications|Table of Contents]] | ||
− | <div align="center"><div id="logicres"></div><div id="s6"></div><br /><span style="font-size: 22pt"> | + | <div align="center"><div id="logicres"></div><div id="s6"></div><br /><span style="font-size: 22pt">Logic Resources</span><br /> |
''Written by [[Lance Ewing]]''<br /> | ''Written by [[Lance Ewing]]''<br /> | ||
''(Last updated: 27 January 1997)''</div> | ''(Last updated: 27 January 1997)''</div> | ||
Line 9: | Line 9: | ||
== <div id="logicfmt"></div><div id="ss6.1"></div><br />6.1 Introduction == | == <div id="logicfmt"></div><div id="ss6.1"></div><br />6.1 Introduction == | ||
− | At the heart of Sierra's Adventure Game Interpreter is the | + | At the heart of Sierra's Adventure Game Interpreter is the Logic file. These files contain the code that makes up the game. Each room has a logic script that goes with it. This logic script governs what can take place in that room. Here is an example of what the programmer writes when a game is being created. |
Example 0: KQ4. Room 7. | Example 0: KQ4. Room 7. | ||
Line 484: | Line 484: | ||
| | ||
− | == <div id="ss6.3"></div><br />6.3 | + | == <div id="ss6.3"></div><br />6.3 Logic Resource Format == |
==== The header ==== | ==== The header ==== | ||
Line 524: | Line 524: | ||
| | ||
− | ==== The | + | ==== The Logic Codes ==== |
The logic code section starts immediately after the header and continues until the start of the text section has been reached. There are three sets of codes used in a logic script. Most codes will have between one and seven arguments inclusive. This is discussed later on. The first set of codes is the AGI commands themselves and they have the range: | The logic code section starts immediately after the header and continues until the start of the text section has been reached. There are three sets of codes used in a logic script. Most codes will have between one and seven arguments inclusive. This is discussed later on. The first set of codes is the AGI commands themselves and they have the range: | ||
Line 731: | Line 731: | ||
<blockquote> | <blockquote> | ||
− | + | {| class="wikitable" | |
− | Byte | + | |- |
− | + | !Byte!!Meaning | |
− | + | |- | |
− | + | |align="center" width="30"|<code>0</code>||<code>Number of messages</code> | |
− | + | |- | |
− | + | |align="center" width="30"|<code>1-2</code>||<code>Pointer to end of messages</code> | |
− | + | |- | |
− | + | |align="center" valign="top" width="30"|<code>3-4</code>||<code>A list of offsets which point to each of the messages. The<br /> | |
− | + | first offset naturally enough points to the start of the<br /> | |
− | + | textual data</code> | |
− | + | |- | |
− | + | |align="center" width="30"|<code>...</code>||<code></code> | |
− | + | |- | |
+ | |align="center" valign="top" width="30"|<code>?</code>||<code>Start of the text data. From this point the messages are<br /> | ||
+ | encrypted with Avis Durgan (in their unencrypted form, each<br /> | ||
+ | message is separated by a 0x00 value)</code> | ||
+ | |} | ||
</blockquote> | </blockquote> | ||
Line 756: | Line 760: | ||
<blockquote> | <blockquote> | ||
− | + | {| border="1px" cellspacing="0" cellpadding="0" bordercolor="#F2F2F2" bgcolor="#F9F9F9" | |
− | <pre> | + | |<pre>;equaln (eg. if (work = 3) ) |
− | ;equaln (eg. if (work = 3) ) | ||
0D71 AC LODSB ;get variable number | 0D71 AC LODSB ;get variable number | ||
0D72 32FF XOR BH,BH | 0D72 32FF XOR BH,BH | ||
Line 767: | Line 770: | ||
0D7D 7502 JNZ 0D81 | 0D7D 7502 JNZ 0D81 | ||
0D7F FEC0 INC AL ;return 1 if equal | 0D7F FEC0 INC AL ;return 1 if equal | ||
− | 0D81 C3 RET | + | 0D81 C3 RET</pre> |
− | </pre> | + | |} |
− | |||
</blockquote> | </blockquote> | ||
Example 6: MH2. equalv. | Example 6: MH2. equalv. | ||
<blockquote> | <blockquote> | ||
− | + | {| border="1px" cellspacing="0" cellpadding="0" bordercolor="#F2F2F2" bgcolor="#F9F9F9" | |
− | <pre> | + | |<pre> |
;equalv (eg. if (work = maxwork) ) | ;equalv (eg. if (work = maxwork) ) | ||
0D82 AC LODSB ;get first var number | 0D82 AC LODSB ;get first var number | ||
Line 787: | Line 789: | ||
0D94 7502 JNZ 0D98 | 0D94 7502 JNZ 0D98 | ||
0D96 FEC0 INC AL ;return 1 if equal | 0D96 FEC0 INC AL ;return 1 if equal | ||
− | 0D98 C3 RET | + | 0D98 C3 RET</pre> |
− | </pre> | + | |} |
− | |||
</blockquote> | </blockquote> | ||
Line 796: | Line 797: | ||
| | ||
− | ==== How the | + | ==== How the Interpreter Handles Logic Code ==== |
− | Now that you know a bit about what the actual code looks like once it has been converted into the | + | Now that you know a bit about what the actual code looks like once it has been converted into the Logic game data, we will now look at how these codes are interpreted by the interpreter. The following 8086 assembly language code is the actual code from the MS-DOS version of Manhunter: San Francisco. There are some calls to routines which aren't displayed. Take my word for it that they do what the comment says. For those of you who can't follow whats going on, I'll explain the interpretation in steps after the code block. |
<blockquote> | <blockquote> | ||
− | + | {| border="1px" cellspacing="0" cellpadding="0" bordercolor="#F2F2F2" bgcolor="#F9F9F9" | |
− | <pre> | + | |<pre>;Decoding a Logic file. |
− | ;Decoding a | ||
1E6C:2EF2 56 PUSH SI | 1E6C:2EF2 56 PUSH SI | ||
1E6C:2EF3 57 PUSH DI | 1E6C:2EF3 57 PUSH DI | ||
Line 809: | Line 809: | ||
1E6C:2EF5 8BEC MOV BP,SP | 1E6C:2EF5 8BEC MOV BP,SP | ||
1E6C:2EF7 83EC02 SUB SP,+02 | 1E6C:2EF7 83EC02 SUB SP,+02 | ||
− | 1E6C:2EFA 8B7608 MOV SI,[BP+08] ;SI -> start of | + | 1E6C:2EFA 8B7608 MOV SI,[BP+08] ;SI -> start of Logic script. |
1E6C:2EFD 8B7406 MOV SI,[SI+06] ;Skip first 6 bytes (header). | 1E6C:2EFD 8B7406 MOV SI,[SI+06] ;Skip first 6 bytes (header). | ||
− | 1E6C:2F00 AC LODSB ;Get next byte in | + | 1E6C:2F00 AC LODSB ;Get next byte in Logic file. |
1E6C:2F01 84C0 TEST AL,AL ;Is code a zero? | 1E6C:2F01 84C0 TEST AL,AL ;Is code a zero? | ||
1E6C:2F03 7414 JZ 2F19 ;If so, jump to exit. | 1E6C:2F03 7414 JZ 2F19 ;If so, jump to exit. | ||
Line 909: | Line 909: | ||
1E6C:2FA7 03F0 ADD SI,AX ;Skip over if (includes 3 else byte | 1E6C:2FA7 03F0 ADD SI,AX ;Skip over if (includes 3 else byte | ||
s) | s) | ||
− | 1E6C:2FA9 E954FF JMP 2F00 | + | 1E6C:2FA9 E954FF JMP 2F00</pre> |
− | </pre> | + | |} |
− | |||
</blockquote> | </blockquote> | ||
− | ''Situation 1.'' Okay, every | + | ''Situation 1.'' Okay, every Logic file starts in normal AGI command execution mode. In this routine, if the code is below 0xFC, then it is presumed to be an AGI command. It will then call the main command execution routine which will jump to the relevant routine for the specific command using the jump table stored in <code>AGIDATA.OVL</code>. The command is performed and it returns to the main execution routine where it loops back to the top and deals with the next code in the Logic file. |
''Situation 2.'' If the code is an 0xFF code, then if jumps to the <code>if</code> statement handler. In this routine is basically assesses whether the whole test condition evaluates to true or to false. It does this by treating each test separately and calling the relevant test command routines using the jump table in the <code>AGIDATA.OVL</code> file. Each test command routine will return a value in <code>AL</code> which says whether it is true or not. Depending on the NOTs and ORs, the whole expression is evaluated. If at any stage during the evaluation the routine decides that the expression will be false, it exits to another routine which skips the rest of the <code>if</code> statement and then adds the two byte word following the closing 0xFF code to the execution pointer. This usually has the affect of jumping over the <code>if</code> block of code. If the <code>if</code> handler gets to the ending 0xFF then it knows the expression is true simply because it hasn't exited out of the routine yet. At this stage it jumps over the two bytes following the closing 0xFF and then goes back to executing straight AGI commands. | ''Situation 2.'' If the code is an 0xFF code, then if jumps to the <code>if</code> statement handler. In this routine is basically assesses whether the whole test condition evaluates to true or to false. It does this by treating each test separately and calling the relevant test command routines using the jump table in the <code>AGIDATA.OVL</code> file. Each test command routine will return a value in <code>AL</code> which says whether it is true or not. Depending on the NOTs and ORs, the whole expression is evaluated. If at any stage during the evaluation the routine decides that the expression will be false, it exits to another routine which skips the rest of the <code>if</code> statement and then adds the two byte word following the closing 0xFF code to the execution pointer. This usually has the affect of jumping over the <code>if</code> block of code. If the <code>if</code> handler gets to the ending 0xFF then it knows the expression is true simply because it hasn't exited out of the routine yet. At this stage it jumps over the two bytes following the closing 0xFF and then goes back to executing straight AGI commands. | ||
Line 950: | Line 949: | ||
</blockquote> | </blockquote> | ||
− | This is exactly how all <code>if</code>s and <code>else</code>s are implemented in the | + | This is exactly how all <code>if</code>s and <code>else</code>s are implemented in the Logic code. The <code>if</code> statement is a conditional branch where the branch is taken if the condition is not met, while the <code>else</code> statement is a nonconditional jump. If a 0xFE code appears in the middle of some AGI code and wasn't actually originally coded as an <code>else</code>, then it was most likely a <code>goto</code> statement. |
| | ||
Line 973: | Line 972: | ||
==== Inner loops ==== | ==== Inner loops ==== | ||
− | At first I almost totally discarded the existence of loops in the AGI code because it seemed to me that execution of the | + | At first I almost totally discarded the existence of loops in the AGI code because it seemed to me that execution of the Logic file continually looped. Loop code like "while", "do..while", and "for" statements wouldn't be needed because you could just use a variable to increment with each pass and an <code>if</code> statement to test the value of the variable and take action if it was withing the desired range. |
<blockquote> | <blockquote> | ||
Line 987: | Line 986: | ||
</blockquote> | </blockquote> | ||
− | I have found evidence of this sort of thing taking place which means that they must loop over continuously. I don't know whether this is something that the interpreter does itself or whether it is part of the AGI code, e.g. at the end of one | + | I have found evidence of this sort of thing taking place which means that they must loop over continuously. I don't know whether this is something that the interpreter does itself or whether it is part of the AGI code, e.g. at the end of one Logic file it calls another which then calls the first one again. With the existence of the conditional branching and unconditional branching nature of the <code>if</code> and <code>else</code> statement, it is easy to see that some of the structures such as "do..while" can in fact be coded into Logic code. |
<blockquote> | <blockquote> | ||
Line 1,004: | Line 1,003: | ||
| | ||
− | ==== New | + | ==== New Information on Logic Interpretation ==== |
− | It has now come to light that <code>logic.0</code> is run over and over again with each interpretation cycle. The other | + | It has now come to light that <code>logic.0</code> is run over and over again with each interpretation cycle. The other Logics that have been loaded will only get executed if <code>logic.0</code> calls them directly or indirectly (i.e. Logics called from <code>logic.0</code> can call other Logics and so on). |
I have also become aware that code 0x00 can basically be thought of as the command <code>return</code>. If <code>logic.0</code> calls another logic, the execution will return to <code>LOGIC.0</code> when the 0x00 code is encountered. | I have also become aware that code 0x00 can basically be thought of as the command <code>return</code>. If <code>logic.0</code> calls another logic, the execution will return to <code>LOGIC.0</code> when the 0x00 code is encountered. | ||
− | It is also possible to set the entry point for a | + | It is also possible to set the entry point for a Logic file. The <code>set.scan.start</code> command makes the entry point of the Logic file being executed equal to the position of the command following <code>set.scan.start</code>. This means that the next time the Logic file is executed, execution begins at that point. The <code>reset.scan.start</code> command sets the entry point back to the start of the Logic. |
| | ||
Line 1,018: | Line 1,017: | ||
The following examples are available in the [http://AGI.helllabs.org/AGIspecs/ distribution package]: | The following examples are available in the [http://AGI.helllabs.org/AGIspecs/ distribution package]: | ||
− | * <code>[[AGI:Examples/logic/logic.c">logic.c]]</code> by [[Lance Ewing]]: loads | + | * <code>[[AGI:Examples/logic/logic.c">logic.c]]</code> by [[Lance Ewing]]: loads Logic resources into Logic File structure |
* <code>[[AGI:Examples/logic/logic.h">logic.h]]</code> by [[Lance Ewing]]: header file for <code>logic.c</code> | * <code>[[AGI:Examples/logic/logic.h">logic.h]]</code> by [[Lance Ewing]]: header file for <code>logic.c</code> | ||
* <code>[[AGI:Examples/files/AGIfiles.c">AGIfiles.c]]</code> by [[Lance Ewing]]: routines to handle loading of resources | * <code>[[AGI:Examples/files/AGIfiles.c">AGIfiles.c]]</code> by [[Lance Ewing]]: routines to handle loading of resources | ||
Line 1,028: | Line 1,027: | ||
[[Adventure Game Interpreter Specifications|Table of Contents]] | [[Adventure Game Interpreter Specifications|Table of Contents]] | ||
− | <span style="float: left">[[AGI Specifications: Chapter 5 - Resource Formats|< Previous: Chapter 5 - Resource Formats]]</span><span style="float: right">[[AGI Specifications: Chapter 7 - | + | <span style="float: left">[[AGI Specifications: Chapter 5 - Resource Formats|< Previous: Chapter 5 - Resource Formats]]</span><span style="float: right">[[AGI Specifications: Chapter 7 - Picture Resources|Next: Chapter 7 - Picture Resources >]]</span> |
| | ||
+ | [[Category:AGI Documentation]] | ||
[[Category:Adventure Game Interpreter Specifications]] | [[Category:Adventure Game Interpreter Specifications]] | ||
+ | [[Category:Examples]] |
Latest revision as of 19:36, 2 March 2018
6.1 Introduction
At the heart of Sierra's Adventure Game Interpreter is the Logic file. These files contain the code that makes up the game. Each room has a logic script that goes with it. This logic script governs what can take place in that room. Here is an example of what the programmer writes when a game is being created.
Example 0: KQ4. Room 7.
Code:<syntaxhighlight lang="agi"> if (said( open, door)) [ must be close enough { if (posn( ego, 86, 120, 106, 133)) { if (!night) { if ( door.open) { print("The door is already open"); } else { set( game.control); set.priority( ego, 11); start.update( door); end.of.loop( door, door.done); } } else { print("You can't -- it's locked"); } } else { set( notCloseEnough); } } </syntaxhighlight>
The logic script is not stored like this in the game files though. Instead each AGI command is stored as a bytecode and the resulting data doesn't look much like the above example at all. This document will try to explain each component of a logic script the way it is stored in the actual game data.
6.2 Command list and argument types
(Last updated: 3 March 1998).
This is a list of all AGI commands and their argument types. The function and name of some of these is not yet known. Check out AGIcommands.pas
in section "Sample code" for a Delphi/Pascal unit containing this information.
Text commands
# Name No. arg1 arg2 arg3 arg4 arg5 arg6 arg7 01
equaln
2
var
num
02
equalv
2
var
var
03
lessn
2
var
num
04
lessv
2
var
var
05
greatern
2
var
num
06
greaterv
2
var
var
07
isset
1
flag
08
issetv
1
var
09
has
1
Iobj
0A
obj.in.room
2
Iobj
var
0B
posn
5
Sobj
num
num
num
num
0C
controller
1
ctr
0D
have.key
0
0E
said
-
...
0F
compare.strings
2
str
str
10
obj.in.box
5
Sobj
num
num
num
num
11
center.posn
5
Sobj
num
num
num
num
12
right.posn
5
Sobj
num
num
num
num
Action commands
# Name No. arg1 arg2 arg3 arg4 arg5 arg6 arg7 00
return
0
01
increment
1
var
02
decrement
1
var
03
assignn
2
var
num
04
assignv
2
var
var
05
addn
2
var
num
06
addv
2
var
var
07
subn
2
var
num
08
subv
2
var
var
09
lindirectv
2
var
var
0A
rindirect
2
var
var
0B
lindirectn
2
var
num
0C
set
1
flag
0D
reset
1
flag
0E
toggle
1
flag
0F
set.v
1
var
10
reset.v
1
var
11
toggle.v
1
var
12
new.room
1
num
13
new.room.v
1
var
14
load.logics
1
num
15
load.logics.v
1
var
16
call
1
num
17
call.v
1
var
18
load.pic
1
var
19
draw.pic
1
var
1A
show.pic
0
1B
discard.pic
1
var
1C
overlay.pic
1
var
1D
show.pri.screen
0
1E
load.view
1
num
1F
load.view.v
1
var
20
discard.view
1
num
21
animate.obj
1
Sobj
22
unanimate.all
0
23
draw
1
Sobj
24
erase
1
Sobj
25
position
3
Sobj
num
num
26
position.v
3
Sobj
var
var
27
get.posn
3
Sobj
var
var
28
reposition
3
Sobj
var
var
29
set.view
2
Sobj
num
02
set.view.v
2
Sobj
var
02
set.loop
2
Sobj
num
02
set.loop.v
2
Sobj
var
02
fix.loop
1
Sobj
02
release.loop
1
Sobj
02
set.cel
2
Sobj
num
1E
set.cel.v
2
Sobj
var
1F
last.cel
2
Sobj
var
20
current.cel
2
Sobj
var
21
current.loop
2
Sobj
var
22
current.view
2
Sobj
var
23
number.of.loops
2
Sobj
var
24
set.priority
2
Sobj
num
25
set.priority.v
2
Sobj
var
26
release.priority
1
Sobj
27
get.priority
2
Sobj
var
03
stop.update
1
Sobj
03
start.update
1
Sobj
03
force.update
1
Sobj
03
ignore.horizon
1
Sobj
03
observe.horizon
1
Sobj
03
set.horizon
1
num
28
object.on.water
1
Sobj
29
object.on.land
1
Sobj
2A
object.on.anything
1
Sobj
2B
ignore.objs
1
Sobj
2C
observe.objs
1
Sobj
2D
distance
3
Sobj
Sobj
2E
stop.cycling
1
Sobj
2F
start.cycling
1
Sobj
30
normal.cycle
1
Sobj
31
end.of.loop
2
Sobj
flag
04
reverse.cycle
1
Sobj
04
reverse.loop
2
Sobj
flag
04
cycle.time
2
Sobj
var
04
stop.motion
1
Sobj
04
start.motion
1
Sobj
04
step.size
2
Sobj
var
32
step.time
2
Sobj
var
33
move.obj
5
Sobj
num
34
move.obj.v
5
Sobj
var
35
follow.ego
3
Sobj
num
36
wander
1
Sobj
37
normal.motion
1
Sobj
38
set.dir
2
Sobj
var
39
get.dir
2
Sobj
var
3A
ignore.blocks
1
Sobj
3B
observe.blocks
1
Sobj
05
block
4
num
num
5B
unblock
0
05
get
1
Iobj
05
get.v
1
var
05
drop
1
Iobj
05
put
2
Iobj
3C
put.v
2
var
var
3D
get.room.v
2
var
var
3E
load.sound
1
num
3F
sound
2
num
flag
64
stop.sound
0
41
1
msg
42
print.v
1
var
43
display
3
num
num
44
display.v
3
var
var
45
clear.lines
3
num
num
6A
text.screen
0
6B
graphics
0
06
set.cursor.char
1
msg
06
set.text.attribute
2
num
num
06
shake.screen
1
num
06
configure.screen
3
num
num
70
status.line.on
0
71
status.line.off
0
48
set.string
2
strinmessa
49
get.string
5
strinmessa
4A
word.to.string
2
word
strin
4B
parse
1
strin
4C
get.num
2
messavar
77
prevent.input
0
78
accept.input
0
4F
set.key
3
num
num
07
add.to.pic
7
num
num
07
add.to.pic.v
7
var
var
7C
status
0
7D
save.game
0
7E
restore.game
0
7F
init.disk
0
80
restart.game
0
51
show.obj
1
num
52
random
3
num
num
83
program.control
0
84
player.control
0
55
obj.status.v
1
var
56
quit
1
num
87
show.mem
0
88
pause
0
89
echo.line
0
8A
cancel.line
0
8B
init.joy
0
8C
toggle.monitor
0
8D
version
0
08
script.size
1
num
08
set.game.id
1
messa
5A
log
1
messa
91
set.scan.start
0
92
reset.scan.start
0
5D
reposition.to
3
Sobj
num
5E
reposition.to.v
3
Sobj
var
95
trace.on
0
60
trace.info
3
num
num
61
print.at
4
messanum
62
print.at.v
4
messavar
63
discard.view.v
1
var
09
clear.text.rect
5
num
num
9B
set.upper.left
2
???
???
09
set.menu
1
messa
09
set.menu.item
2
messacntrl
9E
submit.menu
0
09
enable.item
1
cntrl
00
disable.item
1
cntrl
A1
menu.input
0
00
show.obj.v
1
var
A3
open.dialogue
0
A4
close.dialogue
0
00
mul.n
2
var
num
00
mul.v
2
var
var
00
div.n
2
var
num
00
div.v
2
var
var
A9
close.window
0
AA
unknown170
1
???
AB
unknown171
0
AC
unknown172
0
AD
unknown173
0
AE
unknown174
1
???
AF
unknown175
1
???
B0
unknown176
0*
B1
unknown177
1
???
B2
unknown178
0
B3
unknown179
4
???
???
???
???
B4
unknown180
2
???
???
B5
unknown181
0
* 1 arg for AGI version 3.002.086
6.3 Logic Resource Format
The header
The header of each logic script is seven bytes in length for games before 1988. After this date compression seems to have been introduced and the header was subsequently altered. This compression will be discussed at a later stage.
Byte Meaning 0-1
0x1234: signature for the start of a file in the VOL block
2
Vol file number
3-4
Length of the logic script
5-6
Offset of logic code end and text begin
All text that can be printed to the screen from within a logic script is stored in an encrypted form at the end of the logic script.
Example 1: KQ1. Room 2.
12
34
Signature
01
vol.1
5F
06
Length = 0x065F
BA
02
Text start = 0x02BA
The Logic Codes
The logic code section starts immediately after the header and continues until the start of the text section has been reached. There are three sets of codes used in a logic script. Most codes will have between one and seven arguments inclusive. This is discussed later on. The first set of codes is the AGI commands themselves and they have the range:
0x00 - 0xB5 AGI commands. (eg. animate.obj)
The value 181 (0xB5) may well be different for each game. Sierra will have added more commands to their set as they went along. The value above is for Manhunter 2 which is one of the last AGI games made. The second set of codes is as follows:
FF
if
FE
else (or goto)
FD
not (!)
FC
or (||)
At present these are the only high value codes encountered. The if
and or
codes are more like brackets, ie. the code will be at the start and the end of the section of codes that it refers to. The following example will illustrate this:
Example 2: KQ1. Room 2.
FF
'if' conditions start.
07
07 = isset
05
05 = flag 5
FF
'if' conditions close.
The above translates to:
if (isset(5))
which tests whether flag number 5 is set. The 0xFF effectively switches the interpreter into a condition checking mode which leads us to the next set of codes which I call the condition codes:
0x00 - 0x12 Condition codes
The isset
condition code was introduced in example 2 above. When the interpreter encounters a 0xFF it will then interpret the following code values as being in the condition code range until it encounters the next 0xFF which switches it back into normal AGI command mode. The two bytes immediately following the second 0xFF determine how many bytes this if
statement lasts for before the if
is ended. When the second 0xFF is encountered the interpreter, be it us or the machine, does three things:
- Reads in the following two bytes.
- Opens a bracket.
- Switches to AGI command mode.
Example 3: KQ1. Room 2.
Code:<syntaxhighlight lang="agi"> FF 07 05 FF if (isset(5)) 84 00 { [ For 0x0084 bytes. 18 00 load.pic(0); 19 00 draw.pic(0); 1B 00 discard.pic(0); ... ... } [ Closed. 0x0084 bytes counted. </syntaxhighlight>
.
Of course, the code inside the brackets is only executed if the if
condition is met.
The else
command and more on brackets
The else statement will always continue after an if
bracket block. This next feature is important and has caused a number of hassles in the past. When an else
statement follows an if
, then the bracket distance given after the if
statement will be three bytes longer (this is a consequence of the way the interpreter handles if
and else
codes which is discussed later).
Here's an example:
Code:<syntaxhighlight lang="agi"> if (isset(231)) { FF 07 E7 FF 05 00 printf("The door is already open."); 65 0F } else { FE 11 00 set(36); 0C 24 prevent.input(); 77 start.update(5); 3B 05 assignn(152, 3); 03 98 03 cycle.time(5, 152); 4C 05 98 end.of.loop(5, 232); 49 05 E8 sound(70, 154); 63 46 9A } </syntaxhighlight>
Usually you would expect the bracket distance to be 0x0002 but in the above case it is clearly 0x0005 which illustrates the difference between a straight if
statement and an if..else
structure. The situation is the same for nested if..else
structures.
The else
statements themselves are a lot like if
statements except that they're test condition is given after the 0xFE code but is instead the inverse of the condition given by the above if
statement. Only the bracket distance is given after the 0xFE code and then the AGI command clock that the else
statement encompasses.
Test conditions
Conditions can be one of the following types:
FF
07
05
FF
One condition tested, ie. isset(5)
FF
FD
07
05
FF
One condition NOTed, ie. !isset(5)
FF
07
05
07
06
FF
Multiple conditions, ANDed
FF
FC
07
05
07
06
FC
FF
Multiple conditions ORed
FF
FC
07
06
07
06
FC
FD
07
08
FF
Combination
These conditions translate to:
Code:<syntaxhighlight lang="agi"> if (isset(5)) if (!isset(5)) if (isset(5) && isset(6)) if (isset(5) || isset(6)) if ((isset(5) || isset(6)) && !isset(7)) </syntaxhighlight>
If multiple boolean expressions are grouped together, then there respective values are ANDed together. If multiple boolean expressions are grouped together and then surrounded by a pair of 0xFC codes, then their values are ORed together.
The 0xFD code only applies to the following condition code whose boolean value it inverts.
Arguments
You may well be asking how the interpreter knows how many arguments each code has and what type of argument each argument is. This information is stored in a file called AGIDATA.OVL
(MS-DOS version). Inside this file there is a table which contains four bytes for each AGI command and condition code. These four bytes are interpreted as follows:
Byte Meaning 0-1
Pointer to the machine code implementation contained in the file AGI
2
Number of arguments
3
The type of arguments
The type of arguments value is interpreted as follows:
Bit
7
6
5
4
3
2
1
0
command(
arg1,
arg2,
arg3,
arg4,
arg5,
arg6,
arg7);
(unknown)
If the bit is set, argument is interpreted as a variable; otherwise the argument is interpreted as a number. It is unknown what bit 0 does since no AGI command or AGI condition code has more than seven arguments.
Examples:
- 0x80 Says that the commands first argument is a variable.
- 0x60 Says that the second and third arguments are variable numbers.
The text section
The text section of a logic script contains all the strings that can be displayed by that logic script. These strings are encrypted by xoring every eleven bytes with the string "Avis Durgan".
Example 4: KQ1. Room 2.
Code:<syntaxhighlight lang="agi"> if (said(look, alligators)) { print("The alligators are swimming in the moat."); } </syntaxhighlight>
In the above example, the print statement is represented as:
65 08
The 0x08 is the number given to the string and corresponds to its position in the list of strings at the end of the logic script.
The format of the text section is as follows:
Byte Meaning 0
Number of messages
1-2
Pointer to end of messages
3-4
A list of offsets which point to each of the messages. The
first offset naturally enough points to the start of the
textual data...
?
Start of the text data. From this point the messages are
encrypted with Avis Durgan (in their unencrypted form, each
message is separated by a 0x00 value)
Machine code implementation
The machine code for each AGI statement is found in the AGI file. This is the AGI interpreter itself. The data in the AGIDATA.OVL
file is used to find the start of the implementation for an AGI statement. Below are a couple of examples:
Example 5: MH2. equaln.
;equaln (eg. if (work = 3) ) 0D71 AC LODSB ;get variable number 0D72 32FF XOR BH,BH 0D74 8AD8 MOV BL,AL 0D76 AC LODSB ;get test number 0D77 3A870900 CMP AL,[BX+0009] ;test if var = number 0D7B B000 MOV AL,00 ;return 0 if not equal 0D7D 7502 JNZ 0D81 0D7F FEC0 INC AL ;return 1 if equal 0D81 C3 RET
Example 6: MH2. equalv.
;equalv (eg. if (work = maxwork) ) 0D82 AC LODSB ;get first var number 0D83 32FF XOR BH,BH ;clear bh 0D85 8AD8 MOV BL,AL ;BX = variable number 0D87 8AA70900 MOV AH,[BX+0009] ;get first var value 0D8B AC LODSB ;get second var number 0D8C 8AD8 MOV BL,AL 0D8E 32C0 XOR AL,AL ;return 0 if not equal 0D90 3AA70900 CMP AH,[BX+0009] ;compare variables 0D94 7502 JNZ 0D98 0D96 FEC0 INC AL ;return 1 if equal 0D98 C3 RET
These two examples show the difference between how numbers and variables are dealt with. In the case of a variable, the variables number is used as an index into the table of variable values to get the value which is being tested. It appears that the variable table is at offset 0x0009 in the data segment.
How the Interpreter Handles Logic Code
Now that you know a bit about what the actual code looks like once it has been converted into the Logic game data, we will now look at how these codes are interpreted by the interpreter. The following 8086 assembly language code is the actual code from the MS-DOS version of Manhunter: San Francisco. There are some calls to routines which aren't displayed. Take my word for it that they do what the comment says. For those of you who can't follow whats going on, I'll explain the interpretation in steps after the code block.
;Decoding a Logic file. 1E6C:2EF2 56 PUSH SI 1E6C:2EF3 57 PUSH DI 1E6C:2EF4 55 PUSH BP 1E6C:2EF5 8BEC MOV BP,SP 1E6C:2EF7 83EC02 SUB SP,+02 1E6C:2EFA 8B7608 MOV SI,[BP+08] ;SI -> start of Logic script. 1E6C:2EFD 8B7406 MOV SI,[SI+06] ;Skip first 6 bytes (header). 1E6C:2F00 AC LODSB ;Get next byte in Logic file. 1E6C:2F01 84C0 TEST AL,AL ;Is code a zero? 1E6C:2F03 7414 JZ 2F19 ;If so, jump to exit. 1E6C:2F05 3CFF CMP AL,FF ;If an opening 'if' code is found 1E6C:2F07 7419 JZ 2F22 ;jump to 'if' handler. 1E6C:2F09 3CFE CMP AL,FE ;If an 'else' has not been found 1E6C:2F0B 7505 JNZ 2F12 ;jump over else/branch. 1E6C:2F0D AD LODSW ;Get word (bracket distance) 1E6C:2F0E 03F0 ADD SI,AX ;Add to SI. Skip else code. 1E6C:2F10 EBEE JMP 2F00 ;Go back to get next byte. 1E6C:2F12 E8A8D6 CALL 05BD ;Execute AGI command. 1E6C:2F15 85F6 TEST SI,SI ; 1E6C:2F17 75E8 JNZ 2F01 ;Jump back to top. 1E6C:2F19 8BC6 MOV AX,SI 1E6C:2F1B 83C402 ADD SP,+02 1E6C:2F1E 5D POP BP 1E6C:2F1F 5F POP DI 1E6C:2F20 5E POP SI 1E6C:2F21 C3 RET ;Handler for 'if' statement. ;BH determines if its in an OR bracket (BH=1 means OR). ;BL determines the nature of the evalutation (BL=1 means NOT) 1E6C:2F22 33DB XOR BX,BX 1E6C:2F24 AC LODSB ;Get next byte 1E6C:2F25 3CFC CMP AL,FC ;If less than 0xFC, then 1E6C:2F27 721C JB 2F45 ;jump to normal processing. 1E6C:2F29 7508 JNZ 2F33 ;If greater, jump to 'if' close. 1E6C:2F2B 84FF TEST BH,BH ;(Could BH be the evaluation reg? 1E6C:2F2D 7551 JNZ 2F80 ;or whether its the second FC? 1E6C:2F2F FEC7 INC BH ; 1E6C:2F31 EBF1 JMP 2F24 ;Go back to get next byte. 1E6C:2F33 3CFF CMP AL,FF ;Is the code for an 'if' close? 1E6C:2F35 7505 JNZ 2F3C ;If not, jump to 'not' test. 1E6C:2F37 83C602 ADD SI,+02 ; 1E6C:2F3A EBC4 JMP 2F00 ; 1E6C:2F3C 3CFD CMP AL,FD ;Is the code for a 'not'? 1E6C:2F3E 7505 JNZ 2F45 ;If not, jump to test command. 1E6C:2F40 80F301 XOR BL,01 ; 1E6C:2F43 EBDF JMP 2F24 ;Go back to get next byte. 1E6C:2F45 53 PUSH BX ;BX = test conditions?? 1E6C:2F46 E8E8DD CALL 0D31 ;Evaluate separate test command. 1E6C:2F49 5B POP BX ; 1E6C:2F4A 32C3 XOR AL,BL ;Toggle the result for NOT. 1E6C:2F4C B300 MOV BL,00 ; 1E6C:2F4E 7506 JNZ 2F56 ;If true jump to 2F56. 1E6C:2F50 84FF TEST BH,BH ;If BH=0 then not in OR and 1E6C:2F52 742C JZ 2F80 ;test is truely false. 1E6C:2F54 EBCE JMP 2F24 ;Otherwise evaluate next OR. 1E6C:2F56 84FF TEST BH,BH ;Are we in OR mode? 1E6C:2F58 7424 JZ 2F7E ;If not, continue with testing. 1E6C:2F5A 32FF XOR BH,BH ;If so, then we will skip the 1E6C:2F5C 32E4 XOR AH,AH ;rest of the tests in the OR 1E6C:2F5E AC LODSB ;bracket since the first is true. 1E6C:2F5F 3CFC CMP AL,FC ;OR: Waiting for closing OR. 1E6C:2F61 741B JZ 2F7E ;If OR found, then continue testing. 1E6C:2F63 77F9 JA 2F5E ; 1E6C:2F65 3C0E CMP AL,0E ;If 'said' then goto said handler 1E6C:2F67 7507 JNZ 2F70 ;else goto normal handler 1E6C:2F69 AC LODSB ;Work out number of words in said 1E6C:2F6A D1E0 SHL AX,1 ;and jump over them. 1E6C:2F6C 03F0 ADD SI,AX ; 1E6C:2F6E EBEE JMP 2F5E ; 1E6C:2F70 8BF8 MOV DI,AX ;Jumps over arguments. 1E6C:2F72 D1E7 SHL DI,1 ; 1E6C:2F74 D1E7 SHL DI,1 ; 1E6C:2F76 8A856407 MOV AL,[DI+0764] ;Load up the number of arguments 1E6C:2F7A 03F0 ADD SI,AX ;Add to the execution pointer 1E6C:2F7C EBE0 JMP 2F5E ; 1E6C:2F7E EBA4 JMP 2F24 ;Test is false. ;This routine basically skips over the rest of the codes until it finds the ;closing 0xFF at which point it will load the following two bytes and add ;them to the execution pointer SI. 1E6C:2F80 32FF XOR BH,BH 1E6C:2F82 32E4 XOR AH,AH 1E6C:2F84 AC LODSB ; 1E6C:2F85 3CFF CMP AL,FF ;If the closing 0XFF is found, 1E6C:2F87 741D JZ 2FA6 ;jump 2FA6. 1E6C:2F89 3CFC CMP AL,FC ;If greater than FC, 1E6C:2F8B 73F7 JNB 2F84 ;get next byte. 1E6C:2F8D 3C0E CMP AL,0E ;If 'said' then goto said handler 1E6C:2F8F 7507 JNZ 2F98 ;else goto normal handler. 1E6C:2F91 AC LODSB ;Work out number of words in said 1E6C:2F92 D1E0 SHL AX,1 ;and jump over them. 1E6C:2F94 03F0 ADD SI,AX 1E6C:2F96 EBEC JMP 2F84 1E6C:2F98 8AD8 MOV BL,AL ;Jump over arguments. 1E6C:2F9A D1E3 SHL BX,1 1E6C:2F9C D1E3 SHL BX,1 1E6C:2F9E 8A876407 MOV AL,[BX+0764] ;Load up the number of arguments. 1E6C:2FA2 03F0 ADD SI,AX ;Add to the execution pointer. 1E6C:2FA4 EBDE JMP 2F84 1E6C:2FA6 AD LODSW 1E6C:2FA7 03F0 ADD SI,AX ;Skip over if (includes 3 else byte s) 1E6C:2FA9 E954FF JMP 2F00
Situation 1. Okay, every Logic file starts in normal AGI command execution mode. In this routine, if the code is below 0xFC, then it is presumed to be an AGI command. It will then call the main command execution routine which will jump to the relevant routine for the specific command using the jump table stored in AGIDATA.OVL
. The command is performed and it returns to the main execution routine where it loops back to the top and deals with the next code in the Logic file.
Situation 2. If the code is an 0xFF code, then if jumps to the if
statement handler. In this routine is basically assesses whether the whole test condition evaluates to true or to false. It does this by treating each test separately and calling the relevant test command routines using the jump table in the AGIDATA.OVL
file. Each test command routine will return a value in AL
which says whether it is true or not. Depending on the NOTs and ORs, the whole expression is evaluated. If at any stage during the evaluation the routine decides that the expression will be false, it exits to another routine which skips the rest of the if
statement and then adds the two byte word following the closing 0xFF code to the execution pointer. This usually has the affect of jumping over the if
block of code. If the if
handler gets to the ending 0xFF then it knows the expression is true simply because it hasn't exited out of the routine yet. At this stage it jumps over the two bytes following the closing 0xFF and then goes back to executing straight AGI commands.
Situation 3. If in the normal execution of AGI commands, the code 0xFE is encountered, a very simple action takes place. The two bytes which follow form a 16-bit twos complement value which is added to execution pointer. This is all it does. Previously we said that the 0xFE code stood for the else
statement which is in actual fact correct for over 90% of the time, but the small number of other occurrences are best described as goto
statements. If you're confused by this, the following example will probably explain things.
Example:
Code:<syntaxhighlight lang="agi"> if (said( open, door)) { [ first block of AGI statements } else { [ second block of AGI statements } </syntaxhighlight>
The above example is how the original coder would have written the AGI code. If we now look at the following example, it is not hard to see that it would achieve the same thing.
Code:<syntaxhighlight lang="agi"> if (!said( open, door)) goto label1; [ first block of AGI statements goto label2:
label1: [ second block of AGI statements
label2: </syntaxhighlight>
This is exactly how all if
s and else
s are implemented in the Logic code. The if
statement is a conditional branch where the branch is taken if the condition is not met, while the else
statement is a nonconditional jump. If a 0xFE code appears in the middle of some AGI code and wasn't actually originally coded as an else
, then it was most likely a goto
statement.
The said
test command
The above assembly language code does raise a very important point. The said
command can have a variable number of arguments. Its code is 0x0E, and the byte following this byte gives the number of two byte words that follow as parameters.
Examples:
Code:<syntaxhighlight lang="agi"> if (said(marble)) FF 0E 01 1E 01 FF if (said( open, door)) FF 0E 02 37 02 73 00 FF </syntaxhighlight>
In the above examples, the values 0x011E, 0x0237, and 0x0073 are just random word numbers that could stand for the words given.
Inner loops
At first I almost totally discarded the existence of loops in the AGI code because it seemed to me that execution of the Logic file continually looped. Loop code like "while", "do..while", and "for" statements wouldn't be needed because you could just use a variable to increment with each pass and an if
statement to test the value of the variable and take action if it was withing the desired range.
Example:
Code:<syntaxhighlight lang="agi">
if (greatern(30, 45) && lessn(30, 55)) { print("You're in the hot zone!"); increment(30); } </syntaxhighlight>
I have found evidence of this sort of thing taking place which means that they must loop over continuously. I don't know whether this is something that the interpreter does itself or whether it is part of the AGI code, e.g. at the end of one Logic file it calls another which then calls the first one again. With the existence of the conditional branching and unconditional branching nature of the if
and else
statement, it is easy to see that some of the structures such as "do..while" can in fact be coded into Logic code.
Example:
Code:<syntaxhighlight lang="agi"> FF FD 0D FF 03 00 FE F7 FF do { } while (!havekey); </syntaxhighlight>
The above translation is a simple one which is taken from SQ2. The value 0xFFF7 is the twos complement notation for -9 which is the exact branching value to take the execution back to the start of the if
statement. If the above example had AGI code between the 0x00 and the 0xFE, then there would be code within the brackets of the "do..while" structure. I don't know whether the original AGI coders used these statements or used goto
statements to achieve the same result.
New Information on Logic Interpretation
It has now come to light that logic.0
is run over and over again with each interpretation cycle. The other Logics that have been loaded will only get executed if logic.0
calls them directly or indirectly (i.e. Logics called from logic.0
can call other Logics and so on).
I have also become aware that code 0x00 can basically be thought of as the command return
. If logic.0
calls another logic, the execution will return to LOGIC.0
when the 0x00 code is encountered.
It is also possible to set the entry point for a Logic file. The set.scan.start
command makes the entry point of the Logic file being executed equal to the position of the command following set.scan.start
. This means that the next time the Logic file is executed, execution begins at that point. The reset.scan.start
command sets the entry point back to the start of the Logic.
6.4 Sample code
The following examples are available in the distribution package:
* [[AGI:Examples/logic/logic.c">logic.c]]
by Lance Ewing: loads Logic resources into Logic File structure
* [[AGI:Examples/logic/logic.h">logic.h]]
by Lance Ewing: header file for logic.c
* [[AGI:Examples/files/AGIfiles.c">AGIfiles.c]]
by Lance Ewing: routines to handle loading of resources
* [[AGI:Examples/files/AGIfiles.h">AGIfiles.h]]
by Lance Ewing: header file for AGIfiles.c
* [[AGI:Examples/logic/AGIcommands.pas">AGIcommands.pas]]
by Peter Kelly: Delphi/Pascal unit with a list of all commands and argument types
Table of Contents
< Previous: Chapter 5 - Resource FormatsNext: Chapter 7 - Picture Resources >