Skip to content
Chris edited this page Jun 16, 2026 · 1 revision

Game scripts are stored in the main.scm file.

This file is an untyped binary file with a defined structure. When it is decompiled, the resulting source file has a layout that follows this structure exactly.

Contents

  1. General structure
    1. Header
    2. Main section
      1. Opcode
      2. Expression
      3. Label
    3. Missions
    4. External scripts

General structure

The structure of the text source is what matters here first of all, since working with the binary compiled file is the decompiler's job.

So, what structure should a typical source file have so that it can be successfully compiled into a working main.scm?

The main.scm file consists of these parts:

header
main
missions

Header

At the very top of the source file, you can see the header.

It is a set of fields that define part of the header structure. Each field starts with the word DEFINE.

The first part of the header is the model segment, or model block. It begins with this line:

DEFINE OBJECTS 389

The word DEFINE tells the compiler that this line is part of the header.

OBJECTS tells the compiler that this line defines the size of the object segment. More precisely, it defines the number of objects whose names will be listed below.

389 is the number of these objects. This number can differ between source files.

Allowed values are positive numbers and zero.

See also: object limits.

After this line, the object names are listed.

DEFINE OBJECT INFO                     // Object number -1

The word OBJECT tells the compiler that this line contains the name of an imported model.

A special feature of these models is that they do not need to be loaded before use. In the script, they are referenced not by their real ID, but by the ordinal number shown in the comment, in this case -1.

For example:

0213: $PICKUP_INFO_HOSPITAL = create_pickup #INFO type 3 at 2027.77 -1420.52 16.49

or:

0213: $PICKUP_INFO_HOSPITAL = create_pickup -1 type 3 at 2027.77 -1420.52 16.49

INFO is the name of the DFF model from gta3.img or another archive.

The first model in the list is not used by the game.

Note that this header segment is optional and does not have to be specified.

If you do not specify which models should be compiled as imported models, the compiler will do this for you when it detects model names used in the script that are not present in IDE files.

Models from IDE files can be used by their ID, so they do not need to be specified in the header.

See also: objects in SA.

The second part of the header is the mission segment. It begins with this line:

DEFINE MISSIONS 135

This specifies how many missions will be present in this source file.

The compiler will look for exactly this number of missions. If there are fewer or more, an error will occur.

After specifying the total number of missions, the missions are listed:

DEFINE MISSION 0 AT @INITIAL           // Initial 1

Breaking down this line:

After the word DEFINE, the word MISSION tells the compiler that this line defines the beginning of a mission.

After it comes the mission's ordinal number. This number is used by the start_mission opcode.

After the word AT, the mission label must be specified. This label is where the mission starts.

This line can also contain a comment with the mission name from the original main.scm.

Display of original mission names can be disabled in the program options.

See also: creating a mission in SA.

The header parts described above are common to all games supported by Sanny Builder.

The following header parts are specified only for San Andreas.

DEFINE EXTERNAL_SCRIPTS 78 // Use -1 in order not to compile AAA script

This part of the header defines external scripts.

After the word EXTERNAL_SCRIPTS, the total number of external scripts is specified.

In the source file, these scripts must be located immediately after all missions.

As with missions, the compiler will look for the specified number of scripts.

Note that external scripts include a special script named AAA.

The contents of this script are filled in by the compiler. This is done only for the purpose of matching the original game files as closely as possible.

If EXTERNAL_SCRIPTS is set to 0, the AAA script will still be compiled.

To prevent this, specify -1 as the value. In that case, script.img will not be created at all.

Version 3.0 also allows the AAA script to be overwritten. To do that, create your own script and give it this name.

An external script is defined with a line like this:

DEFINE SCRIPT PLAYER_PARACHUTE AT @PLCHUTE // 0

Here, PLAYER_PARACHUTE is the script name in script.img.

@PLCHUTE is the name of the label where the script body starts.

The comment contains the script's ordinal number, which is used by opcodes, for example:

08A9: load_external_script 0

After the external script segment, there are two more blocks whose purpose is unknown.

Most likely, the game does not use these values.

DEFINE UNKNOWN_EMPTY_SEGMENT 0

DEFINE UNKNOWN_THREADS_MEMORY 574

If you are using a stripped script, such as stripped.txt, without missions and scripts, you do not need to specify the header at all.

The header template can be inserted in the editor with the headsa or headvc macro.

Main section

Immediately after the header comes the main block of the script, called the main.

This is where all threads that are created and run during the game are located.

The beginning of the main section is marked by the comment:

//-------------MAIN---------------.

