event_script: event script modules
- Source
docs/formats/event_script.md- SHA-256
e0767c10…0a0036a9
Where modules are · Header · Scripts · Variables · The unk_0x30_offset lists · Code · Decoded data · Rebuilding · How the game reads it · Leads
A compiled event script: one or more scripts of stack-machine bytecode, each with its functions and jump targets, plus the module's integer and float constants, variables and the lists of functions its opcodes start on other scripts. The script VM in the executable runs it (Event scripts). Modules sit as pakd entries in event/scene/*.dat and event/event_data.dat; they have no text magic, only a header word 0x8000000C. The layout is Gibbed's (Gibbed.TacticsOgre.ScriptFormats, written for the PSP release), which every module fits. Terms are as in the glossary.
| Based on | Every script module in event/, in event/scene/*.dat and event/event_data.dat; Gibbed's ScriptFileHeader.cs, ScriptHeader.cs, FunctionHeader.cs, VariableHeader.cs, VariableFlags.cs, RequestHeader.cs and ScriptFile.cs in reference_tools/Gibbed.LetUsClingTogether/projects/Gibbed.TacticsOgre.ScriptFormats/ (checked 2026-10-11); the unpacked executable, SHA-256 7b0230a11498f40396c9048345e7cfe0ef442cb7ca8faffc6f197920d2669599. |
| Checked | 2026-10-12 |
| Method | Single-file C# programs over the decrypted files (with the toolchain's Containers library): find every pakd entry starting 0x8000000C, check the header fields Gibbed requires, walk every script's code with Gibbed's instruction encoding; static analysis with Ghidra 12.1.4 of the loader (0x14034e780, 0x14034e9d0) and the interpreter (0x14034a1b0); the R31 review of names (2026-10-11). 2026-10-12 (R19): the round trip, tor round-trip local/game/Data/event --docs docs, decoded all 1,656 modules in 826 files with the layouts and opcode sizes below and rebuilt them from their rows (Rebuilding) byte for byte (826/826 files), with no decode problem reported; tor export and tor read-back over the same folder (826/826 same as the original); DuckDB over the decoded data (data/game/event/**/*.event_script/). 2026-10-13 (R36): the round trip again, tor round-trip local/game/Data/event local/game/Data/WarrenReport --docs docs --quiet, 1,656/1,656 modules byte-identical; the decode finds a module by its first word, 0x8000000C, and refuses one whose total_size doesn't fit its entry, whose table offsets aren't inside total_size, or whose scripts' code doesn't walk with the Opcodes sizes from code_offset for instruction_count instructions. DuckDB over the exported data (each module's header.jsonl and scripts.jsonl, each file's entries.jsonl, and the modules' bytes in local/bin/event/, all from tor export) for the facts that cite it. |
| Name from | Ours: the event code (region event/eventdata.cpp, 0x14034faa0–0x14035b880) hands a scene file's module to the bytecode interpreter (0x1402d0600 → 0x14034e780); "event" from the game's source path Event\EventData.cpp and folder Data/event/, "script" the plain word for interpreted bytecode. |
Where modules are
- confirmed: modules are in many of the
event/scene/*.datfiles, one or more in each, and inevent/event_data.dat(#9and#13). In the scene files they are the first entries of the top-levelpakd, before any other kind; inevent_data.datthey come after other entries. Source: DuckDB over each file'sentries.jsonl(R36). - confirmed: each module's entry is its
total_sizeplus 16 zero bytes, thepakdentry padding (containers.md). Source: DuckDB (R36): every module'stotal_sizeis its entry'ssize− 16, and the 16 bytes after it are zero. - likely: the event code hands a scene's module to VM context 0 (
0x1402d0600→0x14034e780,0x14034e9d0). Source: static analysis.
Header
0x64 bytes. Offsets are from the start of the module, and every _offset field is an offset from the start of the module.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 4 | u32 | magic |
0x8000000C in every module. The game doesn't read it. |
likely | The round trip (R36: the decode finds every module by it); not read by 0x14034e780 or 0x14034e9d0. Gibbed splits it into SignatureValue and ExpectedVersion 12. |
| 0x04 | 4 | u32 | total_size |
The module's size; a multiple of 16 in every module. | confirmed | DuckDB (R36): its entry's size − 16 in every module. |
| 0x08 | 4 | u32 | author_offset |
NUL-terminated name, by its look of the script's author (isobe, tatsuchi, mtanaka, …; empty in some). |
likely | DuckDB over the modules' bytes (R36). Gibbed AuthorNameOffset (Shift-JIS). |
| 0x0C | 4 | u32 | source_offset |
NUL-terminated source file name, unique per module (1_0.src, …). |
likely | DuckDB over the modules' bytes (R36). Gibbed SourceNameOffset. |
| 0x10 | 4 | u32 | unk_0x10_offset |
Points at the string 0 in every module; never read. |
— | DuckDB over the modules' bytes (R36). Gibbed DateOffset. |
| 0x14 | 4 | u32 | unk_0x14_offset |
A u16 count, then u16s; the loader sums them into the context at +0x14. What the sum is isn't shown. |
— | 0x14034e780. Gibbed ScriptCountTableOffset. |
| 0x18 | 4 | u32 | script_table_offset |
Scripts: an s32 count, then 0x30-byte script records. One script in most modules, more in the rest. |
likely | The round trip (R36: the decode walks every record and its code); DuckDB over scripts.jsonl (R36). Gibbed ScriptTableOffset. |
| 0x1C | 4 | u32 | unk_0x1C |
0 in every module. | — | DuckDB over header.jsonl (R36). Gibbed requires 0. |
| 0x20 | 4 | u32 | int_table_offset |
An s32 count, then s32 constants that push_int_table (78) pushes. |
likely | 0x14034e780 keeps it at context +0x58; 0x14034a1b0 case 0x4E. Gibbed IntTableOffset. |
| 0x24 | 4 | u32 | float_table_offset |
An s32 count, then f32 constants that push_float_table (80) pushes. |
likely | 0x14034e780 (context +0x60); case 0x50. Gibbed FloatTableOffset. |
| 0x28 | 4 | u32 | variable_table_offset |
Variables: an s32 count, then 8-byte records. | likely | 0x14034e780 (context +0x68), read by 0x14034d500. Gibbed VariableTableOffset. |
| 0x2C | 4 | u32 | unk_0x2C_offset |
A u32 count, then 12-byte records that opcodes 76 and 77 read; the count is 0 in every module. | likely | 0x14034a1b0 (cases 0x4C, 0x4D, through context +0x70); DuckDB over the modules' bytes (R36). Gibbed Unknown2COffset (count must be 0); FF12's "tag". |
| 0x30 | 4 | u32 | unk_0x30_offset |
Lists of functions to start: an s32 count, then u32 offsets; 0–17 lists per module. | likely | 0x14034a1b0 cases 0x5F, 0x61, 0x63. Gibbed RequestTablesOffset. |
| 0x34 | 4 | u32 | module_vars_size |
Size of the module's variable storage: 0 in most modules, else 4–124. | likely | DuckDB over header.jsonl (R36). Gibbed FrameDataSize. |
| 0x38 | 4 | u32 | module_vars_offset |
The module's own variable storage (scope 1, module): the context points at it unless the caller gave one. |
likely | 0x14034e9d0 (context +0x30). Gibbed FrameDataOffset. |
| 0x3C | 4 | u32 | unk_0x3C |
0 in every module. | — | DuckDB over header.jsonl (R36). Gibbed requires 0. |
| 0x40 | 4 | u32 | unk_0x40_offset |
Storage of scope-3 variables. Gibbed: module vars offset + 0, 0x40 or 0x80 by module_vars_size. |
— | 0x14034d500 (scope 3), 0x14034e9d0. Gibbed Unknown40Offset. |
| 0x44 | 4 | u32 | unk_0x44_offset |
Unknown. 0 in most modules; in the rest, often a value inside the header, so not always an offset. | — | DuckDB over header.jsonl (R36). Gibbed Unknown44Offset. |
| 0x48 | 4 | u32 | unk_0x48_offset |
Unknown; the loader keeps it at context +0x40. |
— | 0x14034e780. Gibbed Unknown48Offset. |
| 0x4C | 4 | u32 | string_table_offset |
Start of the script and function names; the last region in every module. | likely | DuckDB over header.jsonl (R36: no other offset is past it). Gibbed StringTableOffset. |
| 0x50 | 2 | s16 | autostart_script |
−1 in every module. When 0 or more, the loader starts that script's functions listed at autostart_offset. |
likely | 0x14034e9d0; DuckDB over header.jsonl (R36). Gibbed requires −1. |
| 0x52 | 2 | s16 | unk_0x52 |
0 in every module. | — | DuckDB over header.jsonl (R36). Gibbed requires 0. |
| 0x54 | 4 | u32 | autostart_offset |
A u16 count, then s16 function indexes to start (−1 skipped). | likely | 0x14034e9d0. Gibbed Unknown54Offset. |
| 0x58 | 4 | u32 | unk_0x58 |
0 in every module. | — | DuckDB over header.jsonl (R36). Gibbed requires 0. |
| 0x5C | 4 | u32 | unk_0x5C_offset |
Points at two u32s, 1 and 0, in every module. | — | DuckDB over the modules' bytes (R36). Gibbed requires 1, 0. |
| 0x60 | 4 | f32 | unk_0x60 |
0 in every module. | — | DuckDB over header.jsonl (R36). Gibbed requires 0. |
- confirmed: every module passes Gibbed's header checks (the fields above it requires to be constant, and
unk_0x40_offsetasScriptFileHeaderworks it out frommodule_vars_size). Source: DuckDB overheader.jsonland the modules' bytes (R36). - confirmed: in every module, the script table, the variable table and the
unk_0x30_offsetlists lie insidetotal_sizewhere their offsets and counts say, and every script'sinstruction_countinstructions walk with the Opcodes sizes. Source: the R19 decode and round trip, all 1,656 (see Method). A round trip passes with any split of a record into fields, so the fields' meanings keep their confidence. - confirmed: the regions the offsets point at come, in most modules, in this order:
unk_0x44(0 in most),unk_0x10, author, source,unk_0x14, module vars,unk_0x40,unk_0x5C, ints, floats, variables, scripts,unk_0x2C, autostart,unk_0x30,unk_0x48, strings. Source: DuckDB overheader.jsonl(R36: each offset no greater than the next).
Scripts
0x30 bytes each, after the table's s32 count.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 4 | u32 | name_offset |
The script's name, from string_table_offset. |
likely | DuckDB over scripts.jsonl (R36: every script's _name_offset is a name). Gibbed ScriptHeader. |
| 0x04 | 2 | u16 | table_index |
Unknown; no reader found. | guess | Gibbed TableIndex. |
| 0x06 | 2 | u16 | unk_0x06 |
255. | — | DuckDB over scripts.jsonl (R36). Gibbed requires 255. |
| 0x08 | 4 | u32 | code_offset |
The script's bytecode. | likely | The round trip (R36: the decode walks every script from it). Gibbed. |
| 0x0C | 4 | s32 | instruction_count |
Number of instructions (not bytes). | likely | The round trip (R36: the decode walks that many). Gibbed code_count. |
| 0x10 | 4 | u32 | function_table_offset |
An s32 count, then {u32 name_offset, u32 code_offset} per function; code offsets are from the script's code start. |
likely | DuckDB over the modules' bytes (R36: every function's name_offset, from string_table_offset, points at a name). Gibbed FunctionHeader. |
| 0x14 | 4 | u32 | jump_table_offset |
An s32 count, then u32 code offsets that the jump opcodes index. | likely | 0x14046c3b0 (jump target by index; cases 0x51–0x57, 0x5A–0x5C, 0x60). |
| 0x18 | 4 | u32 | unk_0x18_offset |
A u16 count, then s16s, −1 or a function index; no reader found. | — | Gibbed EventTableOffset (one function per "event slot"). |
| 0x1C | 4 | u32 | unk_0x1C_offset |
Unknown. | — | Gibbed FrameOffset. |
| 0x20 | 2 | u16 | unk_0x20 |
0. | — | Gibbed requires 0. |
| 0x22 | 2 | u16 | unk_0x22 |
Unknown. | — | Gibbed Index. |
| 0x24 | 12 | raw[12] | unk_0x24 |
0. | — | Gibbed requires 0. |
Variables
8 bytes each, after the table's s32 count.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 4 | u32 | location |
Bits 0–23: byte offset of the variable in its scope's storage (for an array, the offset in the module of its array record). Bits 24–26: the scope, below. Bit 27: pushed as a float. Bits 28–31: the type, 0 byte, 1 signed byte, 2 short, 3 unsigned short, 4 int, 5 float. | likely | 0x14034d500. Gibbed VariableFlags, VariableType, StackType. |
| 0x04 | 4 | u32 | init_offset |
0, or the offset of the variable's initial value: {u16 script index, u16, u32 size, data}, which the loader copies into the variable's storage. |
likely | 0x14034e9d0 (loops over the variable table). Gibbed VariableHeader.Unknown. |
Scope
Bits 24–26 of location: where the variable's storage is (0x14034d500, 0x14034e780).
| Value | Meaning | Notes |
|---|---|---|
| 0 | context | likely: a context field (+0x38). Gibbed Global, but scopes 4 and 5 are the global blocks. |
| 1 | module | likely: the module's variable storage (module_vars_offset, context +0x30). Gibbed File. |
| 2 | script | likely: storage from a callback, per script. Gibbed Script. |
| 3 | unk_3 | the region at unk_0x40_offset |
| 4 | block_f2c690 | the global block at 0x140F2C690 |
| 5 | block_f2c890 | the global block at 0x140F2C890 |
| 7 | array | likely: an array record {u32 location, s32 rank, s32 length[2]} at the offset, indexed by values popped from the stack. Gibbed Array. |
The unk_0x30_offset lists
At unk_0x30_offset: an s32 count of lists, then each list's u32 offset. Each list is a u32 count, then its items, below: the functions opcodes 95, 97 and 99 start on other scripts' threads (0x14034de80, 0x14034df10, not read). Likely; Gibbed RequestHeader.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 2 | s16 | script_index |
A script of this module, by its place in the script table. | likely | DuckDB over the decoded data: every item names a script the module has (R19). Gibbed RequestHeader. |
| 0x02 | 2 | s16 | function_index |
A function of that script, by its place in its function table; 1–23 in the data. | guess | Gibbed RequestHeader; DuckDB (R19). |
Code
- likely: an instruction is an opcode byte; opcodes
0x47(71) and up are followed by a u16 operand, so they take 3 bytes, and the rest take 1. Source:0x14034a1b0(reads the operand when the opcode is above0x46); the size field of each opcode's record (exe.script_opcodes); GibbedOpcodeHelpers.HasImmediate. confirmed: walking every script'sinstruction_countinstructions with this rule finds only opcodes 0–99, in every module (the round trip, R36: the decode walks with the Opcodes sizes, which are this rule's for every opcode a script uses, and refuses an opcode not in it). - likely: each opcode's name and what its case does are in Event scripts; the numbers are Gibbed's (
reference_tools/Gibbed.LetUsClingTogether/docs/cutscene_scripting/opcodes.md). Its size and operand are below. - likely: a call opcode's operand is a native's number (natives); a jump's is an index into the script's jump table; a variable opcode's is an index into the variable table;
push_int_tableandpush_float_tableindex the int and float tables;push_int_immediatecarries the value. Source:0x14034a1b0,0x14034d500,0x14046c3b0.
Opcodes
Size counts the opcode byte and its operand, as the interpreter steps by it: the size of each opcode's record at 0x140C9E300 (exe.script_opcodes). Operand names the u16 that follows (Gibbed's operand_kind in the same records, Event scripts); — for none. Opcodes 0–2 have size 0 and 71 has size 1 though the interpreter reads a u16 operand for every opcode above 0x46; no script uses any of them (the R19 decode). Confidence: likely, from the records; the round trip shows only that every script walks with these sizes.
| Opcode | Size | Operand | Type |
|---|---|---|---|
| 0 | 0 | — | — |
| 1 | 0 | — | — |
| 2 | 0 | — | — |
| 3 | 1 | — | — |
| 4 | 1 | — | — |
| 5 | 1 | — | — |
| 6 | 1 | — | — |
| 7 | 1 | — | — |
| 8 | 1 | — | — |
| 9 | 1 | — | — |
| 10 | 1 | — | — |
| 11 | 1 | — | — |
| 12 | 1 | — | — |
| 13 | 1 | — | — |
| 14 | 1 | — | — |
| 15 | 1 | — | — |
| 16 | 1 | — | — |
| 17 | 1 | — | — |
| 18 | 1 | — | — |
| 19 | 1 | — | — |
| 20 | 1 | — | — |
| 21 | 1 | — | — |
| 22 | 1 | — | — |
| 23 | 1 | — | — |
| 24 | 1 | — | — |
| 25 | 1 | — | — |
| 26 | 1 | — | — |
| 27 | 1 | — | — |
| 28 | 1 | — | — |
| 29 | 1 | — | — |
| 30 | 1 | — | — |
| 31 | 1 | — | — |
| 32 | 1 | — | — |
| 33 | 1 | — | — |
| 34 | 1 | — | — |
| 35 | 1 | — | — |
| 36 | 1 | — | — |
| 37 | 1 | — | — |
| 38 | 1 | — | — |
| 39 | 1 | — | — |
| 40 | 1 | — | — |
| 41 | 1 | — | — |
| 42 | 1 | — | — |
| 43 | 1 | — | — |
| 44 | 1 | — | — |
| 45 | 1 | — | — |
| 46 | 1 | — | — |
| 47 | 1 | — | — |
| 48 | 1 | — | — |
| 49 | 1 | — | — |
| 50 | 1 | — | — |
| 51 | 1 | — | — |
| 52 | 1 | — | — |
| 53 | 1 | — | — |
| 54 | 1 | — | — |
| 55 | 1 | — | — |
| 56 | 1 | — | — |
| 57 | 1 | — | — |
| 58 | 1 | — | — |
| 59 | 1 | — | — |
| 60 | 1 | — | — |
| 61 | 1 | — | — |
| 62 | 1 | — | — |
| 63 | 1 | — | — |
| 64 | 1 | — | — |
| 65 | 1 | — | — |
| 66 | 1 | — | — |
| 67 | 1 | — | — |
| 68 | 1 | — | — |
| 69 | 1 | — | — |
| 70 | 1 | — | — |
| 71 | 1 | — | — |
| 72 | 3 | variable |
u16 |
| 73 | 3 | variable |
u16 |
| 74 | 3 | variable |
u16 |
| 75 | 3 | variable |
u16 |
| 76 | 3 | tag_index |
u16 |
| 77 | 3 | tag_index |
u16 |
| 78 | 3 | int_index |
u16 |
| 79 | 3 | value |
s16 |
| 80 | 3 | float_index |
u16 |
| 81 | 3 | jump |
u16 |
| 82 | 3 | jump |
u16 |
| 83 | 3 | jump |
u16 |
| 84 | 3 | jump |
u16 |
| 85 | 3 | jump |
u16 |
| 86 | 3 | jump |
u16 |
| 87 | 3 | jump |
u16 |
| 88 | 3 | native |
u16 |
| 89 | 3 | native |
u16 |
| 90 | 3 | jump |
u16 |
| 91 | 3 | jump |
u16 |
| 92 | 3 | jump |
u16 |
| 93 | 3 | native |
u16 |
| 94 | 3 | native |
u16 |
| 95 | 3 | list |
u16 |
| 96 | 3 | jump |
u16 |
| 97 | 3 | list |
u16 |
| 98 | 3 | unk_operand |
u16 |
| 99 | 3 | list |
u16 |
Operands: variable an index into the variable table; tag_index into the unk_0x2C_offset table; int_index and float_index into the int and float tables; value the signed value pushed; jump an index into the script's jump table; native a native's number; list an index into the unk_0x30_offset lists; unk_operand read by no code (98's record has operand_kind 0).
Decoded data
tor export writes each module as a folder <n>.event_script/ with one .jsonl per section (toolchain/README.md (../../toolchain/README.md#decoded-data)):
| File | Rows |
|---|---|
header.jsonl |
One: every Header field. |
scripts.jsonl |
One per script record; _name_offset is the script's name. |
variables.jsonl |
One per variable record. |
unk_0x30_offset.jsonl |
One per item of the lists: list (which list), then the item's fields. |
code.jsonl |
One per instruction: script, step (from 0 in each script), opcode with its name in _opcode (Event scripts), then its operand, named as in Opcodes; a native's operand native has its name in _native, from names.jsonl (../../data/exe/names.jsonl) (null for natives 44, 162 and 166, which have no handler of their own). |
The other regions (the int, float and string tables, the function and jump tables, unk_0x14, unk_0x40, unk_0x44, unk_0x48, unk_0x5C, the module variables and the variables' initial values) aren't decoded yet; they are kept as they are (Rebuilding) and are in the entry's .bin.
Rebuilding
A module is rebuilt in place: its original bytes, with each decoded section's records written over their own bytes. confirmed for unchanged rows: all 1,656 modules rebuild byte for byte from their rows (see Method).
header: every Header field, at0x00–0x63.scripts,variablesand the list items: each row's fields over its record, in order; a section keeps its row count (a list, its item count).code: each script's instructions, in order, from its first instruction's offset: the opcode byte, then the operand as its type. A script keeps its instruction count and its code's length in bytes, so an opcode may change only to one of the same size.- Every other byte is kept, the
pakdpadding aftertotal_sizeincluded.
Rows that would add, remove or move a record or an instruction, or change a script's code length, are refused with an error: that needs the regions after it moved and every offset into them rewritten, which the toolchain doesn't do yet (lead below). Offsets in edited rows (code_offset, name_offset …) are written as given and not checked.
How the game reads it
- likely: the game reads only the header fields
0x14,0x20–0x2C,0x38,0x40,0x48,0x50and0x54when it loads a module, theunk_0x30_offsetlists from the interpreter, and the scripts and variables through those. It checks nothing: not the magic ortotal_size. Source:0x14034e780,0x14034e9d0,0x14034a1b0.
Leads
- A byte map of a whole module: decode the regions still kept as bytes (the int, float, string, function and jump tables,
unk_0x44,unk_0x48, the module vars, the variables' initial values), so the docs describe every byte. - Rebuilding with a new layout: adding or removing instructions, functions, variables or strings means moving every region after it and rewriting the header's and records' offsets and the jump tables; needed before a mod can change a script's length.
- What
table_index,unk_0x18_offset,unk_0x1C_offsetandunk_0x22do, and the second u16 of a variable's initial value. - The
unk_0x30_offsetlists:0x14034de80and0x14034df10would confirm the{script_index, function_index}pairs and giveunk_0x30_offseta name. - Script names are Shift-JIS by Gibbed; check whether Reborn's are all ASCII.
- The format and the VM share a word:
event_scriptfor the format, "script VM" for the interpreter (ScriptVm_*, viewsexe.script_*).