SCM language

From GTAMods Wiki
Revision as of 11:17, 23 August 2014 by Seemann (talk | contribs) (Specials: huge article collapsed)
Jump to navigation Jump to search
This section deals with the native SCM syntax of GTA 3 series, nothing other than III, VC, SA, LCS and VCS.
It may contain non-standard SCM definitions as R* hasn't published enough documentation about it yet.

On the occasion of the GTAIII's Tenth Anniversary, after a long period of darkness where we fell about the real SCM syntax, R* finally treated us by attaching part of its own original source code into the GTAIII Anniversary game, available for iOS and Android devices. As far back as 2001, a snip of some debugging scripts has been already provided with main.sc and debug.sc files. However, many secrets are unrevealed yet, thus some things cannot be documented fully and so they can be only guessed. The SCM format abbreviation is one of countless proofs of this inconvenience, which may stand for Script Multifile. Other doubts come with source files, whose SC extension appears to be very close to Mission SCript. Although we have enough information to suppose the currently unknown mysteries of the used language, we still have no safe clue about which was its original denomination. Furthermore, it is a matter of fact that R* developers have been left untouched the miss2 executable name of the GTA 3 series compiler since the chapter 2. In this connection, we could imagine the new language is a variant or an evolution of the GBHscript. In the ancient documentation by DMA (at present Rockstar North), GBH sound like the initials of the codename or the primordial name of GTA2, as the former is expressly stricken and overwritten by the latter (GBHGTA2script occurs twice, whereas GTA2script alone is quite recurrent). Therefore, we are almost sure to say the language name is most likely GTA3script. However, it is definitely based on BASIC.


Preliminary remarks


This article makes use of formatted codes to improve the reading comprehension. Note that:

  • Square brackets mean everything inside may be omitted.
  • Curly brackets denote the presence of useful codes but not necessarily needed.
  • Vertical bars divide what can be chosen alternatively.

Fundamentals

Comments

A comment is an additional text that may be helpful for the code writer or other users, in short for the reader. It is the only part of the source code which gets always ignored when compiling.

Inline

An inline comment, denoted by // (two slashes), makes everything that follows some plain text:

[...] // Some inline comment

Multiline

A multiline comment embraces a particular area of the source code, starting by the opening tag /* (slash and asterisk) and ending with */ (asterisk and slash):

[...]
/*
 * Some multiline comment
 * ...
 */
[...]

Currently, more than one multiline comment is allowed per line:

[...] /* Some inline comment */ [...] /* Some inline comment */ [...]

Highlighters

Highlighters behaviour sounds trivial, that's to say they simply highlight one or more arguments per command within round brackets, individually or together. However, a comma can be used aswell to distinguish each argument. In GTA III, they appear to be used only for SETUP_ZONE_PED_INFO (in a various order) and GXT keys:

SETUP_ZONE_PED_INFO FISHFAC DAY (0) 0 0 0 (0 0 0 0) 0
PRINT_BIG (T4X4_1) 5000 2

Scopes

Scopes are delimited by curly brackets (or multiline brackets) which act like a local variable scopes. Essentially, they enclose the code where local variables are used, including timers. They can be opened and closed many times in a script:

{
    [...]
}
Limit
Scopes cannot be nested.

Labels

A label is a sequence of characters which identifiy a location of the source code useful for jumps. It can be accessed by any part of the source code. To define a label just append : (colon) to its name:

[...]

{lblname}:
[...]

At the compiling time, they are automatically converted into an offset.

Variables

A variable is a storage location assigned to a symbolic name which contains a value of any type.

Value

A value represents any data of any type it is.

Scope


The usage of a variable depends on the scope, that is the context where a specific variable is declared. At this point, we can distinguish the global and local scope.

Global

The global scope grasps the whole source code. Variables defined as globals are visible in any script. They are declared by appending the VAR prefix.

Local


The local scope wraps a localized part of the source code. Variables defined as locals are visible only in the code enclosed by curly brackets. You can put them everywhere and as many times as you want in the source code. They are declared by appending the LVAR prefix.

Timers

A timer is a special local variable whose value rises automatically. It starts incrementing since the beginning of the script where it has been placed and grows endlessly. There are 2 usable timers which are already defined as TIMERA and TIMERB, therefore they do not need to be declared.

Data types


Among the available types, some are equivalent to the most known programming types. Their length is up to 4, 8 and 16 bytes. Each type is appended as a suffix in the variable declaration.

INT

The INT type handles 32-bit signed integers. It is also used to store values with less bytes, such as a bool, a char and a short int.

FLOAT

The FLOAT type handles 32-bit floating-points. As it normally does, decimal precision of a float is usually stuck to 6 digits beyond which it may get lost.

TEXT_LABEL

The TEXT_LABEL type handles 8-byte strings. Generally, a string is an array of 1-byte characters. It requires 7 characters plus the null-terminator (a blank byte meaning the end of the string). It is used to hold GXT keys (those of town zones, interiors, help textes or dialogue subtitles) script names or any short string.

Limit
TEXT_LABEL variables are supported in San Andreas and Vice City Stories.

STRING

The STRING type handles 16-byte strings. Like the previous, this type holds 15 characters plus the null-terminator. It is used to store model and texture names of player clothes, animation names or any long string. It may be a ghost type as later R* compilers might choose between the two string types automatically according to the length of the string itself.

Limit
STRING variables and values are supported in San Andreas.

REFERENCE

The REFERENCE type handles 16-byte strings. They can refer to either a label name or a file name. While inside

Limit
REFERENCE variables aren't available.

NUMBER (pseudo)

The NUMBER type handles 32-bit signed integers or 32-bit floating-points. It is used only to assign and compare numbers to INT or FLOAT variables. It is a pseudo type of INT or FLOAT.

CONSTANT (pseudo)

The CONSTANT type handles 32-bit signed integers. It is used only to assign and compare constants to INT variables regarding model identifiers, task statuses, ped events and such. It is a pseudo type of INT.

Limit
The assignment and comparison of CONSTANT values are supported since Vice City.

BUFFER

The BUFFER type handles 32-byte strings or larger, depending on how many continuous parameters of the same type there are, each of which occupies 32 bytes. It can hold up to 127 characters plus the null-terminator, after which another BUFFER argument may begin. Strings of such type must be put within quotation marks:

SAVE_STRING_TO_DEBUG_FILE "32B-128B TEXT"
Limits
BUFFER values are supported since San Andreas.
BUFFER variables aren't available.

VARLEN

The VARLEN type handles N-byte strings. It holds N characters plus the null-terminator. Strings of this type mustn't exceed 255 characters.

Limits
VARLEN values are supported since San Andreas.
VARLEN variables aren't available.

Declaration


Defining a variable means assigning a token string to a memory cell at the compiling time. Variables must be declared in the following manner:

{varscope}_{vartype} {varname0}[,] [... {varnameN}]

As mentioned in the sections above, local variables have to be put within curly brackets:

{
    {varscope}_{vartype} {varname0}[,] [... {varnameN}]

    [...]
}

Inline variable declaration is allowed, you just have to separate them by spaces or tabulations. Adding a preceding comma before these characters is optional.

Limit
Whereas the variable buffer is limited, you can declare a certain amount of globals and locals. INT and FLOAT) types take 1 variable, while TEXT_LABEL and STRING types occupy respectively 2 and 4 variables to store their data (have a look here for further details).

Arrays

A array is a collection of variables having the same type which can be accessed by an index, a 1-based integer lesser than or equal to the size specified, enclosed by square brackets:

{
    {varscope}_{vartype} {varname0}{[arrsize0]}[,] [... {varnameN}{[arrsizeN]}]

    [...]
}
Limits
The usage of arrays is allowed since Vice City.
Variable indices are quite buggy in Vice City and therefore unrecommended, but they are fully supported since San Andreas.

Handles

A handle is an univocal identifier assigned to a game entity. It is given by the following statement:

short nHandle = (iEntityIndexInPool << 8) | ucEntityFlag;

R* compiler won't let you assign different entity types to the same variable or using a variable which hasn't been assigned to any CREATE_* or ADD_* entity commands.

Operators

