Au cœur de l'AGI (Adventure Game Interpreter) de Sierra se trouve le fichier “logique”.
Ces fichiers contiennent le code qui compose le jeu. Chaque pièce est accompagnée d'un script logique. Ce script logique régit ce qui peut se passer dans cette salle.
Voici un exemple de ce que le programmeur écrit lors de la création d'un jeu.
Exemple: KQ4. Room 7.
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); } }
Les tableaux suivants présentent une liste de toutes les commandes AGI et de leurs types d'arguments.
Les noms des commandes sont tirés des messages de débogage contenus dans certains jeux AGI.
Opcode | Command | Args | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|---|
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 | item | ||||
0A | obj.in.room | 2 | item | var | |||
0B | posn | 5 | obj | 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 | obj | num | num | num | num |
11 | center.posn | 5 | obj | num | num | num | num |
12 | right.posn | 5 | obj | num | num | num | num |
Opcode | Command | Args | 1 | 2 | 4 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|---|
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 | obj | ||||||
22 | unanimate.all | 0 | |||||||
23 | draw | 1 | obj | ||||||
24 | erase | 1 | obj | ||||||
25 | position | 3 | obj | num | num | ||||
26 | position.v | 3 | obj | var | var | ||||
27 | get.posn | 3 | obj | var | var | ||||
28 | reposition | 3 | obj | var | var | ||||
29 | set.view | 2 | obj | num | |||||
2A | set.view.v | 2 | obj | var | |||||
2B | set.loop | 2 | obj | num | |||||
2C | set.loop.v | 2 | obj | var | |||||
2D | fix.loop | 1 | obj | ||||||
2E | release.loop | 1 | obj | ||||||
2F | set.cel | 2 | obj | num | |||||
30 | set.cel.v | 2 | obj | var | |||||
31 | last.cel | 2 | obj | var | |||||
32 | current.cel | 2 | obj | var | |||||
33 | current.loop | 2 | obj | var | |||||
34 | current.view | 2 | obj | var | |||||
35 | number.of.loops | 2 | obj | var | |||||
36 | set.priority | 2 | obj | num | |||||
37 | set.priority.v | 2 | obj | var | |||||
38 | release.priority | 1 | obj | ||||||
39 | get.priority | 2 | obj | var | |||||
3A | stop.update | 1 | obj | ||||||
3B | start.update | 1 | obj | ||||||
3C | force.update | 1 | obj | ||||||
3D | ignore.horizon | 1 | obj | ||||||
3E | observe.horizon | 1 | obj | ||||||
3F | set.horizon | 1 | num | ||||||
40 | object.on.water | 1 | obj | ||||||
41 | object.on.land | 1 | obj | ||||||
42 | object.on.anything | 1 | obj | ||||||
43 | ignore.objs | 1 | obj | ||||||
44 | observe.objs | 1 | obj | ||||||
45 | distance | 3 | obj | obj | var | ||||
46 | stop.cycling | 1 | obj | ||||||
47 | start.cycling | 1 | obj | ||||||
48 | normal.cycle | 1 | obj | ||||||
49 | end.of.loop | 2 | obj | flag | |||||
4A | reverse.cycle | 1 | obj | ||||||
4B | reverse.loop | 2 | obj | flag | |||||
4C | cycle.time | 2 | obj | var | |||||
4D | stop.motion | 1 | obj | ||||||
4E | start.motion | 1 | obj | ||||||
4F | step.size | 2 | obj | var | |||||
50 | step.time | 2 | obj | var | |||||
51 | move.obj | 5 | obj | num | |||||
52 | move.obj.v | 5 | obj | var | |||||
53 | follow.ego | 3 | obj | num | |||||
54 | wander | 1 | obj | ||||||
55 | normal.motion | 1 | obj | ||||||
56 | set.dir | 2 | obj | var | |||||
57 | get.dir | 2 | obj | var | |||||
58 | ignore.blocks | 1 | obj | ||||||
59 | observe.blocks | 1 | obj | ||||||
5A | block | 4 | num | num | |||||
5B | unblock | 0 | |||||||
5C | get | 1 | item | ||||||
5D | get.v | 1 | var | ||||||
5E | drop | 1 | item | ||||||
5F | put | 2 | item | ||||||
60 | put.v | 2 | var | var | |||||
61 | get.room.v | 2 | var | var | |||||
62 | load.sound | 1 | num | ||||||
63 | sound | 2 | num | flag | |||||
64 | stop.sound | 0 | |||||||
65 | 1 | msg | |||||||
66 | print.v | 1 | var | ||||||
67 | display | 3 | num | num | msg | ||||
68 | display.v | 3 | var | var | var | ||||
69 | clear.lines | 3 | num | num | msg | ||||
6A | text.screen | 0 | |||||||
6B | graphics | 0 | |||||||
6C | set.cursor.char | 1 | msg | ||||||
6D | set.text.attribute | 2 | num | num | |||||
6E | shake.screen | 1 | num | ||||||
6F | configure.screen | 3 | num | num | num | ||||
70 | status.line.on | 0 | |||||||
71 | status.line.off | 0 | |||||||
72 | set.string | 2 | str | msg | |||||
73 | get.string | 2 | str | msg | |||||
74 | word.to.string | 2 | word | str | |||||
75 | parse | 1 | str | ||||||
76 | get.num | 2 | str | var | |||||
77 | prevent.input | 0 | |||||||
78 | accept.input | 0 | |||||||
79 | set.key | 3 | num | num | num | ||||
7A | add.to.pic | 7 | num | num | num | num | num | num | num |
7B | add.to.pic.v | 7 | var | var | var | var | var | var | var |
7C | status | 0 | |||||||
7D | save.game | 0 | |||||||
7E | restore.game | 0 | |||||||
7F | init.disk | 0 | |||||||
80 | restart.game | 0 | |||||||
81 | show.obj | 1 | num | ||||||
82 | random | 3 | num | num | var | ||||
83 | program.control | 0 | |||||||
84 | player.control | 0 | |||||||
85 | obj.status.v | 1 | var | ||||||
86 | 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 | |||||||
8E | script.size | 1 | num | ||||||
8F | set.game.id | 1 | msg | ||||||
90 | log | 1 | msg | ||||||
91 | set.scan.start | 0 | |||||||
92 | reset.scan.start | 0 | |||||||
93 | reposition.to | 3 | obj | num | num | ||||
94 | reposition.to.v | 3 | obj | var | var | ||||
95 | trace.on | 0 | |||||||
96 | trace.info | 3 | num | num | num | ||||
97 | print.at | 4 | msg | num | num | num | |||
98 | print.at.v | 4 | var | num | num | num | |||
99 | discard.view.v | 1 | var | ||||||
9A | clear.text.rect | 5 | num | num | num | num | num | ||
9B | set.upper.left | 2 | num | num | |||||
9C | set.menu | 1 | msg | ||||||
9D | set.menu.member | 2 | msg | ctr | |||||
9E | submit.menu | 0 | |||||||
9F | enable.member | 1 | ctr | ||||||
A0 | disable.member | 1 | ctr | ||||||
A1 | menu.input | 0 | |||||||
A2 | show.obj.v | 1 | var | ||||||
A3 | open.dialogue | 0 | |||||||
A4 | close.dialogue | 0 | |||||||
A5 | mul.n | 2 | var | num | |||||
A6 | mul.v | 2 | var | var | |||||
A7 | div.n | 2 | var | num | |||||
A8 | div.v | 2 | var | var | |||||
A9 | close.window | 0 | |||||||
AA | set.simple | 1 | ??? | ||||||
AB | push.script | 0 | |||||||
AC | pop.script | 0 | |||||||
AD | hold.key | 0 | |||||||
AE | set.pri.base | 1 | num | ||||||
AF | discard.sound | 1 | num | ||||||
B0 | hide.mouse | 0 | 1 | ||||||
B1 | allow.menu | 1 | ??? | ||||||
B2 | show.mouse | 0 | |||||||
B3 | fence.mouse | 4 | num | num | num | num | |||
B4 | mouse.posn | 2 | var | var | |||||
B5 | release.key | 0 | |||||||
B6 | adj.ego.move.to.xy | 0 |
L'entête (Header) de chaque script logique a une longueur de sept octets pour les jeux antérieurs à 1988.
Après cette date, une compression semble avoir été introduite et l'en-tête a été modifié par la suite.
Cette compression sera discutée ultérieurement.
Offset | Command |
---|---|
0-1 | Signature (0x12–0x34) |
2 | Numéro du volume dans lequel la ressource est contenue |
3-4 | Longueur de la ressource sans l'entête |
5-6 | Décalage de la section des messages du code logique |
Tout le texte qui peut être imprimé à l'écran à partir d'un script logique est stocké sous une forme cryptée à la fin du script logique.
Exemple: KQ1. Room 2.
12 34 Signature 01 vol.1 5F 06 Length = 0x065F BA 02 Text start = 0x02BA
La section du code logique commence immédiatement après l'en-tête et se poursuit jusqu'au début de la section du message.
Trois séries de codes sont utilisées dans un script logique.
La plupart des codes ont entre un et sept arguments inclus. Nous y reviendrons plus loin.
La première série de codes correspond aux commandes AGI elles-mêmes énumérées dans les tableaux précédents et se situe dans la plage 0x00–0xB6.
La deuxième série de codes est la suivante :
Code | Command |
---|---|
FF | if |
FE | else or goto |
FD | not |
FC | or |
À l'heure actuelle, ce sont les seuls codes de grande valeur que l'on rencontre. Les codes if et or sont comme des parenthèses, c'est-à-dire que le code sera au début et à la fin de la section de codes à laquelle il se réfère. L'exemple suivant l'illustre :
Exemple: KQ1, Room 2.
FF 'if' conditions start. 07 07 = isset 05 05 = flag 5 FF 'if' conditions close.
que l'on peut traduire comme ci-dessous:
if (isset(5))
ce code test si le “flag” numero 5 est actif.
Le 0xFF
fait passer l'interpréteur en mode de vérification des conditions, ce qui nous amène à la série de codes énumérés dans les tableaux suivants
0x00 - 0x12 Condition codes.
Lorsque l'interprète rencontre un 0xFF
, il interprète les valeurs de code suivantes comme étant dans la plage de codes de condition jusqu'à ce qu'il rencontre le 0xFF
suivant, ce qui le ramène en mode de commande AGI normal. normal de l'AGI.
Les deux octets qui suivent immédiatement le deuxième 0xFF
déterminent le nombre d'octets que dure cette instruction if avant que le if ne se termine.
Lorsque le deuxième 0xFF
est rencontré, l'interprète, que ce soit nous ou la machine, fait trois choses. l'interprète, que ce soit nous ou la machine, fait trois choses :
Exemple: KQ1, Room 2.
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.
L'instruction else continue toujours après un bloc de crochets if.
Cette caractéristique est importante et a causé un certain nombre de problèmes dans le passé.
Lorsqu'une instruction else suit une instruction if, la distance entre les crochets après l'instruction if sera plus longue de trois octets (ceci est une conséquence de la manière dont l'interpréteur gère les codes if et else, ce qui est discuté plus loin).
voici un exemple:
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 }
En général, on s'attend à ce que la distance entre les crochets soit de 0x0002, mais dans le cas ci-dessus, elle est clairement de 0x0005, ce qui illustre la différence entre une instruction if directe et une structure if..else.
La situation est la même pour les structures if..else imbriquées.
Les instructions else elles-mêmes ressemblent beaucoup aux instructions if, sauf que leur condition de test est donnée après le code 0xFE
, mais est l'inverse de la condition donnée par l'instruction if ci-dessus.
Seule la distance entre les crochets est donnée après le code 0xFE
et ensuite l'horloge de commande AGI que l'instruction else englobe.
Les conditions peuvent être de l'un des types suivants:
FF 07 05 FF Une condition testée, c'est-à-dire isset(5) FF FD 07 05 FF Une condition Inversée (NOT), c'est-à-dire !isset(5) FF 07 05 07 06 FF Plusieurs conditions, liée par un ET logique (AND). FF FC 07 05 07 06 FC FF Plusieurs conditions, liée par un OU logique (OR). FF FC 07 06 07 06 FC FD 07 08 FF Combination.
Ces conditions se traduisent comme ceci:
if (isset(5)) if (!isset(5)) if (isset(5) && isset(6)) if (isset(5) || isset(6)) if ((isset(5) || isset(6)) && !isset(7))
Si plusieurs expressions booléennes sont regroupées, leurs valeurs respectives sont combinées ensemble par un ET logique (AND).
Si plusieurs expressions booléennes sont regroupées et entourées d'une paire de codes 0xFC
, leurs valeurs sont combinées. leurs valeurs sont combinées par un OU logique (OR).
Le code 0xFD
ne s'applique qu'au code de condition suivant dont il inverse la valeur booléenne.
Vous vous demandez peut-être comment l'interpréteur sait combien d'arguments possède chaque code (ou fonction) et quel type d'argument est associé à chaque argument.
Cette information est stockée dans le fichier agidata.ovl
de la version MS-DOS.
Dans ce fichier, il y a un tableau qui contient quatre octets pour chaque commande AGI et chaque code de condition.
Ces quatre octets sont interprétés comme suit :
Offset | Description |
---|---|
0-1 | Pointeur vers le code de mise en œuvre |
2 | Numbre d'arguments |
3 | Type d'arguments |
Le type de valeur des arguments est interprété comme suit :
Bit 7 6 5 4 3 2 1 0 command( arg1, arg2, arg3, arg4, arg5, arg6, arg7); (unknown)
Si le bit est activé, l'argument est interprété comme une variable ; sinon, l'argument est interprété comme un nombre.
La fonction du bit 0 n'est pas connue, car aucune commande AGI ni aucun code de condition AGI n'a plus de sept arguments.
Examples:
La section des messages d'un script logique contient toutes les chaînes qui peuvent être affichées par ce script logique.
Ces chaînes sont cryptées par XOR tous les onze octets avec la chaîne “Avis Durgan”.
Exemple: KQ1, Room 2.
if (said(look, alligators)) { print("The alligators are swimming in the moat."); }
Dans l'exemple ci-dessus, l'instruction d'impression est représentée comme suit :
65 08
Le 0x08
est le numéro attribué à la chaîne et correspond à sa position dans la liste des chaînes à la fin du script logique.
Le format de la section message est le suivant :
Offset | Description |
---|---|
0 | Number of messages |
1-2 | Pointer to the end of the messages |
3-4 | Array of message pointers |
… | Array of message pointers |
? | 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) |
The implementation 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: 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: 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.
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 bytes) 1E6C:2FA9 E954FF JMP 2F00
Situation 1.
Every logic script 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:
if (said( open, door)) { // first block of AGI statements } else { // second block of AGI statements }
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.
if (!said( open, door)) goto label1; // first block of AGI statements goto label2: label1: // second block of AGI statements label2:
This is exactly how all ifs and elses 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 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:
if (said(marble)) FF 0E 01 1E 01 FF if (said( open, door)) FF 0E 02 37 02 73 00 FF
In the above examples, the values 0x011E, 0x0237, and 0x0073 are just random word numbers that could stand for the words given.
At first I almost totally discarded the existence of loops in the AGI code because it seemed to me that execution of the logic script 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:
if (greatern(30, 45) && lessn(30, 55)) { print("You're in the hot zone!"); increment(30); }
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 script 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 infact be coded into logic scripts.
Example:
FF FD 0D FF 03 00 FE F7 FF do { } while (!havekey);
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.