Difference between revisions of "SCM Instruction"

From GTAMods Wiki
Jump to navigation Jump to search
(Ignore the last. Fixed misinformation)
(17 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{This|This article deals with the technical information on the opcode format. For the opcodes documentation see [[list of opcodes]]}}
+
{{This|This article deals with the technical information on the SCM instruction format. For a list of opcodes along with descriptions, see [[list of opcodes]].}}
Each script instruction is represented by a number called '''opcode''' or [[Wikipedia:Opcode|operation code]] which is implemented using an [[Wikipedia:UINT16|16 bit unsigned integer]]. By this number the game engine identifies an action to perform. Say, an opcode [[0001]] tells to wait for amount of time, [[0003]] shakes the camera, [[0053]] creates a player, etc.  
+
An '''SCM instruction''' is a single operation in an SCM file. They are executed when the script is run, and are used to change what happens ingame.
  
 
__TOC__
 
__TOC__
  
== Format ==
+
== Instruction format ==
  
Initially the instructions are written in a human-readable form. For example, a ''wait'' instruction could look like this:
+
Each instruction is comprised of an ''opcode'' and ''arguments''.
 +
*The "opcode" (short for "[[Wikipedia:Opcode|operation code]]") is a number that tells the game which operation to perform. For example, the opcode [[0001]] tells the game to wait for a certain amount of time, [[0003]] shakes the game camera and [[0053]] creates a player. The opcode is a signed 16-bit integer.
 +
*"Arguments" are values sent with the instruction to change what it does. For example, you can change the amount of time to wait when using opcode [[0001]] by sending a different argument.
 +
 
 +
Before compilation, the instructions are written as text. For example, a <code>wait</code> instruction could look like this:
 
  wait 0
 
  wait 0
where the word ''wait'' is a compiler-dependent representation of the opcode 0001 and the number <code>0</code> is a single parameter for this particular instruction. When a mission script is assembled, the instructions are written back in raw byte form:
+
where <code>wait</code> represents the opcode <code>0001</code> and the number <code>0</code> is an argument passed with the instruction. Different compilers may use different words to represent each opcode. When the code is compiled, the instruction is converted to raw bytes:
  
  {{hint|0100|opcode}}&nbsp;{{hint|04|Data type}}&nbsp;{{hint|00|Parameter value}}
+
  {{hint|01 00|Opcode}}&nbsp;{{hint|04|Data type}}&nbsp;{{hint|00|Argument}}
  