In general, an operator is a token string that represents a math calculation or an operation of any other kind, in order to make the code understanding clearer at a glance.

Arithmetic

Arithmetic operators compute some of the most common algebric calculations between either a variable and a value or two variables. As well as in some programming language happens, CONSTANT, TEXT_LABEL and STRING types are free from these operators, except for the basic assigment (see also Operators composition):

Operator Name Syntax Description
= Assignment expr0 = expr1 Store expr1 to expr0
+ Addition expr0 + expr1 Add expr1 to expr0
- Subtraction expr0 - expr1 Subtract expr1 from expr0
* Multiplication expr0 * expr1 Multiply expr0 by expr1
/ Division expr0 / expr1 Divide expr0 by expr1
+@ Timed addition expr0 +@ expr1 Multiply expr2 by delta time and add the result to expr1
-@ Timed subtraction expr0 -@ expr1 Multiply expr2 by delta time and subtract the result from expr1
++ Increment Pre[*] ++ expr0 Increment expr0 by 1 and store the result to expr0
Post expr0 ++
-- Decrement Pre[*] -- expr0 Decrement expr0 by 1 and store the result to expr0
Post expr0 --
Note
^ Pre and post increments have no difference unlike what you would expect.

Yet, you can put the assignment and algebric operators together inline as follows:

Operators Name Syntax Description
= + Addition and assignment expr0 = expr1[*] + expr2 Add expr2 to expr1 and store the result to expr0
= - Subtraction and assignment expr0 = expr1[*] - expr2 Subtract expr2 from expr1 and store the result to expr0
= * Multiplication and assignment expr0 = expr1[*] * expr2 Multiply expr1 by expr2 and store the result to expr0
= / Division and assignment expr0 = expr1[*] / expr2 Divide expr1 by expr2 and store the result to expr0
= +@ Timed addition and assignment expr0 = expr1[*] +@ expr2 Multiply expr2 by delta time, add the result to expr1 and store everything to expr0
= -@ Timed subtraction and assignment expr0 = expr1[*] -@ expr2 Multiply expr2 by delta time, subtract the result from expr1 and store everything to expr0
Note
^ expr1 can represent expr0 too.
Limit
Multiple algebric operators per line are not allowed.

Compound assignment

Compound assignment operators store values or variable content to other variables having a particular type afterwards the computation of an arithmetic operation, to squeeze the code and clear it up from granted repetitions:

Operator Name Syntax Description
+= Addition assignment expr0 += expr1 Add expr1 to expr0 and store the result to expr0
-= Subtraction assignment expr0 -= expr1 Subtract expr1 from expr0 and store the result to expr0
*= Multiplication assignment expr0 *= expr1 Multiply expr0 by expr1 and store the result to expr0
/= Division assignment expr0 /= expr1 Divide expr0 by expr1 and store the result to expr0
+=@ Timed addition assignment expr0 +=@ expr1 Multiply expr1 by delta time, add the result to expr0 and store everything to expr0
-=@ Timed subtraction assignment expr0 -=@ expr1 Multiply expr1 by delta time, subtract the result from expr0 and store everything to expr0

Uncompounded assignment

Uncompounded assignment operators are those on their own, or rather they are neither derivable nor decomposable similarly as those compounds:

Operator Name Syntax Description
=# Cast assignment expr0 =# expr1 Cast expr1 to any other type and store the result to expr0
Limit
Supported conversions are FLOAT to INT and INT to FLOAT.

Logical

Logical operators influence the way conditions are evalueted and enable to test more of them at a time. More than anything, they are built-in statements:

Operator Name Syntax Description
NOT Logical negation IF NOT condition0 Test if condition0 is false
AND Logical conjunction IF condition0
AND condition8
Test if both condition0 and conditionN are true
OR Logical disjunction IF condition0
OR condition8
Test if either condition0 or conditionN is true

Comparison

Comparison operators test the truth or falsity of the relation between either a variable and a value, a value and a variable or two variables:

Operator Name Syntax Description
= Equal to IF expr0 = expr1 Test if expr0 and expr1 are equal
> Greater than IF expr0 > expr1 Test if expr0 is greater than expr1
< Lesser than IF expr0 < expr1 Test if expr0 is lesser than expr1
>= Greater than or equal to IF expr0 >= expr1 Test if expr0 is greater than or equal to expr1
<= Lesser than or equal to IF expr0 <= expr1 Test if expr0 is lesser than or equal to expr1

Theorically, STRING values should be compared with the first operator only, but the presence of COMPARE_STRING increase the doubts concerning the existence of such operator for this type.

Commands

A command is a symbolic name associated to an opcode which executes a portion of code that specifies the operation to be performed by passing zero or more arguments. Opcodes do not return values that can be assigned to a variable, even though the boolean flag is kept whenever they are used as conditions. It follows the common programming syntax adopted for procedure or function calls:

{commandname} [{anyvalue0|varname0} ... {anyvalueN|varnameN}]

Alternatives

