GOOL Interpreter
In the Crash games, the GOOL Interpreter is responsible for interpreting an object's executable bytecode. This bytecode instructs the interpreter to perform a specific sequence of operations that can conditionally or unconditionally alter specific characteristics of the object (ex. appearance, location, status, etc.), and should ultimately render the object according to its changing characteristics. As a result, the player observes a lively, animated, and interactive enemy, box, collectible item, or whatever other kind of in-game object it may be.
Contents
Interpretation
A single interpretation of an object's bytecode consists of the following steps:
- Fetch the instruction at the object's program counter location
- Read the instruction at the object's program counter location
- Point the object's program counter to the location of the following instruction
- Perform the operation specified by the fetched instruction
- Continue repeating steps 1 and 2 until a suspending instruction is fetched
(A suspending instruction is typically either a RET, which marks the end of a block, ANIF/ANIS, which change the object's current frame of animation, CST/CNEZ/CEQZ, which change the object's state, or EVNT/EVNB/EVNU, which send an event to one or multiple objects.)
A single call to the GOOL interpreter routine with some desired object as its first argument results in a single interpretation of that object's bytecode. The GOOL bytecode interpreter routine expects an object that provides the following information:
- The location in its bytecode at which the interpreter shall begin its interpretation = object's program counter
- The location in its memory of a stack frame created for the interpretation = object's frame pointer
- The location in its memory at which the interpreter shall begin pushing the results of/popping the operands for interpreted instructions = object's stack pointer
Prior to an interpretation, each of these fields (program counter, frame pointer, stack pointer) is modified according to the object's current state [descriptor] and the type of interpretation.
Types of interpretation
Many separate interpretations of an object's bytecode can occur within a single frame. The GOOL interpreter routine can be called with the same object from up to 7 distinct locations in a single iteration of the game loop. Each of the 7 calls to the interpreter routine with that object, and thus the ensuing interpretations of its bytecode, occurs under a separate condition. The 7 interpretations are identified based on the conditions under which they occur, and are as follows:
- One that occurs at a specified rate, i.e. after a specific amount of time has elapsed since its previous occurrence. (Code Block interpretation)
- One that [always] occurs [for each frame]. (Trans Block interpretation)
- One that occurs whenever the object is sent an event and its current state specifies the location of an event service routine. (Event Block interpretation)
- One that occurs whenever the object is sent an event and: its current state does not specify an event service routine or its event service routine failed to return an event, and the event sent maps to an event handler. (Handles Block interpretation)
- One that occurs whenever the object changes state. (Head Block interpretation)
- One that occurs whenever the object changes state and its recent Trans Block interpretation was unsuccessful (*Trans Block interpretation)
- One that occurs when the object minimizes the distance during another object's query to 'find the nearest object', and the event sent for asynchronous handling (from the querying object) maps to an event handler. (*Handles Block interpretation)
Ignoring the details for now, the latter 2 types are just a Trans Block and Handles Block interpretation, respectively, although their calls cross-reference the interpreter routine from different locations. Thus, there are a total of 5 types. These 5 types are more simply viewed as 5 separate threads of execution of the object's bytecode. To demonstrate, here is the expanded game loop routine with only the calls to routines that have a descendant call to the interpreter routine:
sub_80011FC4(levelid) { ... C) sub_80011DD0 - Load entries and/or create universal game objects (HUD/display, Crash, Aku Aku, Shadows, Boxes, Fruit) 1) sub_8001C6C8 - CreateObject() [called once for each object created] ... GAME LOOP: { 1) Code for handling game pause/start button press A) If start pressed... i) If pause menu object does not exist.... 1) sub_8001C6C8 - CreateObject() [create pause menu object] ... else 1) sub_80024040 - SendEvent() [destroy pause menu object] ... 2) If Crash object does not exist: A) sub_8002E98C - Create HUD and initialize level i) sub_8001C6C8 - CreateObject() [create HUD object] ii)sub_80026650 - Reinitialize Level 1) sub_8001C6C8 - CreateObject() [create Crash object] ... 3) Code for loading a new level if necessary A) If new level to load ... ii)sub_80011DD0 - Load entries and/or create universal game objects 1) sub_8001C6C8 - CreateObject() [called once for each object created] ... ... 5) Spawn all objects in current zone [for those that have their respawn bit set] A) sub_80025928 - Spawn objects 1) sub_8001BCC8 - SpawnObject() [called for each object spawned] ... ... 9) Update all objects and create transformed primitives for them A) sub_8001D5EC - UpdateObjects() 1) sub_8001DA0C - UpdateObject() [called for each existing object] ... } }
Based on the above, consider an arbitrary frame of execution in terms of a single object. CreateObject() or SpawnObject() will not be called more than once [to create that object] in that frame; that is-the object is not created or spawned more than once, or even spawned after it is created and vice-versa. The object may not even be created/spawned in that frame because it either already exists or does not exist since the game has not requested it be created/spawned. If it is an existing object, it will also be updated only once in that frame. For a single object, the above can be reduced the following series of potential calls:
sub_80011FC4(levelid) { ... GAME LOOP: { ... CreateObject(obj,...) or SpawnObject(obj,...) ... SendEvent(src,obj,...) ... UpdateObject(obj) } }
What the above leaves out, however, are the number of other locations at which a call to the SendEvent() routine reside. Some are located before the CreateObject or SpawnObject routine calls, some after, some within the UpdateObject routine itself, and even some within the interpreter routine, since there exist several types of GOOL instructions that allow another object to send an event to the recipient object. In fact, there is nothing preventing the object from being the recipient of more than one event sent (from potentially multiple sources) in a single frame. There may be an interpretation for each event sent to the object during that frame, and there is no limit on the number of events that can be sent. Thus, there may be many calls to SendEvent() [with the object as the recipient] in a single frame, but still only at most one call to CreateObject/SpawnObject() and UpdateObject(). Without getting too far off track, it is simply necessary to understand that each of these routines contains (or calls a routine that calls a routine that contains) a call to the interpreter routine:
sub_80011FC4(levelid) { ... GAME LOOP: { ... CreateObject(obj,...) or SpawnObject(obj,...) InitObject(obj, ...) ChangeObjectState(obj, ...) CreateObjectStackFrame(obj); // *create initial stack frame -if there is a head block to interpret for the object CreateObjectStackFrame(obj); obj->pc = obj->pchead; InterpretObject(obj, 0x13, &stateref); // # 1 (head block interpretation) SendEvent(src,obj,...) // there may be multiple calls to this -if there is an event service routine for the object (in its current state) CreateObjectStackFrame(obj); obj->pc = obj->pcevent; InterpretObject(obj, 0x8, &stateref); // # 2 (event block interpretation) -if there is not an event service routine for the object (in its current state) or the event service routine failed to return an event -if the event maps to an event handler in the event->state/handler map CreateObjectStackFrame(obj); obj->pc = handlerlocation; InterpretObject(obj, 0x3, &stateref); // # 3 (handles block interpretation) -else ChangeObjectState(obj,state,...); // * UpdateObject(obj) -if the object is able to animate -if there is a trans block to interpret for the object (in its current state) CreateObjectStackFrame(obj); obj->pc = obj->pctrans; InterpretObject(obj, 0x3, &stateref); // # 4 (trans block interpretation) -if at least n ticks have elapsed, where n is given by the 'wait' operand of the most recently suspending animation type instruction of the object's code block InterpretObject(obj, 0x4, &stateref); // # 5 (code block interpretation) } }
Notice that immediately prior to each call [to the interpreter routine], with the exception of #5, a new stack frame is created for the object (which includes pointing its frame pointer and stack pointer, respectively, to the beginning and ending of its new frame), and its program counter is pointed to a distinct location within its bytecode. If each bytecode instruction is represented by a pseudo-instruction, a potential sequence of calls to the interpreter routine in a single frame [for only that object] might be represented by the following:
label | address | pseudo-instruction pcevent: 0x34 N // call A 0x38 N 0x3C S ... pchead: 0x0 N // call B 0x4 N 0x8 N 0xC S ... pctrans: 0x50 N // call C 0x54 N 0x58 N 0x5C N 0x60 N 0x64 N 0x68 S ... *pccode: 0x70 N // call D 0x74 N 0x78 N 0x7C N 0x80 S ... pcevent: 0x34 N // call E 0x38 N 0x3C S ... pcevent: 0x34 N // call F 0x38 N 0x3C S
Each call above to the interpreter routine is represented by the sequence of instructions (represented as pseudo-instructions) fetched during its interpretation. An N pseudo-instruction represents an instruction that did not suspend the interpreter, causing the next instruction in sequence to be fetched and interpreted. An S pseudo-instruction represents an instruction that did suspend the interpreter, causing the interpreter routine to end the interpretation/return to its caller. Each pseudo-instruction is listed with the address of the instruction it represents in the object's bytecode [relative to the beginning].
The first instruction fetched in each interpretation is also marked with a label; this label is given the name of the object field that points to its associated instruction. Immediately prior to each interpretation, with the exception of the one marked *pccode, the object's program counter is pointed to the location in this particular field; it is this operation that causes each interpretation to begin at the location it does. This particular object field is also primarily responsible for determining the type of interpretation:
Field | Interpretation Type |
---|---|
pctrans | Trans Block Interpretation |
pcevent | Event Block Intepretation |
pchead | Head Block Interpretation |
pc | Code Block Interpretation |
The pctrans
, pcevent
, and pchead
fields each contain a pointer to/the absolute location of an individual block in the object's bytecode. These fields locate the respective entry points for 3 separate threads of interpretation for the object-a trans thread, an event thread, and a head thread. Whenever the object changes state, its pctrans
and pcevent
fields are modified according to the state descriptor for its new state; its pchead
field is then cleared. (If the object wishes to modify its pchead
field, it does so via a MOVC instruction in its bytecode.)
Whenever the object changes state, its program counter (pc
field) is also set directly to the location of the code block specified by the pccode
field of the state descriptor for its new state. The entirety of the object's stack contents are then unwound, or the object's stack pointer is reset to its initial location, and an initial stack frame is [re]created. This program counter location and stack configuration is then restored after each subsequent non-code block interpretation is completed. (That is-since those interpretations require the program counter value to be replaced with a new location and a new stack frame to be created, after they are completed, the original program counter location and initial stack frame are restored). When the interpretation marked #5 in the above pseudo-code (the code block interpretation) finally occurs, the object's program counter will point to its code block and its stack frame will be the initial frame. The code block interpretation is yet another thread of interpretation for the object-a code thread.
For the sake of explanation, handles blocks are viewed as being equivalent to event blocks, so handles interpretations and threads are simply ignored. The timeline to the right shows an arbitrary frame of execution in terms of the times elapsed for the various interpretations of a single object's bytecode. Towards the beginning of the frame, the interpreter has performed a code block interpretation and a trans block interpretation for the object. Towards the end of the frame, the interpreter has performed an event block interpretation for that object, as it has been sent an event from some [unknown] source. The following timeline extends this timeline [to the right] to 6 arbitrary frames of execution:
The extended timeline makes it clear that this particular object has been configured to have its code block interpretation occur every other frame. It also shows the performance of more than one event block interpretation for that object during the second frame. The following timeline shows the same 6 frames of execution in terms of the times elapsed for all interpretations of each existing object's bytecode; in this particular instance, there are only 2 existing objects.
Stack Frame (incomplete)
A new stack frame is created for an object immediately prior to an interpretation of its bytecode. It is also created when jumping to and linking a bytecode routine via the JAL instruction. An object's initial stack frame is created when it enters its initial state, and is recreated for each subsequent state change (after the unwinding the stack). The initial stack frame is used for the object's code block interpretation. At any given point in time, an object's frame pointer and stack pointer, respectively, locate the beginning and end of its current stack frame.
Format
A stack frame has the following format:
Offset | Field | Size | Value |
---|---|---|---|
-0x4 x a | **Argument a | 4 bytes | * |
0x0 | Preserved Interpreter Mode Flags | 4 bytes | 0xFFFF for initial frame; * otherwise |
0x4 | Preserved Program Counter | 4 bytes | * |
0x8 | Preserved Frame Pointer Relative Offset | 2 bytes | f |
0xA | Preserved Stack Pointer Relative Offset | 2 bytes | s |
0xC | Frame Data | * x 4 bytes | * |
(**Arguments come before frames and are not actually 'part' of them)
Initial Stack Frame
When the object is first created/spawned, or when it changes state, its program counter is pointed to the code block with offset pccode
(given by the state descriptor for its initial or new state), its frame pointer is cleared, and its stack pointer is reset to its initial location:
The object's initial stack frame is then [re]created by pushing the following sequence of values to the stack:
- 0x0000FFFF - Interpreter Mode Flags. Not an actual interpreter mode flags value but rather an indicator that an unwinding of this frame shall not restore interpreter mode flags.
obj->pc
- Program Counter. At this point, it points to the code block for the object's initial/new state.(((unsigned long)obj->fp - (unsigned long)&obj->self) << 16) | ((unsigned long)obj->sp - (unsigned long)&obj->self))
Frame Pointer and Stack Pointer relative offsets. At this point, frame pointer (obj->fp
) is 0 and stack pointer points to its initial location; this combined value should be of the form 0x0000****.
The object's frame pointer is then pointed to the beginning of that frame. With these values having been pushed to its stack, the object's stack pointer now points to the end of that frame. Thus, immediately following the instantiation or state change, the object's current stack frame is its initial stack frame.
During the object's code block interpretation, this frame is then extended with additional data, including stack operands and/or the results of interpreted instructions.
Non-Initial Stack Frame
After an object's initial stack frame is created, but before its next code block interpretation occurs, several other non-code block interpretations may be performed. Immediately prior to performing any of these interpretations, a new stack frame is created on top of the initial stack frame, and then the current program counter value (which contains the code block location) is replaced with the location of the non-code block. This frame is created by pushing the following sequence of values to the stack:
modeflags
- Interpreter Mode Flagsobj->pc
- Program Counter. At this point, it points to the code block for the object's initial/new state.(((unsigned long)obj->fp - (unsigned long)&obj->self) << 16) | ((unsigned long)obj->sp - (unsigned long)&obj->self))
Frame Pointer and Stack Pointer relative offsets. At this point, frame pointer points to the beginning of the initial frame, and stack pointer points to the end of the initial frame.
The object's frame pointer is then pointed to the beginning of that frame. With these values having been pushed to its stack, the object's stack pointer now points to the end of that frame. Thus, immediately following the creation of the frame, it is the object's current stack frame-that is, the initial stack frame is no longer the current stack frame.
When the following interpretation finishes, the object's program counter is restored to the code block location preserved in the current frame, and the preserved frame pointer and stack pointer offsets are used to restore the initial stack frame as the current frame.
UNFINISHED
Interpreter Mode Flags
TBD
GOOL Instructions
Crash 1
The GOOL Interpreter in Crash 1 recognizes a total of 58 different GOOL instructions:
- 6 Arithmetic Instructions (incl SHA*)
- 4 Non-bitwise Logical/Logical Comparison Instructions
- 4 Bitwise Logical Instructions
- 5 Comparison Instructions
- 5 Data Transfer Instructions
- 8 Mathematical Function Instructions
- 4 Control Flow Instructions
- 19 Miscellaneous/Multi-Purpose Instructions
GOOL Instruction Tables
Main Instruction Table
The GOOL instruction table below lists the opcode, mnemonic/name, format, operands (based on explicit and/or implicit specification of source origin and/or destination target), general operation (represented with c-style statement), and a description for each GOOL instruction.
Opcode | Name | Encoding/Format | Explicit GOOL ops
in |
Explicit IMM. ops
in |
Implicit STACK ops
in |
Operation | Implicit STACK out | Explicit
GOOL ops out |
Description |
---|---|---|---|---|---|---|---|---|---|
0/0x00 | ADD | 00000000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L + R | O | add | |||
1/0x01 | SUB | 00000001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L - R | O | subtract | |||
2/0x02 | MUL | 00000010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L * R | O | multiply | |||
3/0x03 | DIV | 00000011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L / R | O | divide | |||
4/0X04 | CEQ | 00000100RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L == R),
O = ((L ^ R) == 0) |
O | check if equal | |||
5/0x05 | ANDL | 00000101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L && R),
O = (L ? (R > 0) : 0) |
O | logical and | |||
6/0x06 | ORL | 00000110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L || R) | O | logical or | |||
7/0x07 | ANDB | 00000111RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L & R | O | bitwise and | |||
8/0x08 | ORB | 00001000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L | R | O | bitwise or | |||
9/0x09 | SLT | 00001001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L < R | O | set less than | |||
10/0x0A | SLE | 00001010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L <= R) | O | set less than or equal | |||
11/0x0B | SGT | 00001011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L > R | O | set greater than | |||
12/0x0C | SGE | 00001100RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L >= R) | O | set greater than or equal | |||
13/0x0D | MOD | 00001101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L % R | O | modulo | |||
14/0x0E | XOR | 00001110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L ^ R | O | exclusive or | |||
15/0x0F | TST | 00001111RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (((L & R) ^ R) == 0) | O | test bit | |||
16/0x10 | RND | 00010000AAAAAAAAAAAABBBBBBBBBBBB | A,B | O = B+(rand() % (A - B)) | O | random | |||
17/0x11 | MOVE | 00010001SSSSSSSSSSSSDDDDDDDDDDDD | S | D = S | [D] | move data | |||
18/0x12 | NOTL | 00010010SSSSSSSSSSSSDDDDDDDDDDDD | S | D = (S == 0) | D | logical not | |||
19/0x13 | PATH | 00010011AAAAAAAAAAAABBBBBBBBBBBB | (A),B | R = 0x100 | [R,A] | varies | P | B | path progress |
20/0x14 | LEA | 00010100SSSSSSSSSSSSDDDDDDDDDDDD | S | D = &S | D | load effective address | |||
21/0x15 | SHA | 00010101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = ((R < 0) ? (L >> -R)
: (L << R)) |
O | arithmetic shift | |||
22/0x16 | PSHV | 00010110AAAAAAAAAAAABBBBBBBBBBBB | [A,[B]] | [arg_buf = A]
I = A; J = B; |
[I,[J]] | push value to stack | |||
23/0x17 | NOTB | 00010111SSSSSSSSSSSSDDDDDDDDDDDD | S | D = ~S | D | bitwise not | |||
24/0x18 | MOVC | 000110000000RRRRRRIIIIIIIIIIIIII | R,C | see docs | O | move code pointer | |||
25/0x19 | ABS | 00011001SSSSSSSSSSSSDDDDDDDDDDDD | S | D = (S < 0) ? -S: S | D | absolute value | |||
26/0x1A | PAD | 00011010000TDDDDSSPPBBBBBBBBBBBB | B,P,S,D,T | O = testctrls(instr,0) | O | test controller buttons | |||
27/0x1B | SPD | 00011011VVVVVVVVVVVVBBBBBBBBBBBB | V,B | S = B + ((V*gvel) >> 10) | S | calculate speed | |||
28/0x1C | MSC | 00011100PPPPSSSSSLLLXXXXXXXXXXXX | X | P,S,L | various; see docs | *** | *** | multi-purpose | |
29/0x1D | PRS | 00011101PPPPPPPPPPPPDDDDDDDDDDDD | P,D | large calc; see docs | O | driven sine wave | |||
30/0x1E | SSAW | 00011110DDDDDDDDDDDDPPPPPPPPPPPP | M,P | O = (M + frameCount) % P | O | synchronized saw wave | |||
31/0x1F | RGL | 00011111000000000000IIIIIIIIIIII | I | O = globals[I >> 8] | O | read global variable | |||
32/0x20 | WGL | 00100000SSSSSSSSSSSSIIIIIIIIIIII | I,S | globals[I << 8] = *S | write global variable | ||||
33/0x21 | ANGD | 00100001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = angdist(L,R) | angle between | ||||
34/0x22 | APCH | 00100010RRRRRRRRRRRRLLLLLLLLLLLL | L,(R) | S = 0x100 | [R,S] | O = approach(L,R,S) | O | approach a value | |
35/0x23 | CVMR | 00100011000IIIIIILLL000000000000 | I,L | O = obj.link[L].colors[I] | color vector or matrix read | ||||
36/0x24 | CVMW | 00100100000IIIIIILLLCCCCCCCCCCCC | C | I,L | obj.link[L].colors[I] = C | color vector or matrix write | |||
37/0x25 | ROT | 00100101RRRRRRRRRRRRLLLLLLLLLLLL | L,(R) | [R,S] | O = rotate(L,R,S,0) | O | approach an angle | ||
38/0x26 | PSHP | 00100110AAAAAAAAAAAABBBBBBBBBBBB | [A,[B]] | I = &A; J = &B; | [I,[J]] | push pointer to stack | |||
39/0X27 | ANID | 00100111FFFFFFFFFFFFDDDDDDDDDDDD | F | D=&obj.global.anim[F>>5] | D | set animation | |||
128/0x80 | DBG | 10000000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | debug print ops (beta only) | |||||
129/0x81 | NOP | 10000001000000000000000000000000 | no operation | ||||||
130/0x82 | CFL | 10000010TTCCRRRRRRIIIIIIIIIIIIII | T,C,R,I | general control flow | |||||
BRA | 100000100000RRRRRRVVVVIIIIIIIIII | R,V,I | branch | ||||||
BNEZ | 100000100001RRRRRRVVVVIIIIIIIIII | R,V,I | branch not equal zero | ||||||
BEQZ | 100000100010RRRRRRVVVVIIIIIIIIII | R,V,I | branch equal zero | ||||||
CST | 100000100100RRRRRRSSSSSSSSSSSSSS | R,S | change state | ||||||
CSNZ | 100000100101RRRRRRSSSSSSSSSSSSSS | R,S | change state not zero | ||||||
CSEZ | 100000100110RRRRRRSSSSSSSSSSSSSS | R,S | change state equal zero | ||||||
RET | 100000101000RRRRRRIIIIIIIIIIIIII | R,I | return | ||||||
131/0x83 | ANIS | 10000011HHTTTTTTSSSSSSSSSFFFFFFF | F,S,T,H | W | change animation sequence | ||||
132/0x84 | ANIF | 10000100HHTTTTTTFFFFFFFFFFFFFFFF | F | T,H | W | change animation frame | |||
133/0x85 | VECA | 10000101CCCTTTBBBAAAVVVVVVVVVVVV | V | T,A,B,C | * | ** | multi-purpose vector calcs | ||
134/0x86 | JAL | 10000110VVVV000000IIIIIIIIIIIIII | I,V | jump and link | |||||
135/0x87 | EVNT | 10000111LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | send an event | ||||
136/0x88 | RSTF | 10001001TTCCRRRRRR************** | R,C,T,* | ** | state return guard = false variant | ||||
137/0x89 | RSTT | 10001000TTCCRRRRRR************** | R,C,T,* | ** | state return guard = true variant | ||||
138/0x8A | CHLD | 10001010AAAATTTTTTTTSSSSSSCCCCCC | C,T,S,A | [C],
arg[0 to A] |
spawn children objects | ||||
139/0x8B | NTRY | 10001011TTTTTTTTTTTTEEEEEEEEEEEE | T,E | multi-purpose page operation | |||||
140/0x8C | SNDA | 10001100AAAAAAAAAAAABBBBBBBBBBBB | A,B | adjust audio levels | |||||
141/0x8D | SNDB | 10001101VVVVRRRRRRSSSSSSSSSSSSSS | S | V,R | play sound effect | ||||
142/0x8E | VECB | 10001110CCCTTTBBBAAAVVVVVVVVVVVV | V | T,A,B,C | multi-purpose vector calcs | ||||
143/0x8F | EVNB | 10001111LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | broadcast an event | ||||
144/0x90 | EVNU | 10010000LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | send event unknown variant | ||||
145/0x91 | CHLF | 10100000AAAATTTTTTTTSSSSSSCCCCCC | C,T,S,A | [C],
arg[0 to A] |
spawn children objects;
no replacement if obj pool full |
Note that a few instructions can not be represented with a single mnemonic; operations for these instructions are not only determined by their opcode but also their primary operation subtype and possibly secondary operation subtype fields. These instructions have their own tables, which are given in the following sections.
State Return Instruction Table
Opcode | Name | Encoding/Format | Explicit
IMM. ops in |
Description |
---|---|---|---|---|
136/0x88 | RSTT | 10001000TTCCRRRRRR************** | R,C,T,* | state return guard = true variant |
RST | 100010000100RRRRRRSSSSSSSSSSSSSS | R,S | state return guard = true | |
RSNT | 100010000101RRRRRRSSSSSSSSSSSSSS | R,S | state return if nonzero guard = true | |
RSZT | 100010000110RRRRRRSSSSSSSSSSSSSS | R,S | state return if equal zero guard = true | |
RSCT | 100010000111RRRRRRSSSSSSSSSSSSSS | R,S | state return eval prev cond guard = true | |
RNT | 100010001000RRRRRRxxxxxxxxxxxxxx | R | null return guard = true | |
RNNT | 100010001001RRRRRRxxxxxxxxxxxxxx | R | null return if nonzero guard = true | |
RNZT | 100010001010RRRRRRxxxxxxxxxxxxxx | R | null return if equal zero guard = true | |
RNCT | 100010001011RRRRRRxxxxxxxxxxxxxx | R | null return eval prev cond guard = true | |
GDT | 100010001100RRRRRRxxxxxxxxxxxxxx | R | guard = true | |
GNT | 100010001101RRRRRRxxxxxxxxxxxxxx | R | if nonzero guard = true | |
GZT | 100010001110RRRRRRxxxxxxxxxxxxxx | R | if equal zero guard = true | |
GCT | 100010001111RRRRRRxxxxxxxxxxxxxx | R | eval prev cond guard = true | |
GBNT | 100010000001RRRRRRVVVVIIIIIIIIII | R,V,I | if nonzero guard = true else branch | |
GBZT | 100010000010RRRRRRVVVVIIIIIIIIII | R,V,I | if equal zero guard = true else branch | |
137/0x89 | RSTF | 10001001TTCCRRRRRR************** | R,C,T,* | state return guard = false variant |
RSF | 100010000100RRRRRRSSSSSSSSSSSSSS | R,S | state return guard = false | |
RSNF | 100010000101RRRRRRSSSSSSSSSSSSSS | R,S | state return if nonzero guard = false | |
RSZF | 100010000110RRRRRRSSSSSSSSSSSSSS | R,S | state return if equal zero guard = false | |
RSCF | 100010000111RRRRRRSSSSSSSSSSSSSS | R,S | state return eval prev cond guard = false | |
RNF | 100010001000RRRRRRxxxxxxxxxxxxxx | R | null return guard = false | |
RNNF | 100010001001RRRRRRxxxxxxxxxxxxxx | R | null return if nonzero guard = false | |
RNZF | 100010001010RRRRRRxxxxxxxxxxxxxx | R | null return if equal zero guard = false | |
RNCF | 100010001011RRRRRRxxxxxxxxxxxxxx | R | null return eval prev cond guard = false | |
GDF | 100010001100RRRRRRxxxxxxxxxxxxxx | R | guard = false | |
GNF | 100010001101RRRRRRxxxxxxxxxxxxxx | R | if nonzero guard = false | |
GZF | 100010001110RRRRRRxxxxxxxxxxxxxx | R | if equal zero guard = false | |
GCF | 100010001111RRRRRRxxxxxxxxxxxxxx | R | eval prev cond guard = false | |
GBNF | 100010000001RRRRRRVVVVIIIIIIIIII | R,V,I | if nonzero guard = false else branch | |
GBZF | 100010000010RRRRRRVVVVIIIIIIIIII | R,V,I | if equal zero guard = false else branch |
Crash 2
The GOOL Interpreter in Crash 2 recognizes a total of 79 GOOL instructions, including two duplicates. Many of the instructions are known. The basic arithmetic and logical operations are preserved the same as in Crash 1, but most others have been changed.
Crash 2 GOOL code may also contain sections of machine code for the MIPS processor architecture (used by the playstation).
GOOLv2 Instruction Table
The GOOL instruction table below lists the opcode, mnemonic/name, format, operands (based on explicit and/or implicit specification of source origin and/or destination target), general operation (represented with c-style statement), and a description for each GOOL instruction.
Opcode | Name | Encoding/Format | Explicit GOOL ops
in |
Explicit IMM. ops
in |
Implicit STACK ops
in |
Operation | Implicit STACK out | Explicit
GOOL ops out |
Description |
---|---|---|---|---|---|---|---|---|---|
0/0x00 | ADD | 00000000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L + R | O | add | |||
1/0x01 | SUB | 00000001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L - R | O | subtract | |||
2/0x0 | MUL | 00000010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L * R | O | multiply | |||
3/0x03 | DIV | 00000011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L / R | O | divide | |||
4/0x04 | CEQ | 00000100RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L == R),
O = ((L ^ R) == 0) |
O | check if equal | |||
5/0x05 | ANDL | 00000101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L && R),
O = (R ? L : R) |
O | logical and | |||
6/0x06 | ORL | 00000110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L || R)
O = L | R |
O | logical or (same as ORB) | |||
7/0x07 | ANDB | 00000111RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L & R | O | bitwise and | |||
8/0x08 | ORB | 00001000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L | R | O | bitwise or | |||
9/0x09 | SLT | 00001001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L < R | O | set less than | |||
10/0x0A | SLE | 00001010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L <= R) | O | set less than or equal | |||
11/0x0B | SGT | 00001011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L > R | O | set greater than | |||
12/0x0C | SGE | 00001100RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L >= R) | O | set greater than or equal | |||
13/0x0D | MOD | 00001101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L % R | O | modulo | |||
14/0x0E | XOR | 00001110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L ^ R | O | exclusive or | |||
15/0x0F | TST | 00001111RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (((L & R) ^ R) == 0) | O | test bit | |||
16/0x10 | RND | 00010000AAAAAAAAAAAABBBBBBBBBBBB | A,B | O = B+(rand() % (A - B)) | O | random | |||
17/0x11 | MOVE | 00010001SSSSSSSSSSSSDDDDDDDDDDDD | S | D = S | [D] | move data | |||
18/0x12 | NOTL | 00010010SSSSSSSSSSSSDDDDDDDDDDDD | S | D = (S == 0) | D | logical not | |||
19/0x13 | PATH | 00010011AAAAAAAAAAAABBBBBBBBBBBB | (A),B | R = 0x100 | [R,A] | varies | P | B | path progress |
20/0x14 | LEA | 00010100SSSSSSSSSSSSDDDDDDDDDDDD | S | D = &S | D | load effective address | |||
21/0x15 | SHA | 00010101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = ((R < 0) ? (L >> -R)
: (L << R)) |
O | bit shift | |||
22/0x16 | PUSH | 00010110BBBBBBBBBBBBAAAAAAAAAAAA | A,[B] |
I = A; J = B; |
I,[J] | push value(s) to stack | |||
23/0x17 | NOTB | 00010111SSSSSSSSSSSSDDDDDDDDDDDD | D = ~S | D | bitwise not (nor) | ||||
24/0x18 | MOVC | 00011000RRRRRR000EIIIIIIIIIIIIII | R,E,I | see docs | O | move code pointer | |||
25/0x19 | ABS | 00011001SSSSSSSSSSSSDDDDDDDDDDDD | S | D = (S < 0) ? -S : S | D | absolute value | |||
26/0x1A | PAD | 00011010000TDDDDSSPPBBBBBBBBBBBB | B,P,S,D,T | O = testctrls(instr,0) | O | test controller buttons | |||
27/0x1B | SPD | 00011011VVVVVVVVVVVVBBBBBBBBBBBB | V,B | S = B + ((V*gvel) >> 10) | S | calculate speed | |||
28/0x1C | MSC | 00011100PPPPSSSSSLLLXXXXXXXXXXXX | X | P,S,L | various | multi-purpose | |||
29/0x1D | PSIN | 00011101PPPPPPPPPPPPDDDDDDDDDDDD | P,D | large calc; see docs | driven sine wave | ||||
30/0x1E | TICK | 00011110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (R + TICKS) % L | O | get ticks | |||
31/0x1F | RGL | 00011111000000000000IIIIIIIIIIII | I | O = globals[I] | O | read global variable | |||
32/0x20 | WGL | 00100000SSSSSSSSSSSSIIIIIIIIIIII | I,S | globals[I] = *S | write global variable | ||||
33/0x21 | ANGD | 00100001RRRRRRRRRRRRLLLLLLLLLLLL | R,L | O = ((R << 20) - (L << 20)) >> 20 | O | angle distance (note: shifts are for sign extension) | |||
34/0x22 | APCH | 00100010RRRRRRRRRRRRLLLLLLLLLLLL | L,(R) | O = approach(L,R,S) | O | approach a value | |||
35/0x23 | CVMR | 00100011000IIIIIILLL000000000000 | I,L | O = obj.link[L].colors[I] | O | color vector or matrix read | |||
36/0x24 | CVMW | 00100100000IIIIIILLLCCCCCCCCCCCC | C | I,L | obj.link[L].colors[I] = C | color vector or matrix write | |||
37/0x25 | ROT | 00100101RRRRRRRRRRRRLLLLLLLLLLLL | (R),L | [R,S] | O = rotate(L,R,S,0) | approach an angle | |||
38/0x26 | PSHP | 00100110BBBBBBBBBBBBAAAAAAAAAAAA | A,[B] | I = &A; J = &B; | I,[J] | push pointer(s) | |||
39/0x27 | ANID | 00100111FFFFFFFFFFFFDDDDDDDDDDDD | F | D=&obj.global.anim[F>>5] | D | set animation | |||
40/0x28 | EFLD | 00101000BBBBBBBBBBBBAAAAAAAAAAAA | A,B | O = GetFieldValue(obj->entity, field: A, row: B) | O | push entity field data | |||
41/0x29 | EFLS | 00101001BBBBBBBBBBBBAAAAAAAAAAAA | B | A = GetFieldValue(obj->entity, field: B) | A | get entity field pointer? | |||
42/0x2A | ARRL | 00101010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = *(L + R)
o = L[R] |
O | load array value | |||
43/0x2B | SIN | 00101100SSSSSSSSSSSSDDDDDDDDDDDD | S | D = sin(S) | D | sine wave (output: -0x1000 to 0x1000) | |||
44/0x2C | COS | 00101101SSSSSSSSSSSSDDDDDDDDDDDD | S | D = cos(S)
D = sin(S + PI/2) |
D | cosine wave (output: -0x1000 to 0x1000) | |||
45/0x2D | ATAN | 00101110YYYYYYYYYYYYXXXXXXXXXXXX | X,Y | o = atan2(X, Y) | o | arc-tangent2, angle to point (confirmation needed) | |||
46/0x2E | ? | 00101110SSSSSSSSSSSSDDDDDDDDDDDD | A | D | unknown | ||||
47/0x2F | NOP | 00101111------------------------ | no operation | ||||||
48/0x30 | DBG | 00110000BBBBBBBBBBBBAAAAAAAAAAAA | (debug only) print gool fixed point values | ||||||
49/0x31 | RET | 001100011000RRRRRRIIIIIIIIIIIIII | R,I | conditionally stop gool execution (R and I ignored) | |||||
50/0x32 | BRA | 001100100000RRRRRRVVVVIIIIIIIIII | R,V,I | branch (R ignored) | |||||
51/0x33 | BNEZ | 001100110001RRRRRRVVVVIIIIIIIIII | R,V,I | branch not equal zero | |||||
52/0x34 | BEQZ | 001101000010RRRRRRVVVVIIIIIIIIII | R,V,I | branch equal zero | |||||
53/0x35 | CST | 00110101----RRRRRRAAAASSSSSSSSSS | R,A,S | change object state | |||||
54/0x36 | CNEZ | 00110110----RRRRRRAAAASSSSSSSSSS | R,A,S | change state if not zero | |||||
55/0x37 | CEQZ | 00110111----RRRRRRAAAASSSSSSSSSS | R,A,S | change state if zero | |||||
56/0x38 | ANIS | 00111000HHTTTTTTSSSSSSSSSFFFFFFF | F,S,T,H | W | change animation sequence | ||||
57/0x39 | ANIF | 00111001HHTTTTTT----FFFFFFFFFFFF | F | H,T | W | change animation frame from variable | |||
58/0x3A | VECA | 00111010CCCTTTBBBAAAVVVVVVVVVVVV | V | C,T,B,A | * | ** | multi-purpose vector calcs | ||
59/0x3B | JAL | 00111011VVVV00000EIIIIIIIIIIIIII | I,V | jump and link | |||||
60/0x3C | EVNT | 00111100LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | send an event | ||||
61/0x3D | RSTT | 00111101TTCCRRRRRR************** | R,C,T,* | ** | state return guard = true variant | ||||
62/0x3E | RSTF | 00111110TTCCRRRRRR************** | R,C,T,* | ** | state return guard = false variant | ||||
63/0x3F | CHLD | 00111111AAAATTTTTTTTSSSSSSCCCCCC | C,T,S,A | [C],
arg[0 to A] |
spawn children objects | ||||
64/0x40 | NTRY | 01000000TTTTTTTTTTTTEEEEEEEEEEEE | T,E | multi-purpose page operation | |||||
65/0x41 | SNDA | 01000001AAAAAAAAAAAABBBBBBBBBBBB | A,B | adjust audio levels | |||||
66/0x42 | SNDB | 01000010VVVVFFRRRRRRSSSSSSSSSSSS | S | V,F,R | play sound effect | ||||
67/0x43 | VECB | 01000011CCCTTTBBBAAAVVVVVVVVVVVV | V | C,T,B,A | multi-purpose vector calcs | ||||
68/0x44 | EVNC | 01000100LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | send an event, cascading | ||||
69/0x45 | EVNB | 01000101LLLAAARRRRRREEEEEEEEEEEE | R | L,A,R | send a cascading event to all objects | ||||
70/0x46 | CHLF | 01000110AAAATTTTTTTTSSSSSSCCCCCC | A,T,S,C | [C],
arg[0 to A] |
spawn children objects;
no replacement if obj pool full | ||||
71/0x47 | JALR | 01000111AAAAAAAAAAAAPPPPPPPPPPPP | P,A | arg[0 to A] | jump and link to instruction pointed at by P | ||||
72/0x48 | ? | 01001000AAAAAAAAAAAABBBBBBBBBBBB | A,B | unknown | |||||
73/0x49 | NATIVE | 01001001101111100000101111100000 | begin native (psx mips) code | ||||||
74/0x4A | ? | 01001010------------AAAAAAAAAAAA | A | unknown | |||||
75/0x4B | ? | 01001011------------AAAAAAAAAAAA | A | unknown | |||||
76/0x4C | ? | 01001100AAAAAAAAAAAABBBBBBBBBBBB | A,B | unknown | |||||
77/0x4D | ? | 01001101AAAAAAAAAAAABBBBBBBBBBBB | A,B | P,T | unknown | ||||
78/0x4E | ARRS | 00101011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | S | *(L + R) = S
L[R] = S |
store array value |
GOOLv3 Instruction Table
The GOOL instruction table below lists the opcode, mnemonic/name, format, operands (based on explicit and/or implicit specification of source origin and/or destination target), general operation (represented with c-style statement), and a description for each GOOL instruction added in Crash 3.
Opcode | Name | Encoding/Format | Explicit GOOL ops
in |
Explicit IMM. ops
in |
Implicit STACK ops
in |
Operation | Implicit STACK out | Explicit
GOOL ops out |
Description |
---|---|---|---|---|---|---|---|---|---|
79/0x4F | ? | 01001100AAAAAAAAAAAABBBBBBBBBBBB | B | A | unknown | ||||
80/0x50 | ? | 01001101AAAAAAAAAAAABBBBBBBBBBBB | B | A | unknown | ||||
81/0x51 | ? | 01001100AAAAAAAAAAAABBBBBBBBBBBB | A,B | unknown matrix math |
GOOL Instruction Operands
...
- In/Source type operands
- Explicitly specified (i.e. origin = determined by operand's corresponding bitfield from the instruction)
- GOOL in/source operand
- Immediate operand
- Implicitly specified (i.e. origin = top of object's stack; implicitly popped from top of object's stack)
- Stack in/source operand
- Explicitly specified (i.e. origin = determined by operand's corresponding bitfield from the instruction)
- Out/Destination type operands
- Explicitly specified (i.e. target = determined by operand's corresponding bitfield from the instruction)
- GOOL out/destination operand
- Implicitly specified (i.e. target = top of object's stack; implicitly pushed to top of object's stack)
- Stack out/destination operand/result*
- Explicitly specified (i.e. target = determined by operand's corresponding bitfield from the instruction)
*technically not an 'operand', but rather a result of the instruction's overall operation; it is implied that instructions that use [an] operand(s) of this type always pushes it/them (i.e the result(s)) to the top of the object's stack.
Explicit Operands
A GOOL instruction's explicit operands are explicitly specified in its lower 24 bits (operand portion). Each explicitly specified operand is either an immediate operand, or a GOOL operand.
Immediate Operands
An immediate operand directly specifies its value in its corresponding bitfield (within the operand portion) of the instruction. The number of immediate operands and their positions/bit-lengths varies between instructions.
Immediate Operands are in operands (source operands).
GOOL Operands
Conversely, a GOOL operand is specified as a reference to its actual value or destination location. Each GOOL operand is 12 bits in length; a GOOL instruction may require the specification of at most 2 GOOL operands, within the upper and/or lower halves of its operand portion, respectively. The 12 bit GOOL reference that specifies a GOOL operand encodes a [32 bit] pointer to its corresponding source value and/or destination location. Operations for instructions that require GOOL operand specification first translate the corresponding GOOL reference(s) to yield their decoded pointer(s) before accessing pointed data.
See the GOOL Reference Translation section for more information.
Depending on how a GOOL operand is used by its instruction, it may be an in (source) operand, an out (destination) operand, or both (inout operand). If an instruction only reads the [input] value located by its translated GOOL operand (a pointer), it is an in (source) operand. If an instruction only writes a result/value to the location of its translated GOOL operand, it is an out (destination) operand. If an operand meets both of these criteria it is an inout operand.
Implicit Operands
Implicit In Operands
Some GOOL instructions may require the implicit specification of one or more (implicit) in operands. In operands are implicitly specified prior to an instruction's interpretation by pushing their value(s)-which must remain the topmost values at interpretation-to the object's stack. It is implied that these so-called implicit operands will exist on the stack for the instruction to pop and use as input.
Implicit Out Operands
For most GOOL instructions, the net effect of their operation involves an implicit push of one or more results to the object's stack. That is-it is implied or expected that these instructions will ultimately push their results to the object's stack.
GOOL Reference Translation
The following table lists the encoding format, potential type name, and a descriptive type name for each of the possible GOOL reference types:
Format | Potential Type Name | Descriptive Type Name |
---|---|---|
00RRRRRRRRRR | Ireg Ref | object local executable data pool reference |
01RRRRRRRRRR | Pool Ref | object external executable data pool reference |
100IIIIIIIII | Int Ref | constant mult of 256 (range: -32768 - 32512) |
1010IIIIFFFF | Frac Ref | constant mult of 16 (range: -8192 - 8176) |
10110SSSSSSS | Stack Ref | object frame argument/local variable reference [peek stack @ location relative to fp] |
101111100000 | Null Ref | null reference (translates to 0) |
101111110000 | Sp-Double Ref | double stack pop reference (translates to 1) |
110LLLMMMMMM | Reg Ref | object link memory reference |
111MMMMMMMMM (M != 0x1F) | Var Ref | object field/memory reference |
111000011111 | Stack Pop/Push | stack pop/push reference |
Potential GOOL Reference type names have been deduced based on what appears to be an unreferenced array of GOOL interpreter debug strings at 0x103B8.
Stack Pop/Push
Format | Description | ||
---|---|---|---|
111000011111 | Stack Pop/Push Reference |
A GOOL reference of this format translates differently depending on whether the operand that specifies it is an in operand or an out operand:
- In operands specified as a reference of this type translate to a pointer to the top of the object's stack after an ensuing pop of the value located there (i.e. stack pointer is decremented).
- Out operands specified as a reference of this type translate to a pointer to the top of the object's stack prior to an incrementation of its stack pointer. A stack push will result from the translation's incrementing of the object's stack pointer and the instruction's ultimate dereferencing of the returned/translated pointer for a write.
Var Ref
Format | Description | |||
---|---|---|---|---|
111FFFFFFFFF | Object Field/Memory Reference | |||
Field | Description | Domain | Range | |
F | Object Field Index | 0 to 0x1FF | 0x60 to 0x860 | R = 4N |
GOOL References of this format translate to a pointer to the A
th object field in relation to the object's self
field.
Most of an object's significant fields are located past a 0x60
byte offset from the beginning of its structure-i.e. past its self
field. Note that the last of these fields is the object's array of/address space for its [local] memory where its global variables and stack contents are stored. Because object fields are 4 bytes in size and an object's self
field is located at a 0x60 byte offset from the beginning of its structure, then the formula:
0x60 + (A * 4)
gives the byte offset of the actual field located by the translated pointer.
If A
= 0x1F
, then the reference will have the format 111000011111
; this is, however, already defined as the format for a stack pop/push reference-the processing of which takes precedence over that of var references. Notice that if such a reference were not instead translated as a stack pop/push reference, its translated pointer would locate the field at byte offset 0x60 + (0x1F * 4) = 0xDC
-the object's stack pointer. Then, it should not be possible to directly modify an object's stack pointer by accessing a translated reference. Only indirectly via translation of a stack push/pop reference should the object's stack pointer be modified.
Reg Ref
Format | Description | |||
---|---|---|---|---|
110LLLFFFFFF | Object Link Field/Memory Reference | |||
Field | Description | Domain | Range | |
F | Link Object Field Index | 0 to 0x3F | 0x60 to 0x15C | R = 4N |
L | Object Link Index | 0 to 7 | 0x60 to 0x7C | R = 4N |
GOOL references of this format translate to a pointer to the A
th object field in relation to the object's B
th link object's self
field. An object's link objects are referred to by the group of 8 object pointers beginning with its self
field; the first of these pointers is the object's self
link, the second a pointer to its parent
object, the third a pointer to its sibling
object, and so on.
Ireg Ref
Format | Description | |||
---|---|---|---|---|
00RRRRRRRRRR | Object Local Executable Data Pool Reference | |||
Field | Description | Domain | Range | |
R | Index of Value in Pool | 0 to 0x3FF | 0 to 0xFFC | R = 4N |
GOOL references of this format translate to a pointer to the predefined constant long at an offset of R
longs relative to the beginning of the object's local executable's constant data pool item.
A GOOL executable's constant data pool item is essentially a table of predefined constants. For example, it includes the EIDs of any entries, preconfigured bitfields and bit selectors, audio sample parameters (volume, pan, etc), specific speeds or velocities, and other constants referenced by this type.
NOTE: if an instruction's out operand is specified as a reference of this type, its operation will ultimately replace the constant at the translated location with its output result. A write to the executable's data pool should generally be avoided unless a global change to that constant, and therefore an equal change in behavior for each object that is an instance of that executable, is desired.
Pool Ref
Format | Description | |||
---|---|---|---|---|
01RRRRRRRRRR | Object External Executable Data Pool Reference | |||
Field | Description | Domain | Range | |
R | Index of Value in Pool | 0 to 0x3FF | 0 to 0xFFC | R = 4N |
GOOL references of this format translate to a pointer to the predefined constant long at an offset of R
longs relative to the beginning of the object's external executable's constant data pool item.
The same considerations for ireg refs apply to pool refs.
Frac Ref
Format | Description | |||
---|---|---|---|---|
100CCCCCCCCC or
100IIIIIFFFF |
Reference to Constant Multiple of 16 | |||
Field | Description | Domain | Range | |
C | Constant that will
be multiplied by 16 |
0 to 0x1FF | -8192 to 8176
-0x2000 to 0x1FF0 -16.0 to 15.9375? |
R = 16N
R = 0x10N R = 0.0625N |
I | Integral Portion? | 0 to 0x1F | -15 to 16 | R = 1N |
F | Fractional Portion? | 0 to 15 | 0 to 0.9375 | R = 0.0625N |
GOOL references of this format translate to a pointer to the value (16*C)
.
Two signed long constant buffers are located at offsets 0x40
and 0x44
, respectively, in scratch memory (as referenced by the pointer at memory location 0x56480
[gp$(0x44)
].) At any point during the game's execution, the currently active constant buffer is either the buffer at offset 0x40
or 0x44
, and is indicated by either a 0
or a 1
in the unsigned long at memory location 0x56484
[gp$(0x48)
]. Translation for references of this format consists of the following steps:
- Write the value
(16*C)
-that is, 16 times the value specified by theC
bits-to the currently active constant buffer. - Swap the currently active and inactive constant buffers (that is, if
0
previously indicated the currently active constant buffer, change it to a1
; conversely, if it is a1
, change it to a0
). - Return a pointer to the
(16*C)
signed long value in the buffer it was written to (which, after the swap, will always be the currently inactive buffer.)
This double-buffer allows the sufficient specification of up to 2 constant type GOOL references per instruction; each of the 2 buffers will keep track of up to 2 constants to be used by operations of instructions with the corresponding operands.
C
can be specified as a negative constant with the following 2's complement format:
C = 1NNNNNNNN (-N): V = 11111111111111111111NNNNNNNN0000 (16 * -N)
C = 0NNNNNNNN ( N): V = 00000000000000000000NNNNNNNN0000 (16 * N)
Where V
is the calculated signed long that will be written to the appropriate constant buffer for the specified formats of C
.
Note: out operands are not meant to specify a reference of this type; any that do so are useless. Translation of this reference type returns a pointer to the currently inactive constant buffer after storing the appropriate constant value (C * 16)
in the currently active constant buffer and then swapping the 2 buffers. The constant value in the inactive constant buffer would ultimately be overwritten with an expected result of the instruction with the out operand. However, since that buffer could only be accessed via subsequent translation of another frac or int ref, which would involve first overwriting its current value (the result) with a new constant value, then that result could never be accessed [without first being overwritten].
Int Ref
Format | Description | |||
---|---|---|---|---|
1010IIIIIIII | Reference to Constant Multiple of 256 | |||
Field | Description | Domain | Range | |
I | Constant (int) that will
be multiplied by 256 |
0 to 0xFF | -32768 to 32512
-0x8000 to 0x7F00 |
R = 256N
R = 0x100N |
GOOL references of this format are translated almost identically to frac refs; the only difference is in the calculation for the constant written to the buffer:
I = 1NNNNNNN (-N): V = 1111111111111111NNNNNNNN00000000 (256 * -N)
I = 0NNNNNNN ( N): V = 0000000000000000NNNNNNNN00000000 (256 * N)
The same considerations for frac refs apply to int refs.
Stack Ref
Format | Description | |||
---|---|---|---|---|
10110SSSSSSS | Object Frame Argument/Local Variable Reference [peek stack at location relative to fp] | |||
Field | Description | Domain | Range | |
S | Argument/Local Variable Index/Offset
relative to object fp |
0 to 0x7F
-64 args to +63 local vars |
-0x100 to 0xFC | R = 4N |
GOOL references of this format translate to a pointer to the long that is S
longs (4*S
bytes) relative to the object's current frame pointer. If S
is specified as a negative value/offset, it refers to an argument; this is because arguments are located before the start of the stack frame. If S
is specified as a positive offset (> 3), it refers to a local variable (i.e. operation result).
S
can be specified as a negative offset with the following 2's complement format:
S = 1NNNNNN (-N): O = 111111111111111111111111NNNNNN00 (4 * -N)
(argument offset)S = 0NNNNNN ( N): O = 000000000000000000000000NNNNNN00 (4 * N)
(local variable offset)
Null Ref
Format | Description | |||
---|---|---|---|---|
101111100000 = 0xBE0 | Null Reference
(translates to |
GOOL references of this format translate to the value 0
(i.e. invalid pointer to address 0
). Some instructions will return without making any calculations or pushing any results if an operand is specified as a reference of this type.
Most other instructions do not account for translated references of this type. They assume that a translated reference will always point to a valid memory location. After these instructions translate a null ref, they will attempt to access the memory at address 0x0
-which lies in the kernel code segment-thus resulting in a trap.
In the instruction table, brackets '[]' around one or more of an instruction's explicit operands (*given that parenthesis '()' are not listed around any other operand) indicate that, if any of those operands are specified as null refs, the instruction will return before performing their associated calculations. Brackets '[]' around one or more of an instruction's implicit operands (*given that parenthesis '()' are not listed around any other operand) indicate that, if the 'corresponding' explicit operands are specified as null refs, they (the implicit operands) do not actually exist. The order in which associated calculations are performed is indicated by the nested bracketing of operands.
For example, the PSHV
instruction is listed with explicit GOOL in operands [A,[B]]
, and implicit stack out operands [I,[J]]
-aliases for the respective destinations of two subsequent stack pushes. These operand nestings indicate that:
- If only operand
B
is specified as a null ref, the instruction returns before operationJ = B
(equivalently,push(obj,B)
) is performed. As a result, only operationI = A
(equivalently,push(obj,A)
) is performed. - If operand
A
is specified as a null ref, the instruction returns beforepush(obj,A)
is performed; thenpush(obj,B)
is also never performed-so the instruction does nothing. It does not matter whether or notB
is a null ref because its associated calculation/operation (i.e. push to stack) is never performed. - If neither operand
A
nor operandB
are specified as null refs, both/all operations-push(obj,A)
followed bypush(obj,B)
-are performed.
SP-Double Ref
Format | Description | |||
---|---|---|---|---|
101111110000 = 0xBF0 | Double Stack Pop Reference
(translates to |
GOOL references of this format translate to the value 1
(i.e. invalid pointer to address 1
). Specification of sp-double refs is exclusive to a few specific instructions. Most instructions do not account for translated references of this type. They assume that a translated reference will always point to a valid memory location. After these instructions translate an sp-double ref, they will attempt to access the memory at address 0x1
-which lies in the kernel code segment-thus resulting in a trap.
For a few instructions, if [a specific] one of their operands is explicitly specified as an sp-double ref, the actual values for both that operand and some other operand will be implicitly popped (in 2 subsequent pops) from the object's stack; no actual translation of the reference is performed. Alternatively, if that operand is not specified as an sp-double ref, then it is a valid reference to its actual value (and will be translated to a pointer that can be dereferenced to resolve that value); the other operand will then instead take on some default value.
In the instruction table, parenthesis '()' around one of an instruction's explicit operands indicate that: if that operand is specified as an sp-double ref, the instruction will pop the actual values for the bracketed '[]' pair of implicit stack in operands including that operand, in the order listed; otherwise the operand is a valid reference to its actual value, and its paired operand-for example, P
-will take on a default value of V
, where P = V
is listed as an explicit immediate operand for that instruction.
For example, the PATH
instruction is listed with explicit GOOL in operands (A),B
, explicit immediate in operand R = 0x100
, and implicit stack in operands [R,A]
. If operand A
is specified as an sp-double ref, the values for R
and A
will be popped from the object's stack, in that order. Otherwise, R
[rate of path travel] will take on a default value of 0x100
(1.0
) and A
's reference will be translated to resolve its value (by dereferencing, just as the majority of other instructions do for GOOL operands.)
Operand Name Table
Specification Format |
A | AAAAAAAAAAAA | AAAA | AAA (EVNT/EVNU/EVNB) | AAA (VECA/VECB) |
---|---|---|---|---|---|
Name | Value A | Argument Count | Argument Count | Vector A Index | |
Specification Format | B | BBBBBBBBBBBB | BBBBBBBBBBBB (PAD) | BBBBBBBBBBBB (SPD) | BBB |
Name | Value B | Controller Buttons | Base Speed | Vector B Index | |
Specification Format | C | CCCCCCCCCCCC | CCCCCC | CCC | CC |
Name | Color Value | Spawn Count | Vector C Index | Conditional Check Type | |
Specification Format | D | DDDDDDDDDDDD | DDDDDDDDDDDD (PRS) | DDDD | |
Name | Destination | Wave Phase | Directional Buttons | ||
Specification Format | E | EEEEEEEEEEEE (NTRY) | EEEEEEEEEEEE (EVNT/EVNU/EVNB) | E (Crash 2 MOVC) | |
Name | Entry | Event | External Executable Flag | ||
Specification Format | F | FFFFFFFFFFFFFFFF | FFFFFFFFFFFF | FFFFFFF | |
Name | Animation Frame | Animation Descriptor Offset | Animation Frame | ||
Specification Format |
H | HH | |||
Name | Horizontal Flip | ||||
Specification Format |
I | IIIIIIIIIIIIII | IIIIIIIIIIII | IIIIIIIIII | IIIIII |
Name | Immediate Code Location | Global Variable Index | Immediate Branch Offset | Color Index | |
Specification Format |
L | LLLLLLLLLLLL | LLL | ||
Name | Left Operand | Object Link Index | |||
Specification Format |
P | PPPPPPPPPPPP | PPPP | PP | |
Name | Wave Period | Primary Operation Subtype | Primary Check Type | ||
Specification Format |
R | RRRRRRRRRRRR | RRRRRR | ||
Name | Right Operand | Object Register Index | |||
Specification Format |
S | SSSSSSSSSSSSSS | SSSSSSSSSSSS | SSSSSSSSS | SSSSSS |
Name | State | Source | Animation Sequence Index | Subtype | |
Specification Format |
SSSSS | SS | |||
Name | Secondary Operation Subtype | Secondary Check Type | |||
Specification Format |
T | TTTTTTTTTTTT | TTTTTTTT | TTTTTT | TTT |
Name | Operation Subtype | (Object) Type | Time | Operation Subtype | |
Specification Format |
TT | T | |||
Name | Operation Subtype | Truth Invert Toggle | |||
Specification Format |
V | VVVVVVVVVVVV | VVVV | VVVV (SNDB) | |
Name | Velocity | Variable Count | Volume |
GOOL Instruction Operations
TBD