* First part is the opcode number in a [[Wikipedia:Endianness|little-endian]] format.
+
* The first two bytes (<code>01 00</code> hex) are the opcode bytes in [[Wikipedia:Endianness|little-endian]] order.
* Second part is the [[#Data types|data type]]
+
* The third byte (<code>04</code> hex) is the code for the [[#Data types|data type]].{{Ref|05B6|[*]}} <code>0x4</code> is the code for a signed byte.
* Third part is the parameter value
+
* The final byte (<code>00</code> hex) is the argument value. This is often more than one byte: the size is determined by the data type.
  
As it has been said, an opcode is UINT16 number. It means the minimum opcode is [[0000]] and maximum opcode is 0xFFFF. However due to a specific of the [[SCM language]], any numbers above 0x7FFF denote negative [[Conditional statement|conditional opcodes]]. The original unmodded game supports a way smaller amount of opcodes (maximum [[0A4E]] for [[San Andreas]]), but there are tools adding new ones, most notably [[CLEO|CLEO Library]].
+
While the opcode bytes are always present, there may not always be arguments passed, so there may be no argument bytes.
  
There could be zero or more instruction parameters following the opcode number{{Ref|05B6|[*]}}.
+
No GTA game uses all the available opcodes (32,767 or <code>0x7FFF</code>). There are mods that add more instructions to the game, most notably the [[CLEO|CLEO Library]].
  
=== Parameters ===
+
Opcodes are always positive, but they are sometimes compiled as negative numbers. This happens when the return value of the instruction is inverted (with the <code>not</code> logical operator). In the example <code>a or b or not c</code>, instructions <code>a</code> and <code>b</code> would have positive opcodes, while instruction <code>c</code> would have a negative opcode. The game takes the absolute value of the opcode before finding the associated instruction's implementation.
The game engine knows amount of parameters for each instruction (1 for [[0001]], 2 for [[0004]], 13 for [[014B]], etc). If the script contains other number of parameters it causes a crash.  
 
  
The value of a parameter could be one of following types:
+
== Arguments ==
 +
Each instruction takes a certain number of arguments. If a script passes an incorrect number of arguments, the game will crash.
 +
 
 +
An argument could be one of the following types:
 
* Immediate values
 
* Immediate values
** integer numbers
+
** Integer (<code>1</code>, <code>324</code>)
** floating-point numbers
+
** Float (<code>0.43</code>, <code>672.0</code>)
** [[#Strings|fixed-length strings]]
+
** Strings (<code>"abc"</code>, <code>""</code>)
** {{icon|sa}} [[#Strings|variable-length strings]]
+
*** [[#Strings|Fixed-length strings]]
 +
*** [[#Strings|Variable-length strings]] {{icon|sa}}
 
* Variables
 
* Variables
** global variables
+
** Global variables
** local variables
+
** Local variables
* {{icon|sa}} {{icon|lcs}} {{icon|vcs}} [[#Arrays|arrays]]
+
* [[#Arrays|Arrays]] {{icon|sa}} {{icon|lcs}} {{icon|vcs}}
  
A concrete type of the value is determined by a single byte written before it{{ref|vcstr|[*]}}. This byte is called a data type. The purpose of it is to tell to the game how much bytes to read and how to treat it.
+
=== Concrete data types ===
 +
Each of the types listed above can be represented in a number of ways in compiled code for various reasons:
 +
* Integer values can be signed or unsigned, and there are also different sizes of integer that the game can use.
 +
* There are multiple types of string that allow different numbers of characters.
 +
* Variables use different type codes based on the type of value that they are referencing.
  
==== Data types ====
+
The concrete type of an argument is determined by a single byte before the value bytes{{ref|vcstr|[*]}}. This byte tells the game what value is coming next so that it knows how many bytes to read, and how to treat the value once read.
A '''[[wikipedia:Data type|data type]]''' is a classification of identifying the type of a value. Types commonly used in GTA include integer numbers, floating-point numbers, and strings. The amount of data allowed to be stored is limited by the type and size of the data. For integers there are two ways to represent the data, signed and unsigned, whereas floating-point values are always signed. A signed data range includes negative numbers while unsigned do not include negatives. The following list shows the types and sizes of data in bytes.
 
  
 +
==== III/VC/SA ====
 
{|class="mw-collapsible mw-collapsed wikitable"
 
{|class="mw-collapsible mw-collapsed wikitable"
!Data type<br/>(hex)
+
!Type code</br>(hex)
!Arg.<br/>length
+
!Value length</br>(bytes)
!Target<br/>game
+
!Support
 
!Description&nbsp;
 
!Description&nbsp;
 
|-
 
|-
 
!colspan=6|Typified
 
!colspan=6|Typified
 +
|-
 
|-valign="top"
 
|-valign="top"
 
|00||0||{{icon|3}} {{icon|vc}} {{icon|sa}}||End of argument list (EOAL, [[004F]] or [[0913]] and similar){{ref|partype0|[*]}}
 
|00||0||{{icon|3}} {{icon|vc}} {{icon|sa}}||End of argument list (EOAL, [[004F]] or [[0913]] and similar){{ref|partype0|[*]}}
 
|-valign="top"
 
|-valign="top"
 
|01||4||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 32-bit signed int
 
|01||4||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 32-bit signed int
<source lang="cpp">scriptParam.m_iIntValue = *(int *)m_pScriptPC;
+
value = ReadInt32(Bytecode, PC)
m_pScriptPC += 4;</source>
+
PC += 4
 
|-valign="top"
 
|-valign="top"
|02||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Global integer/floating-point variable
+
|{{anchor|globaltri}}02||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Global integer/floating-point variable
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
+
globalVar = ReadUInt16(Bytecode, PC)
m_pScriptPC += 2;</source>
+
PC += 2
 
|-valign="top"
 
|-valign="top"
|03||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Local integer/floating-point variable
+
|{{anchor|localtri}}03||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Local integer/floating-point variable
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
+
localVar = ReadUInt16(Bytecode, PC)
m_pScriptPC += 2;</source>
+
PC += 2
 
|-valign="top"
 
|-valign="top"
 
|04||1||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 8-bit signed int
 
|04||1||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 8-bit signed int
<source lang="cpp">scriptParam.m_iIntValue = *(char *)m_pScriptPC++;</source>
+
value = SignExtend32(ReadInt8(Bytecode, PC))
 +
PC += 1
 
|-valign="top"
 
|-valign="top"
 
|05||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 16-bit signed int
 
|05||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 16-bit signed int
<source lang="cpp">scriptParam.m_iIntValue = *(short *)m_pScriptPC;
+
value = SignExtend32(ReadInt16(Bytecode, PC))
m_pScriptPC += 2;</source>
+
PC += 2
 
|-valign="top"
 
|-valign="top"
 
|06||2||{{icon|3}}||Immediate 16-bit fixed-point (see [[Talk:Mission_Scripting_(Overview)#Fixed-point_remark|remark]])
 
|06||2||{{icon|3}}||Immediate 16-bit fixed-point (see [[Talk:Mission_Scripting_(Overview)#Fixed-point_remark|remark]])
<source lang="cpp">scriptParam.m_fFloatValue = (float)(*(short *)m_pScriptPC) / 16.0f;
+
value = ToFloat32(ReadUInt16(Bytecode, PC)) / 16.0
m_pScriptPC += 2;</source>
+
PC += 2
 
|-valign="top"
 
|-valign="top"
 
|06||4||align="right"|{{icon|vc}} {{icon|sa}}||Immediate 32-bit floating-point
 
|06||4||align="right"|{{icon|vc}} {{icon|sa}}||Immediate 32-bit floating-point
<source lang="cpp">scriptParam.m_fFloatValue = *(float *)m_pScriptPC;
+
value = ReadFloat32(Bytecode, PC)
m_pScriptPC += 4;</source>
+
PC += 4
 
|-valign="top"
 
|-valign="top"
|07||6||align="right"|{{icon|sa}}||Global integer/floating-point array{{ref|varray|[*]}}
+
|{{anchor|globaltri2}}07||6||align="right"|{{icon|sa}}||Global integer/floating-point array
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
+
globalVar = ReadUInt16(Bytecode, PC)
scriptParam.m_sArrayIndexVar = *(short *)(m_pScriptPC + 2);
+
arrayIndexVar = ReadUInt16(Bytecode, PC+2)
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 4);
+
//arraySize = ReadUInt8(Bytecode, PC+4)
scriptParam.m_arrayProperties = *(ArrayProperties *)(m_pScriptPC + 5);
+
//elementType = ReadUInt8(Bytecode, PC+5) & 0x7F
m_pScriptPC += 6;</source>
+
isGlobalIndex = ReadUInt8(Bytecode, PC+5) >> 7
 +
PC += 6
 
|-valign="top"
 
|-valign="top"
|08||6||align="right"|{{icon|sa}}||Local integer/floating-point array{{ref|varray|[*]}}
+
|{{anchor|localtri2}}08||6||align="right"|{{icon|sa}}||Local integer/floating-point array
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
+
localVar = ReadUInt16(Bytecode, PC)
scriptParam.m_sArrayIndexVar = *(short *)(m_pScriptPC + 2);
+
arrayIndexVar = ReadUInt16(Bytecode, PC+2)
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 4);
+
//arraySize = ReadUInt8(Bytecode, PC+4)
scriptParam.m_arrayProperties = *(ArrayProperties *)(m_pScriptPC + 5);
+
//elementType = ReadUInt8(Bytecode, PC+5) & 0x7F
m_pScriptPC += 6;</source>
+
isGlobalIndex = ReadUInt8(Bytecode, PC+5) >> 7
 +
PC += 6
 +
|-
 +
!colspan=6|Tipified (strings)
 
|-valign="top"
 
|-valign="top"
|09||8||align="right"|{{icon|sa}}||Immediate 8-byte string{{ref|str8|[*]}}
+
|09||8||align="right"|{{icon|sa}}||Immediate 8-byte string
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
+
textLabel = ReadString(Bytecode, PC, 8)
m_pScriptPC += 8;</source>
+
PC += 8
 
|-valign="top"
 
|-valign="top"
|0A||2||align="right"|{{icon|sa}}||Global 8-byte string variable
+
|0A||2||align="right"|{{icon|sa}}||Global 8-byte string variable (see [[#globaltri|02]])
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
 
|-valign="top"
 
|-valign="top"
|0B||2||align="right"|{{icon|sa}}||Local 8-byte string variable
+
|0B||2||align="right"|{{icon|sa}}||Local 8-byte string variable (see [[#localtri|03]])
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
 
|-valign="top"
 
|-valign="top"
|0C||6||align="right"|{{icon|sa}}||Global 8-byte string array{{ref|varray|[*]}}
+
|0C||6||align="right"|{{icon|sa}}||Global 8-byte string array (see [[#globaltri2|07]])
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
scriptParam.m_sArrayIndexVar = *(short *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 4);
 
scriptParam.m_arrayProperties = *(ArrayProperties *)(m_pScriptPC + 5);
 
m_pScriptPC += 6;</source>
 
 
|-valign="top"
 
|-valign="top"
|0D||6||align="right"|{{icon|sa}}||Local 8-byte string array{{ref|varray|[*]}}
+
|0D||6||align="right"|{{icon|sa}}||Local 8-byte string array (see [[#localtri2|08]])
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
scriptParam.m_sArrayIndexVar = *(short *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 4);
 
scriptParam.m_arrayProperties = *(ArrayProperties *)(m_pScriptPC + 5);
 
m_pScriptPC += 6;</source>
 
 
|-valign="top"
 
|-valign="top"
|0E||1 + (n - 1)||align="right"|{{icon|sa}}||Immediate variable-length string{{ref|str16|[*]}} (non null-terminated)
+
|0E||1+n||align="right"|{{icon|sa}}||Immediate variable-length string (non null-terminated)
<source lang="cpp">char cStrLength = *(char *)m_pScriptPC++;
+
length = ReadInt8(Bytecode, PC)
strncpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC, cStrLength);
+
textLabel = ReadString(Bytecode, PC+1, length)
memset(&scriptParam.m_szTextLabel[cStrLength], '\0', ucMaxLength - cStrLength);
+
          + StrPad("\0", 40-length)
m_pScriptPC += cStrLength;</source>
+
PC += 1+length
 
|-valign="top"
 
|-valign="top"
|0F||16||align="right"|{{icon|sa}}||Immediate 16-byte string{{ref|sa16|[*]}}
+
|0F||16||align="right"|{{icon|sa}}||Immediate 16-byte string
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
+
textLabel = ReadString(Bytecode, PC, 16)
m_pScriptPC += 16;</source>
+
PC += 16
 
|-valign="top"
 
|-valign="top"
|10||2||align="right"|{{icon|sa}}||Global 16-byte string variable
+
|10||2||align="right"|{{icon|sa}}||Global 16-byte string variable (see [[#globaltri|02]])
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
 
|-valign="top"
 
|-valign="top"
|11||2||align="right"|{{icon|sa}}||Local 16-byte string variable
+
|11||2||align="right"|{{icon|sa}}||Local 16-byte string variable (see [[#localtri|03]])
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
 
|-valign="top"
 
|-valign="top"
|12||6||align="right"|{{icon|sa}}||Global 16-byte string array{{ref|varray|[*]}}
+
|12||6||align="right"|{{icon|sa}}||Global 16-byte string array (see [[#globaltri2|07]])
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
scriptParam.m_sArrayIndexVar = *(short *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 4);
 
scriptParam.m_arrayProperties = *(ArrayProperties *)(m_pScriptPC + 5);
 
m_pScriptPC += 6;</source>
 
 
|-valign="top"
 
|-valign="top"
|13||6||align="right"|{{icon|sa}}||Local 16-byte string array{{ref|varray|[*]}}
+
|13||6||align="right"|{{icon|sa}}||Local 16-byte string array (see [[#localtri2|08]])
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
scriptParam.m_sArrayIndexVar = *(short *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 4);
 
scriptParam.m_arrayProperties = *(ArrayProperties *)(m_pScriptPC + 5);
 
m_pScriptPC += 6;</source>
 
 
|-
 
|-
 
!colspan=6|Untypified
 
!colspan=6|Untypified
 
|-valign="top"
 
|-valign="top"
 
|N/A||8||{{icon|3}} {{icon|vc}}||Immediate 8-byte string{{ref|vcstr|[*]}}
 
|N/A||8||{{icon|3}} {{icon|vc}}||Immediate 8-byte string{{ref|vcstr|[*]}}
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
+
textLabel = ReadString(Bytecode, PC, 8)
m_pScriptPC += 8;</source>
+
PC += 8
 
|-valign="top"
 
|-valign="top"
 
|N/A||128||align="right"|{{icon|sa}}||Immediate 128-byte string
 
|N/A||128||align="right"|{{icon|sa}}||Immediate 128-byte string
<source lang="cpp">strcpy(scriptParam.m_szString, (char *)m_pScriptPC);
+
string = ReadString(Bytecode, PC, 128)
m_pScriptPC += 128;</source>
+
PC += 128
 
|}
 
|}
  
Depending of the preceeding data type, the parameter value compiled in two bytes <code>{{hint|02 00|Little-endian order}}</code>, could be treated either as the global variable <code style="color:blue">$2</code>, or a local variable <code style="color:blue">2@</code> or a number of <code style="color:maroon">2</code>. The data type allows the game to determine the correct parameter meaning.
+
==== LCS/VCS ====
 
+
Type codes for [[Liberty City Stories]] and [[Vice City Stories]] are very different from other games:
Data types for [[Liberty City Stories]] and [[Vice City Stories]] are much different. First of all, many data types itself denote an immediate value. For example, data type 01 is a value of 0, data type 02 the value 0.0, etc. Floating-point values are packed (1, 2 or 3 bytes of length instead of the [[Wikipedia:IEEE_754-1985#Single-precision_32-bit|common 4]]). Some data types itself are somewhat the identifier of a variable.
+
* In some instances, the type code itself denotes the argument value. For example:
 +
** <code>0x1</code> represents the integer value 0
 +
** <code>0x2</code> represents 0.0
 +
* The type code can sometimes denote a variable.
 +
* Floating point values are packed (8, 16 or 24 bits as opposed to the more common [[Wikipedia:IEEE_754-1985#Single-precision_32-bit|32 bits]]).
  
 
{|class="mw-collapsible mw-collapsed wikitable"
 
{|class="mw-collapsible mw-collapsed wikitable"
!Data type<br/>(hex)
+
!Type code</br>(hex)
!Arg.<br/>length
+
!Value length</br>(bytes)
!Target<br/>game
+
!Support
 
!Description&nbsp;
 
!Description&nbsp;
 
|-
 
|-
Line 174: Line 167:
 
|-valign="top"
 
|-valign="top"
 
|01||0||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit signed integer constant 0
 
|01||0||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit signed integer constant 0
<source lang="cpp">scriptParam.m_iIntValue = 0;</source>
+
value = 0
 
|-valign="top"
 
|-valign="top"
 
|02||0||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit floating-point constant 0.0
 
|02||0||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit floating-point constant 0.0
<source lang="cpp">scriptParam.m_fFloatValue = 0.0f;</source>
+
value = 0.0
 
|-valign="top"
 
|-valign="top"
 
|03||1||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit packed floating-point
 
|03||1||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit packed floating-point
<source lang="cpp">unsigned int uiUnpackedFloat = *(unsigned char *)m_pScriptPC++ << 24;
+
value = AsFloat32(ReadUInt8(Bytecode, PC) << 24)
scriptParam.m_fFloatValue = *(float *)&uiUnpackedFloat;</source>
+
PC += 1
 
|-valign="top"
 
|-valign="top"
 
|04||2||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 16-bit packed floating-point
 
|04||2||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 16-bit packed floating-point
<source lang="cpp">unsigned int uiUnpackedFloat = *(unsigned short *)m_pScriptPC << 16;
+
value = AsFloat32((ReadUInt8(Bytecode, PC) << 16)
scriptParam.m_fFloatValue = *(float *)&uiUnpackedFloat;
+
                <nowiki>|</nowiki> (ReadUInt8(Bytecode, PC+1) << 24))
m_pScriptPC += 2;</source>
+
PC += 2
 
|-valign="top"
 
|-valign="top"
 
|05||3||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 24-bit packed floating-point
 
|05||3||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 24-bit packed floating-point
<source lang="cpp">unsigned int uiUnpackedFloat
+
value = AsFloat32((ReadUInt8(Bytecode, PC) << 8)
    = (*(unsigned short *)m_pScriptPC << 16)
+
                <nowiki>|</nowiki> (ReadUInt8(Bytecode, PC+1) << 16)
    | (*(unsigned char *)(m_pScriptPC + 2) << 8);
+
                <nowiki>|</nowiki> (ReadUInt8(Bytecode, PC+2) << 24))
scriptParam.m_fFloatValue = *(float *)&uiUnpackedFloat;
+
PC += 3
m_pScriptPC += 3;</source>
 
 
|-valign="top"
 
|-valign="top"
 
|06||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 32-bit signed integer
 
|06||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 32-bit signed integer
<source lang="cpp">scriptParam.m_iIntValue = *(int *)m_pScriptPC;
+
value = ReadInt32(Bytecode, PC)
m_pScriptPC += 4;</source>
+
PC += 4
 
|-valign="top"
 
|-valign="top"
 
|07||1||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit signed integer
 
|07||1||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit signed integer
<source lang="cpp">scriptParam.m_iIntValue = *(char *)m_pScriptPC++;</source>
+
value = SignExtend32(ReadInt8(Bytecode, PC))
 +
PC += 1
 
|-valign="top"
 
|-valign="top"
 
|08||2||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 16-bit signed integer
 
|08||2||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 16-bit signed integer
<source lang="cpp">scriptParam.m_iIntValue = *(short *)m_pScriptPC;
+
value = SignExtend32(ReadInt16(Bytecode, PC))
m_pScriptPC += 2;</source>
+
PC += 2
 
|-valign="top"
 
|-valign="top"
 
|09||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 32-bit floating-point
 
|09||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 32-bit floating-point
<source lang="cpp">scriptParam.m_fFloatValue = *(float *)m_pScriptPC;
+
value = ReadFloat32(Bytecode, PC)
m_pScriptPC += 4;</source>
+
PC += 4
 
|-valign="top"
 
|-valign="top"
|0A||n + NUL||align="right"|{{icon|vcs}}||Immediate null-terminated string{{ref|strvcs|[*]}}
+
|0A||n+NUL||align="right"|{{icon|vcs}}||Immediate null-terminated string{{ref|strvcs|[*]}}
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
+
textLabel = ReadString(Bytecode, PC)
m_pScriptPC += strlen((char *)m_pScriptPC) + 1;</source>
+
PC += StrLen(textLabel)+1
 
|-
 
|-
!colspan=5|Untypified (script variables)
+
!colspan=5|Typified (script variables)
 
|-valign="top"
 
|-valign="top"
|T<0C||1||{{icon|lcs}}||Local timers (''TIMERA'', ''TIMERB'')
+
|0A..0B||1||{{icon|lcs}}||Local timers (''TIMERA'', ''TIMERB'')
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ + 0x5E;</source>
+
localVar = ReadUInt8(Bytecode, PC) + 0x5E
 +
PC += 1
 
|-valign="top"
 
|-valign="top"
|T<0D||1||align="right"|{{icon|vcs}}||Local timers (''TIMERA'', ''TIMERB'')
+
|0B..0C||1||align="right"|{{icon|vcs}}||Local timers (''TIMERA'', ''TIMERB'')
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ + 0x5D;</source>
+
localVar = ReadUInt8(Bytecode, PC) + 0x5D
 +
PC += 1
 
|-valign="top"
 
|-valign="top"
|T<6C||1||{{icon|lcs}}||Local integer/floating-point variable
+
|0C..6B||1||{{icon|lcs}}||Local integer/floating-point variable
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ - 0x0C;</source>
+
localVar = ReadUInt8(Bytecode, PC) - 0x0C
 +
PC += 1
 
|-valign="top"
 
|-valign="top"
|T<6D||1||align="right"|{{icon|vcs}}||Local integer/floating-point variable
+
|0D..6C||1||align="right"|{{icon|vcs}}||Local integer/floating-point variable
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ - 0x0D;</source>
+
localVar = ReadUInt8(Bytecode, PC) - 0x0D
 +
PC += 1
 
|-valign="top"
 
|-valign="top"
|T<CC||3||{{icon|lcs}}||Local integer/floating-point array
+
|6C..CB||3||{{icon|lcs}}||Local integer/floating-point array
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC - 0x6C;
+
localVar = ReadUInt8(Bytecode, PC) - 0x6C
scriptParam.m_sArrayIndex = *(unsigned char *)(m_pScriptPC + 2);
+
arrayIndexVar = ReadUInt8(Bytecode, PC+1)
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 3);
+
arraySize = ReadUInt8(Bytecode, PC+2)
m_pScriptPC += 3;</source>
+
PC += 3
 
|-valign="top"
 
|-valign="top"
|T<CD||3||align="right"|{{icon|vcs}}||Local integer/floating-point array
+
|6D..CC||3||align="right"|{{icon|vcs}}||Local integer/floating-point array
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC - 0x6D;
+
localVar = ReadUInt8(Bytecode, PC) - 0x6D
scriptParam.m_sArrayIndex = *(unsigned char *)(m_pScriptPC + 2);
+
arrayIndexVar = ReadUInt8(Bytecode, PC+1)
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 3);
+
arraySize = ReadUInt8(Bytecode, PC+2)
m_pScriptPC += 3;</source>
+
PC += 3
 
|-valign="top"
 
|-valign="top"
|rowspan=2|T<E6||rowspan=2|2||{{icon|lcs}}||Global integer/floating-point variable
+
|CC..E5||2||{{icon|lcs}}||Global integer/floating-point variable{{ref|gvars|[*]}}
<source lang="cpp">unsigned short usBigEndianWord = *(unsigned short *)m_pScriptPC - 0x00CC;
+
globalVar = ByteSwap16(ReadUInt16(Bytecode, PC) - 0xCC)
scriptParam.m_sGlobalVar = (short)((usBigEndianWord << 8) | (usBigEndianWord >> 8));
+
PC += 2
m_pScriptPC += 2;</source>
 
 
|-valign="top"
 
|-valign="top"
|align="right"|{{icon|vcs}}||Global integer/floating-point variable
+
|CD..E5||2||align="right"|{{icon|vcs}}||Global integer/floating-point variable{{ref|gvars|[*]}}
<source lang="cpp">unsigned short usBigEndianWord = *(unsigned short *)m_pScriptPC - 0x00CD;
+
globalVar = ByteSwap16(ReadUInt16(Bytecode, PC) - 0xCD)
scriptParam.m_sGlobalVar = (short)((usBigEndianWord << 8) | (usBigEndianWord >> 8));
+
PC += 2
m_pScriptPC += 2;</source>
 
 
|-valign="top"
 
|-valign="top"
|T>=E6||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Global integer/floating-point array
+
|E6..FF||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Global integer/floating-point array
<source lang="cpp">unsigned short usBigEndianWord = *(unsigned short *)m_pScriptPC - 0x00E6;
+
globalVar = ByteSwap16(ReadUInt16(Bytecode, PC) - 0xE6)
scriptParam.m_sGlobalVar = (short)((usBigEndianWord << 8) | (usBigEndianWord >> 8));
+
arrayIndexVar = ReadUInt8(Bytecode, PC+2)
scriptParam.m_sArrayIndex = *(unsigned char *)(m_pScriptPC + 2);
+
arraySize = ReadUInt8(Bytecode, PC+3)
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 3);
+
PC += 4
m_pScriptPC += 4;</source>
+
|-
 +
!colspan=6|Untipified
 
|-valign="top"
 
|-valign="top"
 
|N/A||8||{{icon|lcs}}||Immediate 8-byte string
 
|N/A||8||{{icon|lcs}}||Immediate 8-byte string
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
+
textLabel = ReadString(Bytecode, PC, 8)
m_pScriptPC += 8;</source>
+
PC += 8
 
|}
 
|}
  
 
{{note|strvcs}} This type was introduced in VCS due to the presence of string variables.
 
{{note|strvcs}} This type was introduced in VCS due to the presence of string variables.
  
All the data types above haven't been tested in a decompiling process yet, hence they still need a practical confirmation.
+
{{note|gvars}} Given the data type range limit the largest global variable in LCS is 6655, in VCS is 6399.
 
 
{{Incomplete}}
 
  
==== Integer numbers ====
+
=== Integer numbers ===
  
An '''[[wikipedia:Integer (computer science)|integer]]''' is a number without a decimal or fractional component.
+
An [[wikipedia:Integer (computer science)|integer]] is a number without a decimal or fractional component.
  
 
{| class="wikitable"
 
{| class="wikitable"
!rowspan="2"| Size<br>({{hint|bytes|Each byte is 4 bits}}) ||colspan="6"| Range
+
!rowspan="2"| Size<br>(bytes) ||colspan="6"| Range
 
|-
 
|-
 
! || Signed || Name || || Unsigned || Name
 
! || Signed || Name || || Unsigned || Name
Line 284: Line 278:
 
|}
 
|}
  
==== Floating-point numbers ====
+
=== Floating-point numbers ===
A '''[[wikipedia:Floating point|floating point]]''' is a number with a decimal component and can store extremely large or small numbers while sacrificing significant digits. This is achieved by internally using exponents in scientific notation.
+
A [[wikipedia:Floating point|floating point]] number is a number with a decimal component.
  
 
{| class="wikitable"
 
{| class="wikitable"
! Size<br>({{hint|bytes|Each byte is 4 bits}}) || Range || Name
+
! Size<br>(bytes) || Range || Name
 
|-
 
|-
 
| 4 || ±1.1754944×10<sup>-38</sup> to ±3.4028234×10<sup>38</sup> || [[wikipedia:Single precision floating-point format|SINGLE]], FLOAT
 
| 4 || ±1.1754944×10<sup>-38</sup> to ±3.4028234×10<sup>38</sup> || [[wikipedia:Single precision floating-point format|SINGLE]], FLOAT
 
|}
 
|}
  
==== Strings ====
+
=== Strings ===
A ''string'' is a sequence of characters not treated as numbers. Those include {{hint|letters|A..Z}}, {{hint|numbers|0..9}}, and other symbols like _ or @. Unlike other programming languages a string could start with any character, even a space.
+
A [[wikipedia:String (computer science)|string]] is a piece of text. Strings can include letters, numbers and symbols.
 
 
There are two kinds of strings.
 
  
{{note|str8}} A '''fixed-length string''' or a null-terminated string. This is the most common type been used since [[GTA 3]]. The string length is fixed. When compiled these strings occupy 8 bytes of a [[SCM]] file even if they are actually shorter (the rest is filled with [[Wikipedia:null terminator|zero bytes]]).
+
There are two kinds of string:
 +
* Fixed-length. This is the most common type of string and has been used since [[GTA 3]]. The string length is fixed. When compiled these strings occupy a certain number of bytes (8 or 16) even if the text is actually shorter (any unused bytes are filled with [[Wikipedia:null terminator|null values]]).
 +
* Variable-length (SA only). Variable-length strings are encoded as a single byte specifying the length followed by the string character bytes. These strings are not null-terminated. The maximum length depends on the instruction{{Ref|longstringslimits|[*]}}. The longest in the original game is 40 characters.
  
{{note|sa16}} San Andreas introduces data type 15 for strings containing up to 15 symbols. They behave same as 8 bytes strings, but always occupy 16 bytes in a SCM file. These strings are only supported by [[Sanny Builder]].
 
 
{|{{Prettytable}}
 
{|{{Prettytable}}
!width="250px" align="left"|String
+
!width="250px" align="left"|String value
!width="250px" align="left"|Equivalent in SCM
+
!width="250px" align="left"|Compiled bytes
 +
|-
 +
| <code>"MAIN"</code>||{{hint|09|Type code}}&nbsp;&nbsp;&nbsp;{{hint|4D 41 49 4E 00 00 00 00|ASCII values of string characters}}
 
|-
 
|-
| <span style="color:red">'MAIN'</span>||{{hint|09|Data type}}&nbsp;&nbsp;&nbsp;{{hint|4D 41 49 4E 00 00 00 00|ASCII code of string characters}}
+
| <code>"MODDING"</code>||{{hint|09|Type code}}&nbsp;&nbsp;&nbsp;{{hint|4D 4F 44 44 49 4E 47 00|ASCII values of string characters}}
 
|-
 
|-
| <span style="color:red">'MODDING'</span>||{{hint|09|Data type}}&nbsp;&nbsp;&nbsp;{{hint|4D 4F 44 44 49 4E 47 00|ASCII code of string characters}}
+
| <code>"SAVE_OUR_SOULS!"</code>||{{hint|0F|Type code}}&nbsp;&nbsp;&nbsp;{{hint|53 41 56 45 5F 4F 55 52 5F 53 4F 55 4C 53 21 00|ASCII values of string characters}}
 
|-
 
|-
| <span style="color:red">'SAVE_OUR_SOULS!'</span>||{{hint|0F|Data type}}&nbsp;&nbsp;&nbsp;{{hint|53 41 56 45 5F 4F 55 52 5F 53 4F 55 4C 53 21 00|ASCII code of string characters}}
+
| <code>"Variable length string"</code>||{{hint|0E|Type code}}&nbsp;&nbsp;&nbsp;{{hint|16|Length (22 decimal)}}&nbsp;&nbsp;&nbsp;{{hint|56 61 72 69 61 62 6C 65 20 6C 65 6E 67 74 68 20 73 74 72 69 6E 67|ASCII values of string characters}}
 
|}
 
|}
{{note|str16}} A '''variable-length string'''. This type was first introduced in San Andreas. Maximum length depends on the instruction{{Ref|longstringslimits|[*]}} (the longest parameter ever read has got 40 characters).
 
 
{{Incomplete}}
 
  
==== Arrays ====
+
=== Arrays ===
{{note|varray}} Native ''[[Wikipedia:Array|arrays]]'' support was introduced in GTA SA, however there were different implementations of [[VC_Arrays|arrays in Vice City]]. In SA SCM arrays are assembled as 2 ''UINT16''s, 1 ''INT8'' and a ''UINT8'':
+
In GTA SA, an array is a static reference to a group of successive variables. There is no actual representation of an array ''value'' as a concrete type, they do have type codes. When an "array" is passed as an argument to an instruction, what is actually passed is a specific variable in that array. For this reason, array elements are passed as variables of the type in the array – that is to say, a value from an array of global integer variables would be passed as a global integer variable. The game simply performs the extra step of retrieving the variable from the array before using it in the instruction.
2b - UINT16 - array offset{{ref|arrayoffset|[*]}}
 
2b - UINT16 - array index{{ref|arrayindex|[*]}}
 
1b - INT8  - array size
 
1b - UINT8  - array properties
 
  
{{note|arrayoffset}} An array offset is basically a variable number. If it's a global array, the offset is a global variable index from which the array begins. For example, if the global array offset is 150 (<code>{{hint|96 00|Little-endian order}}</code>) it means that the first element of the array is <span style="color:blue">$150</span>, the second one is <span style="color:blue">$151</span>, etc. Same valid for the local arrays (offset is a local variable index). <!-- global variables are multiplied by 4 -->
+
This section describes the format of array accesses in GTA SA. Vice City also has arrays, but [[VC_Arrays|in a different format]].
  
{{note|arrayindex}} An array index is a variable number (global or local one) that holds the value of array index. For example, if array index is 3 (<code>{{hint|03 00|Little-endian order}}</code>), the game will read either global variable <span style="color:blue">$3</span> or local variable <span style="color:blue">3@</span> depending on the array properties (see below). This variable holds the number which is array element ID to work with. For example, if the array index is <span style="color:blue">$3</span>, and <span style="color:blue">$3</span> holds number <span style="color:maroon">5</span>, the game will read 5th element of the array.
+
<source lang="cpp">
 
+
// Example: not real code.
===== Properties =====
+
// Applies only to GTA: SA.
Array properties describe the data type of each array element, held by the first 7 bits of the reference field, plus a flag which signals if the array was declared in a global scope, as the most significant bit indicates:
+
struct ArrayAccess {
 
+
    enum ElementType : uint8_t {
<source lang="cpp" style="margin-top: 8px">enum eArrayElementType
+
        Int,
{
+
        Float,
ELEMTYPE_INT,
+
        String8,
ELEMTYPE_FLOAT,
+
        String16
ELEMTYPE_TEXT_LABEL,
+
    };
ELEMTYPE_TEXT_LABEL16
+
   
 +
    // Offset of first variable in the array.
 +
    uint16_t startOffset;
 +
   
 +
    // Index being accessed ("array[index]").
 +
    // This can be a local variable ("array[someLocalVar]") or a global variable ("array[someGlobalVar]").
 +
    uint16_t index;
 +
   
 +
    // Array length.
 +
    int8_t length;
 +
   
 +
    // Array element type. 7 bits.
 +
    ElementType type : 7;
 +
   
 +
    // Determines whether the index is a global variable (true) or a local variable (false).
 +
    // Only 1 bit.
 +
    bool globalIndex : 1;
 
};
 
};
 +
</source>
  
struct ArrayProperties
+
The array offset is a variable which should have a value more than or equal to 0 and smaller than the array size. Global and local variables can be used as index variables – the purpose of the <code>globalIndex</code> field is to tell the game which has been used. The first element in the array is at index <code>0</code>, and the last is at <code>length - 1</code>.
{
 
unsigned char m_nElementType : 7;
 
unsigned char m_bIsIndexGlobalVar : 1;
 
};</source>
 
 
 
{|{{Prettytable}}
 
!width="250px" align="left"|Array
 
!width="250px" align="left"|Equivalent in SCM
 
|-
 
| <span style="color:blue">{{hint|$150(3@,6f)|Sanny Builder syntax}}</span>||{{hint|07|Data type}}&nbsp;&nbsp;&nbsp;{{hint|96 00|Array offset}}&nbsp;{{hint|03 00|Array index}}&nbsp;{{hint|06|Array size}}&nbsp;{{hint|01|Array properties}}
 
|-
 
| <span style="color:blue">{{hint|10@(9@,5s)|Sanny Builder syntax}}</span>||{{hint|0D|Data type}}&nbsp;&nbsp;&nbsp;{{hint|0A 00|Array offset}}&nbsp;{{hint|09 00|Array index}}&nbsp;{{hint|05|Array size}}&nbsp;{{hint|02|Array properties}}
 
|}
 
  
 
== Notes ==
 
== Notes ==
{{note|vcstr}} In [[GTA 3]], [[Vice City]] and [[Liberty City Stories]] short strings (8 bytes) have no data type preceeding it. If the byte does not fit data type range (00-06 for GTA 3 and VC), it's recognized as a beginning of a string and next 8 bytes are read.
+
{{note|vcstr}} In [[GTA 3]], [[Vice City]] and [[Liberty City Stories]], short strings (8 bytes) have no type code. If the first byte of an argument does not fit data type range (<code>0x0</code> - <code>0x6</code> for GTA 3 and VC), it's recognized as the beginning of a string and the remaining 8 bytes of the string are read.
 
 
{{note|partype0}} Some instructions have variable amount of parameters. The most known one is [[004F]] that creates a new [[thread]] and passes arguments to it. The number of such parameters could vary, so the special data type denotes the end of parameters.
 
  
The maximum amount of parameters for any instruction is 16 for GTA 3 and VC, 32 for SA, LCS and VCS. However, those that admit an undefined amount of arguments can pass 18 parameters for GTA 3 and VC, 34 for SA, 106 for LCS and VCS (this information still needs confirmation).
+
{{note|partype0}} Some instructions have a variable number of parameters. One such instruction is [[004F]] that starts a new [[script]] and takes a variable number of arguments to allow extra script setup. The game uses the special data type to end such argument lists. The number of input/output parameters an instruction can collect/store at a time (separately) is 16 for GTA 3 and VC and 32 for SA, LCS and VCS. Variadic instructions allows for passing additional arguments as much as the amount of local variables minus timers which is 16 for GTA 3 and VC, 32 for SA and 96 for LCS and VCS.
  
{{note|05B6}} {{Icon|SA}} Opcode [[05B6]] is a special instruction that defines a table. Immediately after opcode number the stream of data (128 bytes) follows, without a data type.
+
{{note|05B6}} In {{Icon|SA}}, [[05B6]] is a special instruction that defines a table. Immediately after the opcode a 128 byte stream of data follows, without a type code.
  
 
{{note|longstringslimits}} {{GTAF|post|261006|3940262|Post by Seemann describing limits for the long strings in SA}}
 
{{note|longstringslimits}} {{GTAF|post|261006|3940262|Post by Seemann describing limits for the long strings in SA}}

Revision as of 17:47, 27 September 2020

This article deals with the technical information on the SCM instruction format. For a list of opcodes along with descriptions, see list of opcodes.

An SCM instruction is a single operation in an SCM file. They are executed when the script is run, and are used to change what happens ingame.

Instruction format

Each instruction is comprised of an opcode and arguments.

  • The "opcode" (short for "operation code") is a number that tells the game which operation to perform. For example, the opcode 0001 tells the game to wait for a certain amount of time, 0003 shakes the game camera and 0053 creates a player. The opcode is a signed 16-bit integer.
  • "Arguments" are values sent with the instruction to change what it does. For example, you can change the amount of time to wait when using opcode 0001 by sending a different argument.

Before compilation, the instructions are written as text. For example, a wait instruction could look like this:

wait 0

where wait represents the opcode 0001 and the number 0 is an argument passed with the instruction. Different compilers may use different words to represent each opcode. When the code is compiled, the instruction is converted to raw bytes:

01 00 04 00
  • The first two bytes (01 00 hex) are the opcode bytes in little-endian order.
  • The third byte (04 hex) is the code for the data type.[*] 0x4 is the code for a signed byte.
  • The final byte (00 hex) is the argument value. This is often more than one byte: the size is determined by the data type.

While the opcode bytes are always present, there may not always be arguments passed, so there may be no argument bytes.

No GTA game uses all the available opcodes (32,767 or 0x7FFF). There are mods that add more instructions to the game, most notably the CLEO Library.

Opcodes are always positive, but they are sometimes compiled as negative numbers. This happens when the return value of the instruction is inverted (with the not logical operator). In the example a or b or not c, instructions a and b would have positive opcodes, while instruction c would have a negative opcode. The game takes the absolute value of the opcode before finding the associated instruction's implementation.

Arguments

Each instruction takes a certain number of arguments. If a script passes an incorrect number of arguments, the game will crash.

An argument could be one of the following types:

Concrete data types

Each of the types listed above can be represented in a number of ways in compiled code for various reasons:

  • Integer values can be signed or unsigned, and there are also different sizes of integer that the game can use.
  • There are multiple types of string that allow different numbers of characters.
  • Variables use different type codes based on the type of value that they are referencing.

The concrete type of an argument is determined by a single byte before the value bytes[*]. This byte tells the game what value is coming next so that it knows how many bytes to read, and how to treat the value once read.

III/VC/SA

Type code
(hex)
Value length
(bytes)
Support Description 
Typified
00 0 GTA III Vice City San Andreas End of argument list (EOAL, 004F or 0913 and similar)[*]
01 4 GTA III Vice City San Andreas Immediate 32-bit signed int
value = ReadInt32(Bytecode, PC)
PC += 4
02 2 GTA III Vice City San Andreas Global integer/floating-point variable
globalVar = ReadUInt16(Bytecode, PC)
PC += 2
03 2 GTA III Vice City San Andreas Local integer/floating-point variable
localVar = ReadUInt16(Bytecode, PC)
PC += 2
04 1 GTA III Vice City San Andreas Immediate 8-bit signed int
value = SignExtend32(ReadInt8(Bytecode, PC))
PC += 1
05 2 GTA III Vice City San Andreas Immediate 16-bit signed int
value = SignExtend32(ReadInt16(Bytecode, PC))
PC += 2
06 2 GTA III Immediate 16-bit fixed-point (see remark)
value = ToFloat32(ReadUInt16(Bytecode, PC)) / 16.0
PC += 2
06 4 Vice City San Andreas Immediate 32-bit floating-point
value = ReadFloat32(Bytecode, PC)
PC += 4
07 6 San Andreas Global integer/floating-point array
globalVar = ReadUInt16(Bytecode, PC)
arrayIndexVar = ReadUInt16(Bytecode, PC+2)
//arraySize = ReadUInt8(Bytecode, PC+4)
//elementType = ReadUInt8(Bytecode, PC+5) & 0x7F
isGlobalIndex = ReadUInt8(Bytecode, PC+5) >> 7
PC += 6
08 6 San Andreas Local integer/floating-point array
localVar = ReadUInt16(Bytecode, PC)
arrayIndexVar = ReadUInt16(Bytecode, PC+2)
//arraySize = ReadUInt8(Bytecode, PC+4)
//elementType = ReadUInt8(Bytecode, PC+5) & 0x7F
isGlobalIndex = ReadUInt8(Bytecode, PC+5) >> 7
PC += 6
Tipified (strings)
09 8 San Andreas Immediate 8-byte string
textLabel = ReadString(Bytecode, PC, 8)
PC += 8
0A 2 San Andreas Global 8-byte string variable (see 02)
0B 2 San Andreas Local 8-byte string variable (see 03)
0C 6 San Andreas Global 8-byte string array (see 07)
0D 6 San Andreas Local 8-byte string array (see 08)
0E 1+n San Andreas Immediate variable-length string (non null-terminated)
length = ReadInt8(Bytecode, PC)
textLabel = ReadString(Bytecode, PC+1, length)
          + StrPad("\0", 40-length)
PC += 1+length
0F 16 San Andreas Immediate 16-byte string
textLabel = ReadString(Bytecode, PC, 16)
PC += 16
10 2 San Andreas Global 16-byte string variable (see 02)
11 2 San Andreas Local 16-byte string variable (see 03)
12 6 San Andreas Global 16-byte string array (see 07)
13 6 San Andreas Local 16-byte string array (see 08)
Untypified
N/A 8 GTA III Vice City Immediate 8-byte string[*]
textLabel = ReadString(Bytecode, PC, 8)
PC += 8
N/A 128 San Andreas Immediate 128-byte string
string = ReadString(Bytecode, PC, 128)
PC += 128

LCS/VCS

Type codes for Liberty City Stories and Vice City Stories are very different from other games:

  • In some instances, the type code itself denotes the argument value. For example:
    • 0x1 represents the integer value 0
    • 0x2 represents 0.0
  • The type code can sometimes denote a variable.
  • Floating point values are packed (8, 16 or 24 bits as opposed to the more common 32 bits).
Type code
(hex)
Value length
(bytes)
Support Description 
Typified
00 0 Liberty City Stories Vice City Stories End of argument list (EOAL)
01 0 Liberty City Stories Vice City Stories Immediate 8-bit signed integer constant 0
value = 0
02 0 Liberty City Stories Vice City Stories Immediate 8-bit floating-point constant 0.0
value = 0.0
03 1 Liberty City Stories Vice City Stories Immediate 8-bit packed floating-point
value = AsFloat32(ReadUInt8(Bytecode, PC) << 24)
PC += 1
04 2 Liberty City Stories Vice City Stories Immediate 16-bit packed floating-point
value = AsFloat32((ReadUInt8(Bytecode, PC) << 16)
                | (ReadUInt8(Bytecode, PC+1) << 24))
PC += 2
05 3 Liberty City Stories Vice City Stories Immediate 24-bit packed floating-point
value = AsFloat32((ReadUInt8(Bytecode, PC) << 8)
                | (ReadUInt8(Bytecode, PC+1) << 16)
                | (ReadUInt8(Bytecode, PC+2) << 24))
PC += 3
06 4 Liberty City Stories Vice City Stories Immediate 32-bit signed integer
value = ReadInt32(Bytecode, PC)
PC += 4
07 1 Liberty City Stories Vice City Stories Immediate 8-bit signed integer
value = SignExtend32(ReadInt8(Bytecode, PC))
PC += 1
08 2 Liberty City Stories Vice City Stories Immediate 16-bit signed integer
value = SignExtend32(ReadInt16(Bytecode, PC))
PC += 2
09 4 Liberty City Stories Vice City Stories Immediate 32-bit floating-point
value = ReadFloat32(Bytecode, PC)
PC += 4
0A n+NUL Vice City Stories Immediate null-terminated string[*]
textLabel = ReadString(Bytecode, PC)
PC += StrLen(textLabel)+1
Typified (script variables)
0A..0B 1 Liberty City Stories Local timers (TIMERA, TIMERB)
localVar = ReadUInt8(Bytecode, PC) + 0x5E
PC += 1
0B..0C 1 Vice City Stories Local timers (TIMERA, TIMERB)
localVar = ReadUInt8(Bytecode, PC) + 0x5D
PC += 1
0C..6B 1 Liberty City Stories Local integer/floating-point variable
localVar = ReadUInt8(Bytecode, PC) - 0x0C
PC += 1
0D..6C 1 Vice City Stories Local integer/floating-point variable
localVar = ReadUInt8(Bytecode, PC) - 0x0D
PC += 1
6C..CB 3 Liberty City Stories Local integer/floating-point array
localVar = ReadUInt8(Bytecode, PC) - 0x6C
arrayIndexVar = ReadUInt8(Bytecode, PC+1)
arraySize = ReadUInt8(Bytecode, PC+2)
PC += 3
6D..CC 3 Vice City Stories Local integer/floating-point array
localVar = ReadUInt8(Bytecode, PC) - 0x6D
arrayIndexVar = ReadUInt8(Bytecode, PC+1)
arraySize = ReadUInt8(Bytecode, PC+2)
PC += 3
CC..E5 2 Liberty City Stories Global integer/floating-point variable[*]
globalVar = ByteSwap16(ReadUInt16(Bytecode, PC) - 0xCC)
PC += 2
CD..E5 2 Vice City Stories Global integer/floating-point variable[*]
globalVar = ByteSwap16(ReadUInt16(Bytecode, PC) - 0xCD)
PC += 2
E6..FF 4 Liberty City Stories Vice City Stories Global integer/floating-point array
globalVar = ByteSwap16(ReadUInt16(Bytecode, PC) - 0xE6)
arrayIndexVar = ReadUInt8(Bytecode, PC+2)
arraySize = ReadUInt8(Bytecode, PC+3)
PC += 4
Untipified
N/A 8 Liberty City Stories Immediate 8-byte string
textLabel = ReadString(Bytecode, PC, 8)
PC += 8

^ This type was introduced in VCS due to the presence of string variables.

^ Given the data type range limit the largest global variable in LCS is 6655, in VCS is 6399.

Integer numbers

An integer is a number without a decimal or fractional component.

Size
(bytes)
Range
Signed Name Unsigned Name
1 -128 to 127 INT8, CHAR 0 to 255 UINT8, BYTE
2 -32,768 to 32,767 INT16, SHORT 0 to 65,535 UINT16, WORD, USHORT
4 -2,147,483,648 to 2,147,483,647 INT32, LONG 0 to 4,294,967,295 UINT32, DWORD, ULONG

Floating-point numbers

A floating point number is a number with a decimal component.

Size
(bytes)
Range Name
4 ±1.1754944×10-38 to ±3.4028234×1038 SINGLE, FLOAT

Strings

A string is a piece of text. Strings can include letters, numbers and symbols.

There are two kinds of string:

  • Fixed-length. This is the most common type of string and has been used since GTA 3. The string length is fixed. When compiled these strings occupy a certain number of bytes (8 or 16) even if the text is actually shorter (any unused bytes are filled with null values).
  • Variable-length (SA only). Variable-length strings are encoded as a single byte specifying the length followed by the string character bytes. These strings are not null-terminated. The maximum length depends on the instruction[*]. The longest in the original game is 40 characters.
String value Compiled bytes
"MAIN" 09   4D 41 49 4E 00 00 00 00
"MODDING" 09   4D 4F 44 44 49 4E 47 00
"SAVE_OUR_SOULS!" 0F   53 41 56 45 5F 4F 55 52 5F 53 4F 55 4C 53 21 00
"Variable length string" 0E   16   56 61 72 69 61 62 6C 65 20 6C 65 6E 67 74 68 20 73 74 72 69 6E 67

Arrays

In GTA SA, an array is a static reference to a group of successive variables. There is no actual representation of an array value as a concrete type, they do have type codes. When an "array" is passed as an argument to an instruction, what is actually passed is a specific variable in that array. For this reason, array elements are passed as variables of the type in the array – that is to say, a value from an array of global integer variables would be passed as a global integer variable. The game simply performs the extra step of retrieving the variable from the array before using it in the instruction.

This section describes the format of array accesses in GTA SA. Vice City also has arrays, but in a different format.

// Example: not real code.
// Applies only to GTA: SA.
struct ArrayAccess {
    enum ElementType : uint8_t {
        Int,
        Float,
        String8,
        String16
    };
    
    // Offset of first variable in the array.
    uint16_t startOffset;
    
    // Index being accessed ("array[index]").
    // This can be a local variable ("array[someLocalVar]") or a global variable ("array[someGlobalVar]").
    uint16_t index;
    
    // Array length.
    int8_t length;
    
    // Array element type. 7 bits.
    ElementType type : 7;
    
    // Determines whether the index is a global variable (true) or a local variable (false).
    // Only 1 bit.
    bool globalIndex : 1;
};

The array offset is a variable which should have a value more than or equal to 0 and smaller than the array size. Global and local variables can be used as index variables – the purpose of the globalIndex field is to tell the game which has been used. The first element in the array is at index 0, and the last is at length - 1.

Notes

^ In GTA 3, Vice City and Liberty City Stories, short strings (8 bytes) have no type code. If the first byte of an argument does not fit data type range (0x0 - 0x6 for GTA 3 and VC), it's recognized as the beginning of a string and the remaining 8 bytes of the string are read.

^ Some instructions have a variable number of parameters. One such instruction is 004F that starts a new script and takes a variable number of arguments to allow extra script setup. The game uses the special data type to end such argument lists. The number of input/output parameters an instruction can collect/store at a time (separately) is 16 for GTA 3 and VC and 32 for SA, LCS and VCS. Variadic instructions allows for passing additional arguments as much as the amount of local variables minus timers which is 16 for GTA 3 and VC, 32 for SA and 96 for LCS and VCS.

^ In San Andreas, 05B6 is a special instruction that defines a table. Immediately after the opcode a 128 byte stream of data follows, without a type code.

^ Post.png GTAForums: Post by Seemann describing limits for the long strings in SA

See also