Alternative commands are those which are basically overloaded on the bases of different argument types:

  • GTA III Vice City San Andreas Liberty City Stories Vice City Stories:
    • SET (=)
    • CSET (=#)
    • ADD_THING_TO_THING (+=)
    • SUB_THING_FROM_THING (-=)
    • MULT_THING_BY_THING (*=)
    • DIV_THING_BY_THING (/=)
    • IS_THING_EQUAL_TO_THING (=)
    • IS_THING_NOT_EQUAL_TO_THING (NOT =)
    • IS_THING_GREATER_THAN_THING (>)
    • IS_THING_GREATER_OR_EQUAL_TO_THING (>=)
    • ADD_THING_TO_THING_TIMED (+=@)
    • SUB_THING_FROM_THING_TIMED (-=@)
    • ABS
  • San Andreas:
    • IS_TEXT_LABEL_NULL
    • IS_STRING_NULL
    • IS_BIT_SET
    • SET_BIT
    • CLEAR_BIT
    • COMPARE_STRING

This section is incomplete. You can help by fixing and expanding it.

Internals

Internal commands are those which have special characteristics and are handled internally by the compiler:

  • GTA III Vice City San Andreas Liberty City Stories Vice City Stories:
    • GOTO
    • GOTO_IF_FALSE
    • MISSION_END
    • START_NEW_SCRIPT
    • ANDOR
    • LAUNCH_MISSION
    • PLAYER_MADE_PROGRESS
    • SET_PROGRESS_TOTAL[*]
    • REGISTER_MISSION_GIVEN
    • REGISTER_MISSION_PASSED
    • SCRIPT_NAME
    • LOAD_AND_LAUNCH_MISSION
    • LOAD_AND_LAUNCH_MISSION_INTERNAL
    • SET_TOTAL_NUMBER_OF_MISSIONS[*]
  • Vice City San Andreas Liberty City Stories Vice City Stories:
    • REGISTER_ODDJOB_MISSION_PASSED
  • GTA III Liberty City Stories Vice City Stories:
    • GOTO_IF_TRUE
    • GOSUB_FILE
  • GTA III Vice City:
    • CREATE_COLLECTABLE1
    • SET_COLLECTABLE1_TOTAL[*]
  • Vice City San Andreas:
    • LOAD_AND_LAUNCH_MISSION_EXCLUSIVE
  • Liberty City Stories Vice City Stories:
    • CALL
    • CALLB
  • San Andreas
    • INIT_TABLE_OF_GOTO
    • APPEND_GOTO_TO_TABLE
  • Vice City Stories:
    • SET_COLLECTABLE2_TOTAL
Note
^ The argument of these commands must be set respectively according to:
  • The sum of PLAYER_MADE_PROGRESS values;
  • The amount of REGISTER_MISSION_PASSED (those that doesn't have an immediate value are excluded) and REGISTER_ODDJOB_MISSION_PASSED;
  • The amount of CREATE_COLLECTABLE1.

If the argument of the listed commands differs from that expected, a 0-value must be passed.

This section is incomplete. You can help by fixing and expanding it.

WAIT

WAIT skips the execution of a script according to some milliseconds after which it will resume again. Indeed, it is absolutely necessary into infinite loops or those that break after more than one frame, such as the WHILE statement. In this case, a INT equal to 0 is passed.

GOTO

GOTO jumps to the label of any location of the source code. It is also used internally to build other statements or singularly but then it mustn't point off the current context:

// File: any.sc

jump0:
GOTO jumpN
// File: any.sc

jumpN:
GOTO jump0

ANDOR

ANDOR set out the way the comparison among more conditions have to occur (see also Comparing rule).

GOTO_IF_TRUE

GOTO_IF_TRUE operates in conjunction with ANDOR and jumps to a label if the returned boolean flag is true.

GOTO_IF_FALSE

Unlike GOTO_IF_TRUE, GOTO_IF_FALSE jumps to the disired label only if the comparison returns false.

SCRIPT_NAME

SCRIPT_NAME simply associates an unique name to the parent script.

Note
R* compiler doesn't enable you to associate a name previously used.

SAVE_STRING_TO_DEBUG_FILE

SAVE_STRING_TO_DEBUG_FILE accepts an argument which can admit up to 127 characters plus the null-terminator. In the compiling process, the argument is skipped but its string is copied to a predefined 128-bytes buffer, compiled afterwards. In San Andreas, these are the predetermined bytes that seem to do nothing:

00 00 41 00 09 2E 00 00 00 00 00 00 00 00 00 00 
09 2E 00 00 00 00 00 00 1C FB 12 00 D8 A8 41 00
00 00 41 00 09 2E 00 00 00 00 00 00 01 00 00 00 
09 2E 00 00 00 00 00 00 1C FB 12 00 D8 A8 41 00
00 00 41 00 09 2E 00 00 00 00 00 00 02 00 00 00 
09 2E 00 00 00 00 00 00 1C FB 12 00 D8 A8 41 00
00 00 41 00 09 2E 00 00 00 00 00 00 03 00 00 00 
09 2E 00 00 00 00 00 00 1C FB 12 00 D8 A8 41 00

Dynamic values within a block are marked in grey, while those which differs among each block are coloured in blue. Possibly rubbish data.

Constants

A constant is a symbolic name associated to a specific value. When compiling, their caption is converted in the assigned value. In GTA III and Vice City, they are hardcoded as everything inside the compiler. Since Vice City, names and identifiers of objects within OBJS and TOBJ blocks are loaded from every IDE file defined into gta_vc.dat, then those of vehicles and pedestrians within PEDS and CARS blocks are retrieved from default.ide. In San Andreas, they are listed into TXT files, whose name follows the Pascal Case (eg. AudioEvents.txt). These files respect the syntax below:

{constname0} {constvalue0}

{constnameN} {constvalueN}

Constant names and values are divided by as many spaces or tabulations as you want. Constant lines are distinguished by two \n (new line) characters. The model names which aren't assigned to a constant are still valid (see also Identifiers). Keep in mind arguments of some commands having the CONSTANT type accept only constant values of a single namespace.

Formatting

Everything is case-insensitive, that means the uppercase and lowercase letters are computed as the same character. Usually, the source code is formatted as shown in this table:

Uppercase Lowercase
Label X
Declaration X
Variable X
Command X
Constant X
Statement X

Compiling

Structure

The source code is split up into several SC files which comprehend main file, foreign gosubs, subscripts, mission scripts, and trigger scripts. These files can be included more times because they are actually processed once.

Main file


The main file is the most significant part of the whole source. It can include many script files and/or embedded gosubs, scripts or functions. Originally, it is characterized by the absence of the local scope. It must be put outside the directory, having the same name as the main script file, where all other foreign scripts must be. R* compiler will search into subfolders too:

<directory>
| main
|  | gosub
|  |  |- gosub1.sc
|  |  \- gosubN.sc
|  | subscript
|  |  |- subscript1.sc
|  |  \- subscriptN.sc
|  | mission_guy
|  |  |- mission_guy1.sc
|  |  \- mission_guyN.sc
|  |- gosub.sc
|  |- subscript.sc
|  \- mission.sc
\- main.sc

Foreign gosubs


Foreign gosubs (also called subroutines) are main extension files. They are called using the GOSUB_FILE command which jumps to a specific label and executes some code that returns back to the place where it has been called with RETURN. You are able to specify the gosub label to start jumping at aswell:

// File: main.sc

GOSUB_FILE gosub0 gosub.sc
// File: gosub.sc

gosub0:
{
    [...]
}
RETURN
Limit
Foreign gosubs were introduced since GTA III. They were unused in Vice City and got removed in San Andreas, but then they were reimplemented in Liberty City Stories and Vice City Stories.

Gosubs

As mentioned, gosubs are also embedded in any script file. They follow almost the same rules, except they are called by GOSUB and can actually inehrit the local scope of the parent script:

// File: any.sc

GOSUB gosub0
// File: any.sc

gosub0:
{
    [...]
}
RETURN
Note
R* compiler doesn't take care if the code within a scope jumps to a gosub inside which another scope is declared. It is strongly recommended to pay attention at this issue or you will fall down into an irreparable local variable mismatch.

Subscripts


Subscripts are code blocks which take part of a queue of other scripts and are allocated over the memory by LAUNCH_MISSION. They are denoted by the presence of MISSION_START at the very top of the mission file. As long as they aren't ended with MISSION_END, their execution never expires till the end of the game process. Each one works independently, even though they are able to share global variables:

// File: main.sc

LAUNCH_MISSION subscript.sc
// File: subscript.sc

MISSION_START

[VAR_{vartype} {varname0}[,] [... {varnameN}]]

SCRIPT_NAME main

subscript_loop:
{
    [LVAR_{vartype} {varname0}[,] [... {varnameN}]]

    [...]
}
//GOTO subscript_loop
MISSION_END
Note
MISSION_START is a special and fake directive that isn't assigned to any command. R* compiler will notify an error if it isn't placed at the first line of a subscript or a mission script.

Scripts

As for gosubs, scripts can be embedded everywhere in a script file. They are started by START_NEW_SCRIPT which has an undefined amount of arguments, whose type must match with those of each local variable of the starting script in order to be passed, else the compilation will interrupt. Unlike subscripts, they get terminated by TERMINATE_THIS_SCRIPT or TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME (elsewhere in another script):

// File: any.sc

START_NEW_SCRIPT script [{anyvalue0|varname0} ... {anyvalueN|varnameN}]
// File: any.sc

script:
{
    SCRIPT_NAME script

script_loop:

    [LVAR_{vartype} {varname0}[,] [... {varnameN}]]

    [...]

    //GOTO script_loop
    TERMINATE_THIS_SCRIPT
}
Notes
Scripts must have a local scope.
Script commands must be inserted within or after the local scope.
Since Vice City, the opening curly bracket must be put before the script label when more arguments are passed.

Functions

This section is incomplete. You can help by fixing and expanding it.

Mission scripts


Mission scripts are those subscripts which are responsible for the presence of a storyline in the game. When they are launched with LOAD_AND_LAUNCH_MISSION, the mission is loaded in the mission block, allocated over the memory and the script pointer is moved to the corresponding mission offset. Do not forget to begin a mission script with MISSION_START and end it with MISSION_END:

// File: main.sc

LOAD_AND_LAUNCH_MISSION mission.sc
// File: mission.sc

MISSION_START

GOSUB mission_start

IF HAS_DEATHARREST_BEEN_EXECUTED
    GOSUB mission_failed
ENDIF

GOSUB mission_cleanup

MISSION_END

[VAR_{vartype} {varname0}[,] [... {varnameN}]]

mission_start:

REGISTER_MISSION_GIVEN
SCRIPT_NAME mission

// Variables initialization

{
    [LVAR_{vartype} {varname0}[,] [... {varnameN}]]

    [...]
}
GOTO mission_passed

mission_failed:
[...]
RETURN

mission_passed:
REGISTER_MISSION_PASSED mission
//PLAYER_MADE_PROGRESS 1
[...]
RETURN

mission_cleanup:
// Mark everything as no longer needed
[...]
RETURN

Few missions doesn't need to be executed twice or more times because they may just initialize some global variables defined in the main script or launch the intro mission. For this reason, here comes the use of LOAD_AND_LAUNCH_MISSION_EXCLUSIVE:

// File: main.sc

LOAD_AND_LAUNCH_MISSION_EXCLUSIVE initial.sc
LOAD_AND_LAUNCH_MISSION_EXCLUSIVE intro.sc
Note
Esclusive missions are never launched in the source code. It's likely, it was an idea not came to the end successfully.
Limits
LOAD_AND_LAUNCH_MISSION_EXCLUSIVE is available only in Vice City and San Andreas.
R* compiler won't let you using more than 2 LOAD_AND_LAUNCH_MISSION_EXCLUSIVE.

Trigger scripts


This section is incomplete. You can help by fixing and expanding it.

Statements

As usual, the evolution of something implies its development over the years. Alongside, the statements implementation has been distributed equally into every chapter. Their definitions are similar to those used in pseudocodes resulting in a raw source code. However, you are still able to build your own control flows:

ANDOR {value}
    [NOT] {condition0}
    [[NOT] {condition8}]
GOTO_IF_FALSE ELSE
    {consequence}
    [GOTO ENDIF]
ELSE:
    [{alternative}
ENDIF:]
Note
It's likely, user-made control flows weren't intended to be usable because R* compiler cannot recognize an equal to rather than an assignment operator.

IF

IF is one of the most widespread conditional statements which executes some codes by evaluating a boolean flag, the returning value of one or more conditions. According to the returning value, either the consequence or the alternative will be performed. The condition result can be inverted by appending the NOT logical operator before. More conditions require the use of the remaining logical operators, they are AND, when verifying if all checks are true, and OR, while testing if one of all checks is true. The syntax below summarize the whole explanation:

IF [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
[ELSE
    {alternative}]
ENDIF
Limit
More than 8 conditions per statement are't allowed.

IFNOT

IFNOT is a variation of the IF statement as already stated. As opposed to its closest relative, the conditions evaluetion is reversed, that is the consequence is perfomed when the boolean flag is false, else the alternative is executed:

IFNOT [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
[ELSE
    {alternative}]
ENDIF
Limit
It is supported in GTA III, Liberty City Stories and Vice City Stories.

WHILE

Alike the IF construct, WHILE is a conditional statement. The only difference consists in how it performs the consequence, that is it loops every line of code built into if the boolean flag is true:

WHILE [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
ENDWHILE

WHILENOT

WHILENOT acts seemingly like the WHILE statement, as all condition truths are inverted and therefore the consequence is performed over and over again until the boolean flag becomes true:

WHILENOT [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
ENDWHILE
Limit
It is supported in GTA III, Liberty City Stories and Vice City Stories.

REPEAT

Similar to the WHILE construct, REPEAT iterates the consequence repeatedly depending on a 0-value incremental variable which rises till the times specified:

REPEAT {times} {varname}
    {consequence}
ENDREPEAT
Limits
It is supported since Vice City.
The times must be positive.
The code will be read at least once in any case.

CASE

Basically, CASE is a group of concatenated IF statements. When a condition is false the next case gets performed, otherwise the consequence is executed and the code jumps to the end of the construct. If none of the cases is true, an ELSE clause may be carried out:

CASE {varname}
    WHEN {value0}
        {consequence}
    [WHEN {valueN}
        {consequence}]
    [ELSE
        {alternative}]
ENDCASE
Limits
It is supported since San Andreas.
The WHEN clause allows the use of integer values only.
In San Andreas, values must be sorted.
Multiple cases per consequence aren't allowed.

Decompiling

Structure

For further information about the SCM file format, read this article. Take into account the compiling order of each SC file is main file - foreign gosubs - subscripts - mission scripts apart from the reading order of the commands used to include them. Trigger scripts are compiled individually into the script.img file. On the other hand, functions are compiled like gosubs.

Identifiers

Undefined constants of model identifiers, whose name refers to a DFF which is presumably archived into any of the IMGs, loaded by the game, are overwritten by a decrementing value in the order they get compiled. These model names are then put into the second segment of the SCM header. Those of mission scripts and trigger scripts respect the same rule except the fact they are turned into a 0-based growing identifier, while exclusive mission scripts are launched by a negative identifier.

Offsets

An offset is a 32-bit signed integer which points to a location of the source code. Those within the main file, foreign gosubs and subscripts are absolute offsets that start from the beginning of the main script, while the ones inside mission scripts and trigger scripts are relative offsets starting from their beginning. The offset is related to global variables aswell, whose interval goes from 8 and ends to FFFC, each one is aligned to the nearest 4 bytes.

Variables

The following table shows the variables range of the local scope for each game version:

Context GTA III Vice City San Andreas Liberty City Stories Vice City Stories
Foreign gosub/Gosub 0-15 0-15 n/a 0-95 0-95
Subscript/Script 0-15 0-15 0-31 0-95 0-95
Mission script 0-15 0-15 0-1023 0-95 0-95
Trigger script n/a n/a 0-31 n/a n/a
Function n/a n/a n/a 0-95 0-95
Timer 16-17 16-17 32-33 10-11? 254-255

Operators composition

As far as you wouldn't know, SCM's operators always take two operands to compute an operation. Their composition is listed below:

Operator/s Name Syntax Composition
++ Increment Pre ++ expr0 expr0 += 1
Post expr0 ++
-- Decrement Pre -- expr0 expr0 -= 1
Post expr0 --
= + Addition and assignment expr0 = expr1 + expr2 expr0 = expr1
expr0 += expr2
= - Subtraction and assignment expr0 = expr1 - expr2 expr0 = expr1
expr0 -= expr2
= * Multiplication and assignment expr0 = expr1 * expr2 expr0 = expr1
expr0 *= expr2
= / Division and assignment expr0 = expr1 / expr2 expr0 = expr1
expr0 /= expr2
= +@ Timed addition and assignment expr0 = expr1 +@ expr2 expr0 = expr1
expr0 +=@ expr2
= -@ Timed subtraction and assignment expr0 = expr1 -@ expr2 expr0 = expr1
expr0 -=@ expr2

Opcodes

An opcode is a 16-bit unsigned integer referring to a portion of code the game executes when it is called by passing an undefined or absent amount of arguments. The maximum number of available opcodes is 0x7FFF, since the last bit (0x8000) is set whenever they are used as negative conditions (those with the NOT logical operator).

Arguments

An argument is some data given as input to an opcode. Normally, opcodes have a defined amount of arguments up to 32. Those not, such as START_NEW_SCRIPT, can pass as many arguments as the available local variables are, except timers. This limitation is game specific.

Specials

Here is the list of all special commands and their relative specifications:

Legend:

List:

Command Opcode Arg.
count
Arguments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... 32
GTA III Vice City San Andreas Liberty City Stories Vice City Stories
MISSION_START[*] 0
GOTO[*] 0002 1 VR
GTA III Vice City San Andreas Liberty City Stories
= SET_VAR_INT 0004 2 GI VI
SET_VAR_FLOAT 0005 GF VF
SET_LVAR_INT 0006 LI VI
SET_LVAR_FLOAT 0007 LF VF
+= ADD_VAL_TO_INT_VAR 0008 2 GI VI
ADD_VAL_TO_FLOAT_VAR 0009 GF VF
ADD_VAL_TO_INT_LVAR 000A LI VI
ADD_VAL_TO_FLOAT_LVAR 000B LF VF
-= SUB_VAL_FROM_INT_VAR 000C 2 GI VI
SUB_VAL_FROM_FLOAT_VAR 000D GF VF
SUB_VAL_FROM_INT_LVAR 000E LI VI
SUB_VAL_FROM_FLOAT_LVAR 000F LF VF
*= MULT_INT_VAR_BY_VAL 0010 2 GI VI
MULT_FLOAT_VAR_BY_VAL 0011 GF VF
MULT_INT_LVAR_BY_VAL 0012 LI VI
MULT_FLOAT_LVAR_BY_VAL 0013 LF VF
/= DIV_INT_ANY_BY_VAL 0014 2 GI VI
DIV_FLOAT_VAR_BY_VAL 0015 GF VF
DIV_INT_LVAR_BY_VAL 0016 LI VI
DIV_FLOAT_LVAR_BY_VAL 0017 LF VF
> IS_INT_VAR_GREATER_THAN_NUMBER 0018 2 GI VN
IS_INT_LVAR_GREATER_THAN_NUMBER 0019 LI VN
IS_NUMBER_GREATER_THAN_INT_VAR 001A VN GI
IS_NUMBER_GREATER_THAN_INT_LVAR 001B VN LI
IS_INT_VAR_GREATER_THAN_INT_VAR 001C GI GI
IS_INT_LVAR_GREATER_THAN_INT_LVAR 001D LI LI
IS_INT_VAR_GREATER_THAN_INT_LVAR 001E GI LI
IS_INT_LVAR_GREATER_THAN_INT_VAR 001F LI GI
IS_FLOAT_VAR_GREATER_THAN_NUMBER 0020 GF VN
IS_FLOAT_LVAR_GREATER_THAN_NUMBER 0021 LF VN
IS_NUMBER_GREATER_THAN_FLOAT_VAR 0022 VN GF
IS_NUMBER_GREATER_THAN_FLOAT_LVAR 0023 VN LF
IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR 0024 GF GF
IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR 0025 LF LF
IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR 0026 GF LF
IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR 0027 LF GF
>= IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER 0028 2 GI VN
IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER 0029 LI VN
IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR 002A VN GI
IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR 002B VN LI
IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR 002C GI GI
IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR 002D LI LI
IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR 002E GI LI
IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR 002F LI GI
IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER 0030 GF VN
IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER 0031 LF VN
IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR 0032 VN GF
IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR 0033 VN LF
IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR 0034 GF GF
IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR 0035 LF LF
IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR 0036 GF LF
IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR 0037 LF GF
= IS_INT_VAR_EQUAL_TO_NUMBER 0038 2 GI VN
IS_INT_LVAR_EQUAL_TO_NUMBER 0039 LI VN
IS_INT_VAR_EQUAL_TO_INT_VAR 003A GI GI
IS_INT_LVAR_EQUAL_TO_INT_LVAR 003B LI LI
IS_INT_VAR_EQUAL_TO_INT_LVAR 003C GI LI
IS_FLOAT_VAR_EQUAL_TO_NUMBER 0042 GF VN
IS_FLOAT_LVAR_EQUAL_TO_NUMBER 0043 LF VN
IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR 0044 GF GF
IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR 0045 LF LF
IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR 0046 GF LF
GOTO_IF_FALSE[*] 004D 1 VR
TERMINATE_THIS_SCRIPT 004E 0
MISSION_END
START_NEW_SCRIPT[*] 004F 1 VR _ _ _
GOSUB 0050 1 VR
RETURN 0051 0
+= ADD_INT_VAR_TO_INT_VAR 0058 2 GI GI
ADD_FLOAT_VAR_TO_FLOAT_VAR 0059 GF GF
ADD_INT_LVAR_TO_INT_LVAR 005A LI LI
ADD_FLOAT_LVAR_TO_FLOAT_LVAR 005B LF LF
ADD_INT_VAR_TO_INT_LVAR 005C LI GI
ADD_FLOAT_VAR_TO_FLOAT_LVAR 005D LF GF
ADD_INT_LVAR_TO_INT_VAR 005E GI LI
ADD_FLOAT_LVAR_TO_FLOAT_VAR 005F GF LF
-= SUB_INT_VAR_FROM_INT_VAR 0060 2 GI GI
SUB_FLOAT_VAR_FROM_FLOAT_VAR 0061 GF GF
SUB_INT_LVAR_FROM_INT_LVAR 0062 LI LI
SUB_FLOAT_LVAR_FROM_FLOAT_LVAR 0063 LF LF
SUB_INT_VAR_FROM_INT_LVAR 0064 LI GI
SUB_FLOAT_VAR_FROM_FLOAT_LVAR 0065 LF GF
SUB_INT_LVAR_FROM_INT_VAR 0066 GI LI
SUB_FLOAT_LVAR_FROM_FLOAT_VAR 0067 GF LF
*= MULT_INT_VAR_BY_INT_VAR 0068 2 GI GI
MULT_FLOAT_VAR_BY_FLOAT_VAR 0069 GF GF
MULT_INT_LVAR_BY_INT_LVAR 006A LI LI
MULT_FLOAT_LVAR_BY_FLOAT_LVAR 006B LF LF
MULT_INT_VAR_BY_INT_LVAR 006C GI LI
MULT_FLOAT_VAR_BY_FLOAT_LVAR 006D GF LF
MULT_INT_LVAR_BY_INT_VAR 006E LI GI
MULT_FLOAT_LVAR_BY_FLOAT_VAR 006F LF GF
/= DIV_INT_VAR_BY_INT_VAR 0070 2 GI GI
DIV_FLOAT_VAR_BY_FLOAT_VAR 0071 GF GF
DIV_INT_LVAR_BY_INT_LVAR 0072 LI LI
DIV_FLOAT_LVAR_BY_FLOAT_LVAR 0073 LF LF
DIV_INT_VAR_BY_INT_LVAR 0074 GI LI
DIV_FLOAT_VAR_BY_FLOAT_LVAR 0075 GF LF
DIV_INT_LVAR_BY_INT_VAR 0076 LI GI
DIV_FLOAT_LVAR_BY_FLOAT_VAR 0077 LF GF
+=@ ADD_TIMED_VAL_TO_FLOAT_VAR 0078 2 GF VF
ADD_TIMED_VAL_TO_FLOAT_LVAR 0079 LF VF
ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR 007A GF GF
ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR 007B LF LF
ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR 007C LF GF
ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR 007D GF LF
-=@ SUB_TIMED_VAL_FROM_FLOAT_VAR 007E 2 GF VF
SUB_TIMED_VAL_FROM_FLOAT_LVAR 007F LF VF
SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR 0080 GF GF
SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR 0081 LF LF
SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR 0082 LF GF
SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR 0083 GF LF
= SET_VAR_INT_TO_VAR_INT 0084 2 GI GI
SET_LVAR_INT_TO_LVAR_INT 0085 LI LI
SET_VAR_FLOAT_TO_VAR_FLOAT 0086 GF GF
SET_LVAR_FLOAT_TO_LVAR_FLOAT 0087 LF LF
SET_VAR_FLOAT_TO_LVAR_FLOAT 0088 GF LF
SET_LVAR_FLOAT_TO_VAR_FLOAT 0089 LF GF
SET_VAR_INT_TO_LVAR_INT 008A GI LI
SET_LVAR_INT_TO_VAR_INT 008B LI GI
=# CSET_VAR_INT_TO_VAR_FLOAT 008C 2 GI GF
CSET_VAR_FLOAT_TO_VAR_INT 008D GF GI
CSET_LVAR_INT_TO_VAR_FLOAT 008E LI GF
CSET_LVAR_FLOAT_TO_VAR_INT 008F LF GI
CSET_VAR_INT_TO_LVAR_FLOAT 0090 GI LF
CSET_VAR_FLOAT_TO_LVAR_INT 0091 GF LI
CSET_LVAR_INT_TO_LVAR_FLOAT 0092 LI LF
CSET_LVAR_FLOAT_TO_LVAR_INT 0093 LF LI
ABS ABS_VAR_INT 0094 1 GI
ABS_LVAR_INT 0095 LI
ABS_VAR_FLOAT 0096 GF
ABS_LVAR_FLOAT 0097 LF
ANDOR[*] 00D6 1 VI
LAUNCH_MISSION 00D7 1 VR
GTA III Vice City San Andreas
PLAYER_MADE_PROGRESS 030C 1 VI
SET_PROGRESS_TOTAL 030D 1 VI
REGISTER_MISSION_GIVEN 0317 0
REGISTER_MISSION_PASSED 0318 1 VT
SCRIPT_NAME 03A4 1 VT
LOAD_AND_LAUNCH_MISSION 0416 1 VR
LOAD_AND_LAUNCH_MISSION_INTERNAL 0417 1 VI
SET_TOTAL_NUMBER_OF_MISSIONS 042C 1 VI
REGISTER_ODDJOB_MISSION_PASSED 0595 0
GTA III Vice City
CREATE_COLLECTABLE1 02EC 1 VI
SET_COLLECTABLE1_TOTAL 02ED 1 VI
GTA III Liberty City Stories
GOTO_IF_TRUE[*] 004C 1 VR
Vice City San Andreas
= IS_INT_VAR_EQUAL_TO_CONSTANT 04A3 2 GC VC
IS_INT_LVAR_EQUAL_TO_CONSTANT 04A4 LC VC
SET_VAR_INT_TO_CONSTANT 04AE GC VC
SET_LVAR_INT_TO_CONSTANT 04AF LC VC
> IS_INT_VAR_GREATER_THAN_CONSTANT 04B0 2 GC VC
IS_INT_LVAR_GREATER_THAN_CONSTANT 04B1 LC VC
IS_CONSTANT_GREATER_THAN_INT_VAR 04B2 VC GC
IS_CONSTANT_GREATER_THAN_INT_LVAR 04B3 VC LC
>= IS_INT_VAR_GREATER_OR_EQUAL_TO_CONSTANT 04B4 2 GC VC
IS_INT_LVAR_GREATER_OR_EQUAL_TO_CONSTANT 04B5 LC VC
IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_VAR 04B6 VC GV
IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_LVAR 04B7 VC LC
LOAD_AND_LAUNCH_MISSION_EXCLUSIVE 0515 1 VR
GTA III
GOSUB_FILE 02CD 2 VR VR
San Andreas
= SET_ANY_TEXT_LABEL_TO_VAR_TEXT_LABEL[*] 05A9 2 GT VT
GT GT
GT LT
SET_ANY_TEXT_LABEL_TO_LVAR_TEXT_LABEL[*] 05AA LT VT
LT GT
LT LT
IS_TEXT_LABEL_VAR_EQUAL_TO_TEXT_LABEL_ANY[*] 05AD GT VT
GT GT
GT LT
IS_TEXT_LABEL_LVAR_EQUAL_TO_TEXT_LABEL_ANY[*] 05AE LT VT
LT GT
LT LT
SET_ANY_STRING_TO_VAR_STRING[*] 06D1 GS VS
GS GS
GS LS
SET_ANY_STRING_TO_LVAR_STRING[*] 06D2 LS VS
LS GS
LS LS
IS_INT_LVAR_EQUAL_TO_INT_VAR 07D6 LI GI
IS_FLOAT_LVAR_EQUAL_TO_FLOAT_VAR 07D7 LF GF
IS_TEXT_LABEL_NULL[*] IS_TEXT_LABEL_NULL_VAR_TEXT_LABEL[*] 0844 1 GT
IS_TEXT_LABEL_NULL_LVAR_TEXT_LABEL[*] 0845 LT
IS_STRING_NULL[*] IS_STRING_NULL_VAR_STRING[*] 0846 1 GS
IS_STRING_NULL_LVAR_STRING[*] 0847 LS
INIT_TABLE_OF_GOTO[*][*] 0871 18 GI VI VI VR VI VR VI VR VI VR VI VR VI VR VI VR VI VR
LI VI VI VR VI VR VI VR VI VR VI VR VI VR VI VR VI VR
APPEND_GOTO_TO_TABLE[*][*] 0872 18 VI VR VI VR VI VR VI VR VI VR VI VR VI VR VI VR VI VR
IS_BIT_SET IS_BIT_SET_VAR_INT_NUMBER[*] 08B4 2 GI VN
IS_BIT_SET_VAR_INT_VAR_INT[*] 08B5 GI GI
IS_BIT_SET_VAR_INT_LVAR_INT[*] 08B6 GI LI
IS_BIT_SET_LVAR_INT_NUMBER[*] 08B7 LI VN
IS_BIT_SET_LVAR_INT_VAR_INT[*] 08B8 LI GI
IS_BIT_SET_LVAR_INT_LVAR_INT[*] 08B9 LI LI
SET_BIT_SET SET_BIT_VAR_INT_NUMBER[*] 08BA 2 GI VN
SET_BIT_VAR_INT_VAR_INT[*] 08BB GI GI
SET_BIT_VAR_INT_LVAR_INT[*] 08BC GI LI
SET_BIT_LVAR_INT_NUMBER[*] 08BD LI VN
SET_BIT_LVAR_INT_VAR_INT[*] 08BE LI GI
SET_BIT_LVAR_INT_LVAR_INT[*] 08BF LI LI
CLEAR_BIT_SET CLEAR_BIT_VAR_INT_NUMBER[*] 08C0 2 GI VN
CLEAR_BIT_VAR_INT_VAR_INT[*] 08C1 GI GI
CLEAR_BIT_VAR_INT_LVAR_INT[*] 08C2 GI LI
CLEAR_BIT_LVAR_INT_NUMBER[*] 08C3 LI VN
CLEAR_BIT_LVAR_INT_VAR_INT[*] 08C4 LI GI
CLEAR_BIT_LVAR_INT_LVAR_INT[*] 08C5 LI LI
=
COMPARE_STRING[*]
IS_STRING_VAR_EQUAL_TO_STRING_ANY[*]
COMPARE_STRING_VAR_STRING_ANY_STRING[*]
08F9 2 GS VS
GS GS
GS LS
IS_STRING_LVAR_EQUAL_TO_STRING_ANY[*]
COMPARE_STRING_LVAR_STRING_ANY_STRING[*]
08FA LS VS
LS GS
LS LS
Liberty City Stories
RETURN_TRUE 00C5 0
RETURN_FALSE 00C6 0
ANDOR[*] 00DB 1 VI
LAUNCH_MISSION 00DC 1 VR
GOSUB_FILE 02D2 2 VR VR
PLAYER_MADE_PROGRESS 0311 1 VI
SET_PROGRESS_TOTAL 0312 1 VI
REGISTER_MISSION_GIVEN 031C 0
REGISTER_MISSION_PASSED 031D 1 VT
SCRIPT_NAME 03A9 1 VT
LOAD_AND_LAUNCH_MISSION 041B 1 VR
LOAD_AND_LAUNCH_MISSION_INTERNAL 041C 1 VI
SET_TOTAL_NUMBER_OF_MISSIONS 0431 1 VI
REGISTER_ODDJOB_MISSION_PASSED 059A 0
= IS_INT_VAR_EQUAL_TO_CONSTANT 04A8 2 GC VC
IS_INT_LVAR_EQUAL_TO_CONSTANT 04A9 LC VC
SET_VAR_INT_TO_CONSTANT 04B3 GC VC
SET_LVAR_INT_TO_CONSTANT 04B4 LC VC
> IS_INT_VAR_GREATER_THAN_CONSTANT 04B5 2 GC VC
IS_INT_LVAR_GREATER_THAN_CONSTANT 04B6 LC VC
IS_CONSTANT_GREATER_THAN_INT_VAR 04B7 VC GC
IS_CONSTANT_GREATER_THAN_INT_LVAR 04B8 VC LC
>= IS_INT_VAR_GREATER_OR_EQUAL_TO_CONSTANT 04B9 2 GC VC
IS_INT_LVAR_GREATER_OR_EQUAL_TO_CONSTANT 04BA LC VC
IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_VAR 04BB VC GV
IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_LVAR 04BC VC LC
CALL[*][*][*] 05AE 1 VR _ _ _
CALLB[*][*][*] 05AF 1 VR _ _ _
Vice City Stories
= SET_ANY_INT 0004 2 GI VI
LI VI
SET_ANY_FLOAT 0005 GF VF
LF VF
SET_ANY_TEXT_LABEL 0006 GT VT
LT VT
+= ADD_VAL_TO_INT_ANY 0007 2 GI VI
LI VI
ADD_VAL_TO_FLOAT_ANY 0008 GF VF
LF VF
-= SUB_VAL_FROM_INT_ANY 0009 2 GI VI
LI VI
SUB_VAL_FROM_FLOAT_ANY 000A GF VF
LF VF
*= MULT_INT_ANY_BY_VAL 000B 2 GI VI
LI VI
MULT_FLOAT_ANY_BY_VAL 000C GF VF
LF VF
/= DIV_INT_ANY_BY_VAL 000D 2 GI VI
LI VI
DIV_FLOAT_ANY_BY_VAL 000E GF VF
LF VF
> IS_INT_ANY_GREATER_THAN_NUMBER 000F 2 GI VN
LI VN
IS_NUMBER_GREATER_THAN_INT_ANY 0010 VN GI
VN LI
IS_INT_ANY_GREATER_THAN_INT_ANY 0011 GI GI
GI LI
LI GI
LI LI
IS_FLOAT_ANY_GREATER_THAN_NUMBER 0012 GF VN
LF VN
IS_NUMBER_GREATER_THAN_FLOAT_ANY 0013 VN GF
VN LF
IS_FLOAT_ANY_GREATER_THAN_FLOAT_ANY 0014 GF GF
GF LF
LF GF
LF LF
>= IS_INT_ANY_GREATER_OR_EQUAL_TO_NUMBER 0015 2 GI VN
LI VN
IS_NUMBER_GREATER_OR_EQUAL_TO_INT_ANY 0016 VN GI
VN LI
IS_INT_ANY_GREATER_OR_EQUAL_TO_INT_ANY 0017 GI GI
GI LI
LI GI
LI LI
IS_FLOAT_ANY_GREATER_OR_EQUAL_TO_NUMBER 0018 GF VN
LF VN
IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_ANY 0019 VN GF
VN LF
IS_FLOAT_ANY_GREATER_OR_EQUAL_TO_FLOAT_ANY 001A GF GF
GF LF
LF GF
LF LF
= IS_INT_ANY_EQUAL_TO_NUMBER 001B 2 GI VN
LI VN
IS_INT_ANY_EQUAL_TO_INT_ANY 001C GI GI
GI LI
LI GI
LI LI
IS_FLOAT_ANY_EQUAL_TO_NUMBER 001D GF VN
LF VN
IS_FLOAT_ANY_EQUAL_TO_FLOAT_ANY 001E GF GF
GF LF
LF GF
LF LF
= IS_TEXT_LABEL_ANY_EQUAL_TO_TEXT_LABEL 001F 2 GT VT
LT VT
IS_ANY_TEXT_LABEL_EQUAL_TO_TEXT_LABEL_ANY 0020 GT GT
GT LT
LT GT
LT LT
GOTO_IF_TRUE[*] 0021 1 VR
GOTO_IF_FALSE[*] 0022 1 VR
TERMINATE_THIS_SCRIPT 0023 0
MISSION_END
START_NEW_SCRIPT[*] 0024 1 VR _ _ _
GOSUB 0025 1 VR
RETURN 0026 0
+= ADD_INT_ANY_TO_INT_ANY 0029 2 GI GI
GI LI
LI GI
LI LI
ADD_FLOAT_ANY_FLOAT_ANY 002A GF GF
GF LF
LF GF
LF LF
-= SUB_INT_ANY_FROM_INT_ANY 002B 2 GI GI
GI LI
LI GI
LI LI
SUB_FLOAT_ANY_FROM_FLOAT_ANY 002C GF GF
GF LF
LF GF
LF LF
*= MULT_INT_ANY_BY_INT_ANY 002D 2 GI GI
GI LI
LI GI
LI LI
MULT_FLOAT_ANY_BY_FLOAT_ANY 002E GF GF
GF LF
LF GF
LF LF
/= DIV_INT_ANY_BY_INT_ANY 002F 2 GI GI
GI LI
LI GI
LI LI
DIV_FLOAT_ANY_BY_FLOAT_ANY 0030 GF GF
GF LF
LF GF
LF LF
+=@ ADD_TIMED_VAL_TO_FLOAT_ANY 0031 2 GF VF
LF VF
ADD_TIMED_FLOAT_ANY_TO_FLOAT_ANY 0032 GF GF
GF LF
LF GF
LF LF
-=@ SUB_TIMED_VAL_FROM_FLOAT_ANY 0033 2 GF VF
LF VF
SUB_TIMED_FLOAT_ANY_FROM_FLOAT_ANY 0034 GF GF
GF LF
LF GF
LF LF
= SET_ANY_INT_TO_ANY_INT 0035 2 GI GI
GI LI
LI GI
LI LI
SET_ANY_FLOAT_TO_ANY_FLOAT 0036 GF GF
GF LF
LF GF
LF LF
SET_ANY_TEXT_LABEL_TO_ANY_TEXT_LABEL 0037 GT GT
GT LT
LT GT
LT LT
=# CSET_ANY_INT_TO_ANY_FLOAT 0038 2 GI GF
GI LF
LI GF
LI LF
CSET_ANY_FLOAT_TO_ANY_INT 0039 GF GI
GF LI
LF GI
LF LI
ABS ABS_ANY_INT 003A 1 GI
LI
ABS_ANY_FLOAT 003B GF
LF
RETURN_TRUE 005E 0
RETURN_FALSE 005F 0
ANDOR[*] 0078 1 VI
LAUNCH_MISSION 0079 1 VR
GOSUB_FILE 01BA 2 VR VR
PLAYER_MADE_PROGRESS 01DF 1 VI
SET_PROGRESS_TOTAL 01E0 1 VI
REGISTER_MISSION_GIVEN 01EA 0
REGISTER_MISSION_PASSED 01EB 1 VT
SCRIPT_NAME 0238 1 VT
LOAD_AND_LAUNCH_MISSION 0288 1 VR
LOAD_AND_LAUNCH_MISSION_INTERNAL 0289 1 VI
SET_TOTAL_NUMBER_OF_MISSIONS 0296 1 VI
= IS_INT_ANY_EQUAL_TO_CONSTANT 02DB 2 GC VC
LC VC
SET_ANY_INT_TO_CONSTANT 02E2 GC VC
LC VC
> IS_INT_ANY_GREATER_THAN_CONSTANT 02E3 2 GC VC
LC VC
IS_CONSTANT_GREATER_THAN_INT_ANY 02E4 VC GC
VC LC
>= IS_INT_ANY_GREATER_OR_EQUAL_TO_CONSTANT 02E5 2 GC VC
LC VC
IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_ANY 02E6 VC GV
VC LC
REGISTER_ODDJOB_MISSION_PASSED 036A 0
CALL[*][*][*] 037A 1 VR _ _ _
CALLB[*][*][*] 037B 1 VR _ _ _
SET_COLLECTABLE2_TOTAL 04DA 1 VI
Notes
^ A special mission directive which is never compiled.
^ It is used also to build statements internally.
^ It has an undefined amount of arguments.
^ It is a likely definition of the standard command.
^ Arguments amount varies when compiling.

This section is incomplete. You can help by fixing and expanding it.

Uncommons

Arguments of some commands keep uncommon values which look familiar after encoding:

Command Arg. ID Syntax Encoded
Command Arg. ID Value Type
GTA III Vice City San Andreas Liberty City Stories Vice City Stories
GOTO 1 Any label GOTO 1 Offset INT
GOTO_IF_FALSE 1 Any label GOTO_IF_FALSE 1 Offset INT
GOSUB 1 Gosub label GOSUB 1 Offset INT
GOSUB_FILE 1 Gosub label GOSUB_FILE 1 0-based offset INT
2 Foreign gosub file 2
1 Gosub label 1 Negative offset
2 Foreign gosub label 2
START_NEW_SCRIPT 1 Script label START_NEW_SCRIPT 1 0-based offset INT
LAUNCH_MISSION 1 Subscript file LAUNCH_MISSION 1 0-based offset INT
Subscript label Negative offset
LOAD_AND_LAUNCH_MISSION 1 Mission script file LOAD_AND_LAUNCH_MISSION_INTERNAL 1 Mission identifier INT
Mission script label LOAD_AND_LAUNCH_MISSION Negative offset
GTA III Liberty City Stories Vice City Stories
GOTO_IF_TRUE 1 Any label GOTO_IF_TRUE 1 Offset INT
Vice City San Andreas
LOAD_AND_LAUNCH_MISSION_EXCLUSIVE 1 Mission script file LOAD_AND_LAUNCH_MISSION_INTERNAL 1 Negative mission identifier INT
Mission script label LOAD_AND_LAUNCH_MISSION Negative offset
Liberty City Stories Vice City Stories
CALL
CALLB
1 Function label CALL
CALLB
1 Function arguments INT
2 Returning arguments
3 Parent script locals
4 Offset

This section is incomplete. You can help by fixing and expanding it.

Comparing rule

The comparing rule can handle up to 8 checks per construct. 006D (GTA III, Vice City, San Andreas), 00DB (Liberty City Stories) and 0078 (Vice City Stories) indicate you are verifying a single check (0) or multiple checks with either AND (1 to 8) or OR (21 to 28) logical operators (see also ANDOR).

Analysis

As an overview of the compiled source, statements are literally nested meaning that the code is unoptimized. Furthermore, the jump of an embedded construct doesn't get merged with that of the construct itself, which consists of a benefit for the code parsing.

IF

As regards the IF statement, if the whole check is true the consequence is performed and the code jumps to the end of the construct, otherwise it skips to the alternative (see also Comparing rule):

Decompiled GTA III Vice City San Andreas Liberty City Stories Vice City Stories Compiled
IF [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
[ELSE
    {alternative}]
ENDIF
{006D}
{....}
{....}
{004D}
{....}
{0002}
 ---- 
{....}
 ---- 
{00DB}
{....}
{....}
{004D}
{....}
{0002}
 ---- 
{....}
 ---- 
{0078}
{....}
{....}
{0022}
{....}
{0002}
 ---- 
{....}
 ---- 
ANDOR {value}
    [NOT] {condition0}
    [[NOT] {condition8}]
GOTO_IF_FALSE ELSE
    {consequence}
    [GOTO ENDIF]
ELSE:
    [{alternative}
ENDIF:]

IFNOT

Not that much to say more than the preceding construct, the IFNOT statement is built nearly in the same way. In fact, the ELSE clause points to the alternative, whereas the GOTO jumps to its end. The substantial difference consists in the substitution of GOTO_IF_FALSE with GOTO_IF_TRUE:

Decompiled GTA III Liberty City Stories Vice City Stories Compiled
IFNOT [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
[ELSE
    {alternative}]
ENDIF
{00D6}
{....}
{....}
{004C}
{....}
{0002}
 ---- 
{....}
 ---- 
{00DB}
{....}
{....}
{004C}
{....}
{0002}
 ---- 
{....}
 ---- 
{0078}
{....}
{....}
{0021}
{....}
{0002}
 ---- 
{....}
 ---- 
ANDOR {value}
    [NOT] {condition0}
    [[NOT] {condition8}]
GOTO_IF_TRUE ELSE
    {consequence}
    [GOTO ENDIF]
ELSE:
    [{alternative}
ENDIF:]

WHILE

The WHILE statement is pretty much similar to the previous, even though when the consequence is read the code is moved to the beginning of the construct:

Decompiled GTA III Vice City San Andreas Liberty City Stories Vice City Stories Compiled
WHILE [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
ENDWHILE
 ---- 
{006D}
{....}
{....}
{004D}
{....}
{0002}
 ---- 
 ---- 
{00DB}
{....}
{....}
{004D}
{....}
{0002}
 ---- 
 ---- 
{0078}
{....}
{....}
{0022}
{....}
{0002}
 ---- 
WHILE:
ANDOR {value}
    [NOT] {condition0}
    [[NOT] {condition8}]
GOTO_IF_FALSE ENDWHILE
    {consequence}
    GOTO WHILE
ENDWHILE:

WHILENOT

To say the least, WHILENOT statement follows the same constitution of both WHILE and IFNOT constructs, by exchanging GOTO_IF_FALSE with GOTO_IF_TRUE:

Decompiled GTA III Liberty City Stories Vice City Stories Compiled
WHILENOT [NOT] {condition0}
[AND|OR [NOT] {condition8}]
    {consequence}
ENDWHILE
 ---- 
{00D6}
{....}
{....}
{004C}
{....}
{0002}
 ---- 
 ---- 
{00DB}
{....}
{....}
{004C}
{....}
{0002}
 ---- 
 ---- 
{0078}
{....}
{....}
{0021}
{....}
{0002}
 ---- 
WHILENOT:
ANDOR {value}
    [NOT] {condition0}
    [[NOT] {condition8}]
GOTO_IF_TRUE ENDWHILE
    {consequence}
    GOTO WHILENOT
ENDWHILE:

REPEAT

Seemingly, the REPEAT statement is the first construct ever optimized as a result of a possible R* compiler fault. Moreover, it sounds ambiguous as it loops at least once. This was probably the intention of R* programmers, that is iterating at least once else the construct is useless. However, there are few chance they decide to use such structure to avoid some conflict with some other constructs:

Decompiled G/L Vice City San Andreas Liberty City Stories G/L Vice City Stories Compiled
REPEAT {times} {varname}
    {consequence}
ENDREPEAT
{0004}
 ---- 
{....}
{0008}
{0028}
{004D}
{0005}
 ---- 
{....}
{0009}
{0029}
{004D}
{0004}
 ---- 
{....}
{0007}
{0015}
{0022}
{varname} = {value0}
LOOP:
{consequence}
++ {varname}
    {varname} >= {valueN}
GOTO_IF_FALSE LOOP

CASE

In San Andreas, the CASE statement is more complex and efficient because the game uses internally a binary search algorithm to jump at the label that matches with the value of a particular case. This method requires a known amount of cases which is up to 75. When a case is true, a consequence is executed and the code jumps to the end of the construct, otherwise the alternative may be performed. As the code is unoptimized, the GOTO of the last case is still compiled even though its label points to the end of the jump itself:

Decompiled San Andreas Compiled
CASE {varname}
    WHEN {value0}
        {consequence}
    [WHEN {valueN}
        {consequence}]
    [ELSE
        {alternative}]
ENDCASE
{0871}
{0872}
 ---- 
{....}
{0002}
 ---- 
{....}
{0002}
 ---- 
{....}
{0002}
 ---- 
{varname} {cases} {iselse} ELSE {value0} WHEN0 {valueN} WHENN -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE
[-1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE -1 ENDCASE]
WHEN0:
    {consequence}
    GOTO ENDCASE
[WHENN:
    {consequence}
    GOTO ENDCASE]
[ELSE:
    {alternative}
    GOTO ENDCASE]
ENDCASE:

In Liberty City Stories and Vice City Stories, such statement is a set of nested IF constructs which causes a very slight loss of performance by considering that 00DB (Liberty City Stories) and 0078 (Vice City Stories aren't compiled:

Decompiled G/L Liberty City Stories G/L Vice City Stories Compiled
CASE {varname}
    WHEN {value0}
        {consequence}
    [WHEN {valueN}
        {consequence}]
    [ELSE
        {alternative}]
ENDCASE
 ---- 
{0038}
{004D}
{....}
{0002}
 ---- 
{0038}
{004D}
{....}
{0002}
 ---- 
{....}
 ---- 
 ---- 
 ---- 
{0039}
{004D}
{....}
{0002}
 ---- 
{0039}
{004D}
{....}
{0002}
 ---- 
{....}
 ---- 
 ---- 
 ---- 
{001B}
{0022}
{....}
{0002}
 ---- 
{001B}
{0022}
{....}
{0002}
 ---- 
{....}
 ---- 
 ---- 
WHEN0:
    {varname} = {value0}
GOTO_IF_FALSE WHENN
    {consequence}
    [GOTO ENDCASE0]
WHENN:
        [{varname} = {valueN}
    GOTO_IF_FALSE ELSE
        {consequence}
        [GOTO ENDCASEN]
    ELSE:
        [{alternative}
    ENDCASEN:]]
ENDCASE0:

Optimization

In Liberty City Stories and Vice City Stories, whenever a single condition is checked 00DB (Liberty City Stories) and 0078 (Vice City Stories) don't get compiled cause no logical operator (AND, OR) is used and so they become really useless. Its lack increase the script efficiency a lot. However, the jump of the ELSE clause of an IF statement which points to the end of the construct is still compiled after a GOTO. Furthermore, Stories Games come with an improved data type managing which causes a considerable decrease of the compiled file size.

Tools

External links