The main section consists of a set of separate threads.

The very first thread, which is automatically started by the game, is the MAIN thread. It starts with this opcode:

03A4: name_thread 'MAIN'

All other threads are activated from this thread with opcode 004F or 00D7.

Note that these opcodes are essentially the same, except 004F can also pass parameters into the thread being created.

This note about creating threads from MAIN applies only to a new game.

When loading a saved game, the game restores previously created threads by itself.

Threads consist of opcodes, expressions, and labels.

During compilation, the script may also contain constructions such as:

VAR..END
CONST..END
FOR..END

and so on.

It may also contain directives. These exist only to make working with the code more convenient.

See also: coding in Sanny Builder 3.

After compilation, these constructions are converted in one way or another into opcodes, expressions, or labels.

Opcode

An opcode, or Operation Code, is a number that represents one of the instructions for the game's script parser.

It tells the parser which specific sequence of actions must be executed, taking the passed parameters into account.

For example, 0001 is the operation code for wait. It tells the engine to pause execution of the thread for the number of milliseconds passed as the parameter of that opcode.

All opcodes are stored in the INI file.

Expression

An expression is an opcode with a mathematical or logical operation.

Examples:

$var += 1
10 < 5@
&10 = 0

and so on.

Some expressions can be written without specifying the opcode number, as shown in the examples above.

Logical expressions, or conditions, are used to check values.

Label

A label is an identifier that marks a position in the script.

The label name must be unique.

In essence, it is a number that represents the offset from the beginning of the main section or mission to the place where the label is located.

In code, it is written with the : symbol:

:MAIN_177

Labels are useless by themselves if nothing uses them.

Using a label means jumping to it with one of the special opcodes.

There are two kinds of jumps to a label:

conditional
unconditional

An unconditional jump is performed with opcodes such as jump and gosub.

jump @MAIN_177

Note that a label name used for a jump begins with the @ symbol.

When the automatic list is enabled, the editor will show a list of labels used in the code.

When the game reaches this opcode, it immediately jumps to the place in the script where the label is located:

:MAIN_177

The special feature of the gosub command is that after jumping to the label, the game can, and must, return back after it reaches the return command.

...
gosub @MAIN_177
$var += 1
...

:MAIN_177
$var = 1
return

When the game reaches the gosub command, it immediately jumps to the specified label, to this command:

$var = 1

After executing this command, it goes to the return command.

The meaning of return is:

return to the place where the last gosub was called

In this example, it returns to this command:

$var += 1

The commands jump, or goto, gosub, and return are basic concepts in any programming language.

A conditional jump, unlike an unconditional jump, is performed only when a certain condition is met.

For this, the jump_if_false, or jf, opcode is used.

It is part of every condition in scripts.

00D6: if
08AB:   external_script 75 loaded
004D: jump_if_false @MAIN_4492
0913: run_external_script 75

In this example, the script first checks whether external script 75 is loaded into memory.

After opcode 08AB, the condition state will be either true or false, depending on whether the script is loaded.

The condition state determines two different game behaviors after the check.

If the condition is true, meaning the script is loaded, the game skips the jump_if_false opcode.

The next step is therefore opcode 0913.

If the condition is false, meaning the script is not loaded, the game executes the jump_if_false opcode and jumps to the label:

@MAIN_4492

The meaning is visible from the words jump_if_false: jump if false.

The main section ends where the missions begin.

Missions

The mission block starts with the label of mission zero from the header. In the original, this is:

:INITIAL

Usually there is a comment there:

//-------------Mission 0---------------.

A special feature of missions is that only one mission can be running in the game at the same time.

After the start_mission command, the game fully loads the mission body into a separate memory area. After that, it works with it as with a normal thread from the main section.

Everything said about opcodes, expressions, and labels in the main section is also true for missions.

A mission, like a normal thread, ends with the end_thread command.

All missions in the source file must appear in the same order as in the file header.

A mission must be one solid block. It is not allowed to place part of one mission inside the body of another.

External scripts

In San Andreas, some game scripts are moved into a separate file named script.img.

It consists of a set of small SCM files.

Their difference from the main main.scm file is that they do not have a header.

When decompiled, these scripts are added to the end of the source file, forming its fourth part.

Therefore, the beginning of this block is the end of the mission block.

The label of the first external script should be there, if external scripts are used.

Usually there is a comment there:

//-------------External script 0 (PLAYER_PARACHUTE)---------------.

An external script is a normal thread with its own opcodes, expressions, and labels.

This thread is loaded into a separate memory area.

Clone this wiki locally