Difference between revisions of "Mission Scripting (Overview)"

From GTAMods Wiki
Jump to navigation Jump to search
(Tools section removed)
(script instruction section moved to Opcode)
Line 48: Line 48:
 
| {{hint|0D 03|opcode}}   {{hint|05|Data type}}   {{hint|BB 00|Parameter value}}||030D: 187||[[030D]]: set_max_progress 187
 
| {{hint|0D 03|opcode}}   {{hint|05|Data type}}   {{hint|BB 00|Parameter value}}||030D: 187||[[030D]]: set_max_progress 187
 
|}
 
|}
 
== Script instructions ==
 
A [[SCM]] file itself is a [[Wikipedia:Bytecode|bytecode]] containing instructions telling to the game what to do. An instruction consist of an opcode and its parameters (if there are any). Sometimes the whole script instruction is called opcode.
 
 
=== Opcode ===
 
{{This|This section deals with the technical information on the opcode format. For the opcodes documentation see [[list of opcodes]]}}
 
Each script instruction is represented by a number called [[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, opcode [[0001]] tells to wait for amount of time, [[0003]] shakes the camera, [[0053]] creates a player, etc.
 
 
This is how an opcode [[0001]] looks in a scm file:
 
{{hint|0100|opcode}} {{hint|04|Data type}} {{hint|00|Parameter value}}
 
 
* First part is the opcode number in a [[Wikipedia:Endianness|little-endian]] format.
 
* Second part is the [[#Data types|data type]]
 
* Third part is a parameter value
 
 
When a mission script is disassembled, opcodes are written in a human-readable format. The example above will look something like this:
 
wait 0
 
This is made for the end-user convenience only. The game does not know what the word ''wait'' means, but it knows what the opcode 0001 is, so when a mission script is assembled the commands are written back in raw byte form.
 
 
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 opcodes. More on this [[Conditional statement|read there]]. 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]].
 
 
After an opcode number the data types and parameter values follow{{Ref|05B6|[*]}}.
 
 
=== Data types ===
 
A parameter's [[data type]] is determined with a single byte written before it{{ref|vcstr|[*]}}. The purpose of it is to tell to the game how much bytes to read next and what kind of data it is.
 
 
{|class="mw-collapsible mw-collapsed wikitable"
 
!Data type<br/>(hex)
 
!Arg.<br/>length
 
!Target<br/>game
 
!Description&nbsp;
 
|-
 
!colspan=6|Typified
 
|-valign="top"
 
|00||0||{{icon|3}} {{icon|vc}} {{icon|sa}}||End of argument list (EOAL, [[004F]] or [[0913]] and similar){{ref|partype0|[*]}}
 
|-valign="top"
 
|01||4||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 32-bit signed int
 
<source lang="cpp">scriptParam.m_iIntValue = *(int *)m_pScriptPC;
 
m_pScriptPC += 4;</source>
 
|-valign="top"
 
|02||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Global integer/floating-point variable
 
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|03||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Local integer/floating-point variable
 
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|04||1||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 8-bit signed int
 
<source lang="cpp">scriptParam.m_iIntValue = *(char *)m_pScriptPC++;</source>
 
|-valign="top"
 
|05||2||{{icon|3}} {{icon|vc}} {{icon|sa}}||Immediate 16-bit signed int
 
<source lang="cpp">scriptParam.m_iIntValue = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|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;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|06||4||align="right"|{{icon|vc}} {{icon|sa}}||Immediate 32-bit floating-point
 
<source lang="cpp">scriptParam.m_fFloatValue = *(float *)m_pScriptPC;
 
m_pScriptPC += 4;</source>
 
|-valign="top"
 
|07||6||align="right"|{{icon|sa}}||Global integer/floating-point array{{ref|varray|[*]}}
 
<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"
 
|08||6||align="right"|{{icon|sa}}||Local integer/floating-point array{{ref|varray|[*]}}
 
<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"
 
|09||8||align="right"|{{icon|sa}}||Immediate 8-byte string{{ref|str8|[*]}}
 
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
 
m_pScriptPC += 8;</source>
 
|-valign="top"
 
|0A||2||align="right"|{{icon|sa}}||Global 8-byte string variable
 
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|0B||2||align="right"|{{icon|sa}}||Local 8-byte string variable
 
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|0C||6||align="right"|{{icon|sa}}||Global 8-byte string array{{ref|varray|[*]}}
 
<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"
 
|0D||6||align="right"|{{icon|sa}}||Local 8-byte string array{{ref|varray|[*]}}
 
<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"
 
|0E||1 + (n - 1)||align="right"|{{icon|sa}}||Immediate variable-length string{{ref|str16|[*]}} (non null-terminated)
 
<source lang="cpp">char cStrLength = *(char *)m_pScriptPC++;
 
strncpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC, cStrLength);
 
memset(&scriptParam.m_szTextLabel[cStrLength], '\0', ucMaxLength - cStrLength);
 
m_pScriptPC += cStrLength;</source>
 
|-valign="top"
 
|0F||16||align="right"|{{icon|sa}}||Immediate 16-byte string{{ref|sa16|[*]}}
 
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
 
m_pScriptPC += 16;</source>
 
|-valign="top"
 
|10||2||align="right"|{{icon|sa}}||Global 16-byte string variable
 
<source lang="cpp">scriptParam.m_usGlobalOffset = *(unsigned short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|11||2||align="right"|{{icon|sa}}||Local 16-byte string variable
 
<source lang="cpp">scriptParam.m_sLocalVar = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|12||6||align="right"|{{icon|sa}}||Global 16-byte string array{{ref|varray|[*]}}
 
<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"
 
|13||6||align="right"|{{icon|sa}}||Local 16-byte string array{{ref|varray|[*]}}
 
<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
 
|-valign="top"
 
|N/A||8||{{icon|3}} {{icon|vc}}||Immediate 8-byte string{{ref|vcstr|[*]}}
 
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
 
m_pScriptPC += 8;</source>
 
|-valign="top"
 
|N/A||128||align="right"|{{icon|sa}}||Immediate 128-byte string
 
<source lang="cpp">strcpy(scriptParam.m_szString, (char *)m_pScriptPC);
 
m_pScriptPC += 128;</source>
 
|}
 
 
As it might be seen from the table two bytes <code>{{hint|02 00|Little-endian order}}</code> could have 3 different meanings as a parameter: if it's preceeded by a data type of 02 it is a global variable (<span style="color:blue">$2</span>), data type of 03 - local variable (<span style="color:blue">2@</span>), data type of 05 - 16-bit number (<span style="color:maroon">2</span>), so only the data type allows the game to determine the correct parameter meaning.
 
 
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.
 
 
{|class="mw-collapsible mw-collapsed wikitable"
 
!Data type<br/>(hex)
 
!Arg.<br/>length
 
!Target<br/>game
 
!Description&nbsp;
 
|-
 
!colspan=5|Typified
 
|-valign="top"
 
|00||0||align="center"|{{icon|lcs}} {{icon|vcs}}||End of argument list (EOAL)
 
|-valign="top"
 
|01||0||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit signed integer constant 0
 
<source lang="cpp">scriptParam.m_iIntValue = 0;</source>
 
|-valign="top"
 
|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>
 
|-valign="top"
 
|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;
 
scriptParam.m_fFloatValue = *(float *)&uiUnpackedFloat;</source>
 
|-valign="top"
 
|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;
 
scriptParam.m_fFloatValue = *(float *)&uiUnpackedFloat;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|05||3||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 24-bit packed floating-point
 
<source lang="cpp">unsigned int uiUnpackedFloat
 
    = (*(unsigned short *)m_pScriptPC << 16)
 
    | (*(unsigned char *)(m_pScriptPC + 2) << 8);
 
scriptParam.m_fFloatValue = *(float *)&uiUnpackedFloat;
 
m_pScriptPC += 3;</source>
 
|-valign="top"
 
|06||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 32-bit signed integer
 
<source lang="cpp">scriptParam.m_iIntValue = *(int *)m_pScriptPC;
 
m_pScriptPC += 4;</source>
 
|-valign="top"
 
|07||1||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 8-bit signed integer
 
<source lang="cpp">scriptParam.m_iIntValue = *(char *)m_pScriptPC++;</source>
 
|-valign="top"
 
|08||2||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 16-bit signed integer
 
<source lang="cpp">scriptParam.m_iIntValue = *(short *)m_pScriptPC;
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|09||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Immediate 32-bit floating-point
 
<source lang="cpp">scriptParam.m_fFloatValue = *(float *)m_pScriptPC;
 
m_pScriptPC += 4;</source>
 
|-valign="top"
 
|0A||n + NUL||align="right"|{{icon|vcs}}||Immediate null-terminated string{{ref|strvcs|[*]}}
 
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
 
m_pScriptPC += strlen((char *)m_pScriptPC) + 1;</source>
 
|-
 
!colspan=5|Untypified (script variables)
 
|-valign="top"
 
|T<0C||1||{{icon|lcs}}||Local timers (''TIMERA'', ''TIMERB'')
 
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ + 0x5E;</source>
 
|-valign="top"
 
|T<0D||1||align="right"|{{icon|vcs}}||Local timers (''TIMERA'', ''TIMERB'')
 
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ + 0x5D;</source>
 
|-valign="top"
 
|T<6C||1||{{icon|lcs}}||Local integer/floating-point variable
 
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ - 0x0C;</source>
 
|-valign="top"
 
|T<6D||1||align="right"|{{icon|vcs}}||Local integer/floating-point variable
 
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC++ - 0x0D;</source>
 
|-valign="top"
 
|T<CC||3||{{icon|lcs}}||Local integer/floating-point array
 
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC - 0x6C;
 
scriptParam.m_sArrayIndex = *(unsigned char *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 3);
 
m_pScriptPC += 3;</source>
 
|-valign="top"
 
|T<CD||3||align="right"|{{icon|vcs}}||Local integer/floating-point array
 
<source lang="cpp">scriptParam.m_sLocalVar = *(unsigned char *)m_pScriptPC - 0x6D;
 
scriptParam.m_sArrayIndex = *(unsigned char *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 3);
 
m_pScriptPC += 3;</source>
 
|-valign="top"
 
|rowspan=2|T<E6||rowspan=2|2||{{icon|lcs}}||Global integer/floating-point variable
 
<source lang="cpp">unsigned short usBigEndianWord = *(unsigned short *)m_pScriptPC - 0x00CC;
 
scriptParam.m_sGlobalVar = (short)((usBigEndianWord << 8) | (usBigEndianWord >> 8));
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|align="right"|{{icon|vcs}}||Global integer/floating-point variable
 
<source lang="cpp">unsigned short usBigEndianWord = *(unsigned short *)m_pScriptPC - 0x00CD;
 
scriptParam.m_sGlobalVar = (short)((usBigEndianWord << 8) | (usBigEndianWord >> 8));
 
m_pScriptPC += 2;</source>
 
|-valign="top"
 
|T>=E6||4||align="center"|{{icon|lcs}} {{icon|vcs}}||Global integer/floating-point array
 
<source lang="cpp">unsigned short usBigEndianWord = *(unsigned short *)m_pScriptPC - 0x00E6;
 
scriptParam.m_sGlobalVar = (short)((usBigEndianWord << 8) | (usBigEndianWord >> 8));
 
scriptParam.m_sArrayIndex = *(unsigned char *)(m_pScriptPC + 2);
 
scriptParam.m_ucArraySize = *(unsigned char *)(m_pScriptPC + 3);
 
m_pScriptPC += 4;</source>
 
|-valign="top"
 
|N/A||8||{{icon|lcs}}||Immediate 8-byte string
 
<source lang="cpp">strcpy(scriptParam.m_szTextLabel, (char *)m_pScriptPC);
 
m_pScriptPC += 8;</source>
 
|}
 
 
{{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.
 
 
{{Incomplete}}
 
 
=== Parameters ===
 
The game engine knows amount of parameters for each opcode (1 for [[0001]], 2 for [[0004]], 13 for [[014B]], etc). If the script contains another number of parameter it causes a crash.
 
 
The parameters could be one of following kinds:
 
* Immediate values
 
** integer numbers
 
** floating-point numbers
 
** [[#Strings|short strings]]
 
** {{icon|sa}} [[#Strings|long strings]]
 
* Variables
 
** global variables
 
** local variables
 
* {{icon|sa}} {{icon|lcs}} {{icon|vcs}} [[#Arrays|arrays]]
 
{{Incomplete}}
 
 
==== Strings ====
 
''Strings'' are sequences of characters. 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.
 
 
There are two kinds of strings.
 
 
{{note|str8}} '''Short string''' or a null-terminated string. This is the most common type been used since [[GTA 3]]. The term ''short'' means that the string length is strongly limited. It may contain up to 7 characters and the last one (8th) is a [[Wikipedia:null terminator|null terminator]] byte. When compiled these strings occupy 8 bytes of a [[SCM]] file even if they are actually shorter (the rest of bytes is filled with zero bytes).
 
 
{{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}}
 
!width="250px" align="left"|String
 
!width="250px" align="left"|Equivalent in SCM
 
|-
 
| <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}}
 
|-
 
| <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}}
 
|-
 
| <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}}
 
|}
 
{{note|str16}} '''Long string'''. This type was first introduced in San Andreas. Maximum length depends on the opcode{{Ref|longstringslimits|[*]}} (the longest parameter ever read has got 40 characters).
 
 
{{Incomplete}}
 
 
==== 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'':
 
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 -->
 
 
{{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.
 
 
===== Properties =====
 
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:
 
 
<source lang="cpp" style="margin-top: 8px">enum eArrayElementType
 
{
 
ELEMTYPE_INT,
 
ELEMTYPE_FLOAT,
 
ELEMTYPE_TEXT_LABEL,
 
ELEMTYPE_TEXT_LABEL16
 
};
 
 
struct ArrayProperties
 
{
 
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 ===
 
{{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|partype0}} Some opcodes have variable amount of parameters. Most known opcode 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 opcode 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|05B6}} {{Icon|SA}} Opcode [[05B6]] is a special opcode that defines a table. Immediately after opcode number the stream of data (128 bytes) follows, without a data type.
 
 
{{note|longstringslimits}} {{GTAF|post|261006|3940262|Post by Seemann describing limits for the long strings in SA}}
 
  
 
== Cracking the SCM ==
 
== Cracking the SCM ==

Revision as of 16:12, 5 September 2016

This article handles the scripting technology and background for the GTA 3 game series (including GTA LCS and GTA VCS). It does not cover GTA IV.
For more information about the GTA IV script read the article about the SCO format.
40px-Ambox rewrite orange.svg.png This article may need to be rewritten.
Please help improve this article. The discussion page may contain suggestions.

This article deals with the general overview on the mission scripting in GTA 3D series. Mission scripting is the process of writing scripts: small codes that control many aspects of gameplay. Although most of the game features are hardcoded, still much things could be done via scripting. In fact, every single mission in Grand Theft Auto series comes from the scripts. That is, knowing the format of scripts and having a proper tool, it is possible to change the mission details and even create an absoletely new story plot (although it's considered to be the most complex area in GTA modding, so most often the scripting results in small scripts adding new features in gameplay).

Introduction

The original mission script is looked like this[*] (taken from Vice City debug.sc file):

IF IS_BUTTON_PRESSED PAD2 RIGHTSHOULDER1
AND flag_create_car = 1
AND button_press_flag = 0
	IF IS_CAR_DEAD magic_car
		DELETE_CAR magic_car
	ELSE
		IF NOT IS_PLAYER_IN_CAR player magic_car
			DELETE_CAR magic_car
		ELSE
			MARK_CAR_AS_NO_LONGER_NEEDED magic_car
		ENDIF
	ENDIF 
	flag_create_car = 0
	initial_car_selected = 0
	button_press_flag = 1
ENDIF

Easy to read and understand, it is fairly basic so anyone with an idea of basic coding (or maybe even English) can understand it. However, very little code came with the game like that. The majority of the mission script comes in a file called main.scm (although in San Andreas there are alternate mains and external scripts, but they all follow the same basic format - hex codes). Example, for the code:

IF IS_CAR_DEAD magic_car
	DELETE_CAR magic_car

The equivalent in the main.scm would look something like this:

D6 00 04 00 19 01 02 45 0E 4D 00 01 FE 3D 87 02 A6 00 02 45 0E

This is how the beginning of the San Andreas mission script looks like:

Byte data Decompiled data Decompiled data with description
A4 03   09   4D 41 49 4E 00 00 00 00 03A4: 'MAIN' 03A4: name_thread 'MAIN'
6A 01   04   00   04   00 016A: 0 0 016A: fade 0 time 0
2C 04   05   93 00 042C: 147 042C: set_total_missions_to 147
0D 03   05   BB 00 030D: 187 030D: set_max_progress 187

Cracking the SCM

As has been said, very little of the code was supplied with the game in a decompiled state (only two small files, both test scripts), so how, as asked, do we create our own scripts based on the original? With a decompiler - but how do these work (no decompilers have been provided by Rockstar).

The original SCM format was cracked shortly after the release of GTA 3 (the first game to use this mission coding method), with people having to first figure out what all the sections did (there are 5 segments is an SCM - memory, objects, mission defines, MAIN and missions (GTA SA has more, but only one of these (global variables) has had its use determined), where they started/ended etc, figuring out how many parameters each OpCode had and a lot more. Once this was done, they knew where each OpCode began and ended, so they could split them up to make it more readable, but the data on what each one does was lost in the compiling, so they still only had something that looked like this:

:label035F78
0001: 0?
00D6: 0?
0256: 4??


That doesn't still doesn't mean a lot though, so people had to try figure out what the different OpCodes meant.

(Note: this code is in early Mission Builder format:

:labelxxxxxx means this code was originally at this offset in the mission script (the 'label' is added in by the decompiler)
x? means a one byte number
x?? means a variable stored at this offset from the start

label (i.e. for if we wanted to 'jump' to a label))

Some were easy, the very first line of a decompiled script (besides decompiler headers) looks something like:


The only parameter this command has is a reference to a label, so this is most likely (and in fact is) a jump statement, so we know all 0002s are jumps. Of course, finding what OpCodes do (and in fact finding the original number of parameters took a while to confirm) takes time, you have to have an idea first and then have to test your theory - many OpCodes have still not been named, but with the amount of OpCodes discovered so far, we have a general idea on what the mission script does.

Once the mission script had been cracked, people could write programs to read through it and output it in a form we could understand (based on a format of opcodes, text to say what they do and a list of parameter values - nothing like the original - the opcodes are needed to determine which opcode it is, the describing text is completely ignored). Originally there were two main decompilers, BWME (Barton Waterduck's Mission Editor) and CyQ's disassembler, each with their own compilers (to compile the decompiled code back into an SCM file). BWME quickly became the most commonly used, especially among newer coders, probably due to the fact that the parameters were inter-mixed with the code, so you had something like:

00B1: is_car $car in_cube $lowerx $lowery $lowerz $upperx $uppery $upperz 0

As opposed to the gtaMa/DisAsm format:

is_car_in_cube $car, $lowerx, $lowery, $lowerz, $upperx, $uppery, $upperz, 0

(also note the lack of OpCode in the second example, this builder uses a lookup to find the opcode (if the function is known) instead of just quoting it)

Although you can't see much difference with that example, it can make a lot of difference. Since Barton left the modding community, Seemann created an even more versatile decompiler, the Sanny Builder. It has become the most popular mission builder.

See also

External links