Script Container

From GTAMods Wiki
Jump to navigation Jump to search

YSC files contain GTA V's game scripts. The new format is an adaptation of the script container format used in Red Dead Redemption.

File Format

The basic format of YSC starts with a header that's 0x80 bytes in size, there are still some unknowns in the header but for basic dissassembling you have enough information.

struct YSCHeader
{
	QWORD pageBase;// 0x00
	QWORD pageMapPointer;// 0x08
	QWORD CodeBlocksBasePointer;// 0x10 Points to an array of code block offsets
	DWORD GlobalsSignature;// 0x18
	DWORD CodeSize;// 0x1C - The size of all the code tables
	DWORD ParameterCount;// 0x20 - These are for starting a script with args. The args appear at the start of the script static variables
	DWORD StaticCount;// 0x24 - The number of static variables in the script
	DWORD GlobalCount;// 0x28 - This is used for scripts that seem to initialise global variable tables
	DWORD NativesCount;// 0x2C - The total amount of natives in the native table
	QWORD StaticsPointer;// 0x30 - The Offset in file where static variables are initialised
	QWORD GlobalsPointer;// 0x38 - The Offset in file where global variales are initilaised(only used for registration scripts)
	QWORD NativesPointer;// 0x40 - The Offset in file where the natives table is stored
	QWORD Null1;//0x48
	QWORD Null2;//0x50;
	DWORD ScriptNameHash;//0x58 - A Jenkins hash of the scripts name
	DWORD UnkUsually1;//0x5C
	QWORD ScriptNamePointer;//0x60 - Points to an offset in the file that has the name of the script
	QWORD StringBlocksBasePointer;//0x68 - Points to an array of string block offsets
	DWORD StringSize;//0x70 - The Size of all the string tables
	DWORD Null3;//0x74
	DWORD Null4;//0x78
	DWORD Null4;//0x7C
};

All Pointer items are stored in the file as 0x50XXXXXX and must therefore be masked with 0xFFFFFF to get their actual file offset.

Pages

The Code and Strings used by the script are stored in pages, each one up to a maximum size of 0x4000 bytes. The number of pages used can be calculated based of the CodeSize/StringSize using this:

int pageCount = (totalSize + 0x3FFF) >> 14;

The size of each of the pages can be calculated using this function

int pageSize(int pageIndex, int totalSize)
{
	if (pageIndex > (totalSize >> 14) || pageIndex < 0){//page out of range
		return 0;
	}else if (pageIndex == (totalSize >> 14)){
		return totalSize & 0x3FFF;
	}
	return 0x4000;
}

Code

The Position of each of the code pages can be found by reading the positions stored in the array pointer to from CodeBlocksBasePointer, then you can read up to pageSize in that page. The code itself is a set of 131 opcodes that is ran on a stack machine.

Opcodes

ID Name Description Length
0 NOP No operation 1 byte
1 IADD Adds the top 2 items on the stack 1 byte
2 ISUB Subtracts the top 2 items on the stack 1 byte
3 IMUL Multiplies the top 2 items on the stack 1 byte
4 IDIV Divides the top 2 items on the stack 1 byte
5 IMOD Mods the top 2 items on the stack 1 byte
6 INOT Checks the first item on the stack to see if it equals 0 1 byte
7 INEG Reverses the sign on the item on the top of the stack 1 byte
8 IEQ Compares the top 2 integers on the stack to see if they are equal 1 byte
9 INE Compares the top 2 integers on the stack to see if they are not equal 1 byte
10 IGT Compares the top 2 integers on the stack to see if the first one is greater than the second one 1 byte
11 IGE Compares the top 2 integers on the stack to see if the first one is greater than or equal to the second one 1 byte
12 ILT Compares the top 2 integers on the stack to see if the first one is less than the second one 1 byte
13 ILE Compares the top 2 integers on the stack to see if the first one is less than or equal to the second one 1 byte
14 FADD Adds the top 2 floats on the stack 1 byte
15 FSUB Subtracts the top 2 floats on the stack 1 byte
16 FMUL Multiplies the top 2 floats on the stack 1 byte
17 FDIV Divides the top 2 floats on the stack 1 byte
18 FMOD Mods the top 2 floats on the stack 1 byte
19 FNEG Reverses the sign on the first float on the stack 1 byte
20 FEQ Compares the top 2 floats on the stack to see if they are equal 1 byte
21 FNE Compares the top 2 floats on the stack to see if they are not equal 1 byte
22 FGT Compares the top 2 floats on the stack to see if the first one is greater than the second one 1 byte
23 FGE Compares the top 2 floats on the stack to see if the first one is greater than or equal to the second one 1 byte
24 FLT Compares the top 2 floats on the stack to see if the first one is less than the second one 1 byte
25 FLE Compares the top 2 floats on the stack to see if the first one is less than or equal to the second one 1 byte
26 VADD Adds the top 2 Vectors[1] on the stack 1 byte
27 VSUB Subtracts the top 2 Vectors[1] on the stack 1 byte
28 VMUL Multiplies the top 2 Vectors[1] on the stack 1 byte
29 VDIV Divides the top 2 Vectors[1] on the stack 1 byte
30 VNEG Reverses the sign on the first vector[1] on the stack 1 byte
31 IAND Performs an And operation to the first 2 integers on the stack 1 byte
32 IOR Performs an Or operation to the first 2 integers on the stack 1 byte
33 IXOR Performs a Xor operation to the first 2 integers on the stack 1 byte
34 I2F Converts the top integer on the stack to a float, and puts that float on the stack 1 byte
35 F2I Converts the top float on the stack to an integer, and puts that integer on the stack 1 byte
36 F2V Converts the top float into a Vector containing 3 instances of the same float, and pushes the pointer to that Vector onto the top of the stack 1 byte
37 PUSH_CONST_U8 Pushes a byte onto the stack, the byte is defined as the next byte after the opcode 2 bytes
38 PUSH_CONST_U8_U8 Pushes 2 bytes onto the stack, the bytes are the next 2 bytes after the opcode 3 bytes
39 PUSH_CONST_U8_U8_U8 Pushes 3 bytes onto the stack, the bytes are the next 3 bytes after the opcode 4 bytes
40 PUSH_CONST_U32 Pushes an int onto the stack, the integer is defined in the next 4 bytes after the opcode 5 bytes
41 PUSH_CONST_F Pushes a float onto the stack, the float is defined in the next 4 bytes after the opcode 5 bytes
42 DUP Duplicates the first item on the stack, and pushes it back onto the stack 1 byte
43 DROP Pops the top item off the stack 1 byte
44 NATIVE Calls a native function. The number of arguments for the native to take is defined in the first 6 bits of the next byte after the opcode(0-63). The number of returns is stored in the following 2 bits(0-3). The hash of the native functions is stored in the native table at the index specified by the following 2 bytes(expressed as an unsigned short) 4 bytes
45 ENTER Indicates the beginning of an internal function. The byte after the opcode indicates the amount of arguments the function takes off the stack, and the next 2 bytes after that indicate the number of variables the function will have to generate on the stack. The following byte indicates how many bytes to skip past after the opcode(usually so a name can be appended to the function definition) 5 bytes + value of 5th byte
46 LEAVE Indicates the end of an internal function. The byte after the opcode indicates the amount of arguments that will have to be popped off the stack, and the next byte after that indicates the stack number of the return address 3 bytes
47 LOAD Pops a pointer off the stack and pushes the value stored in that pointer back onto the stack 1 byte
48 STORE Pops 2 items off the stack and stores the second item at the location of the first item (the first item being a pointer) 1 byte
49 STORE_REV Pops the first item off the stack and peeks at the second item on the stack, then stores the first item at the location pointed to by the second item on the stack 1 byte
50 LOAD_N Pops 2 items off the stack, the first being the number of items, second being the memory address. It then pushes that many items to the stack from the memory address 1 byte
51 STORE_N Pops 2 items off the stack, the first being the number of items, second being the memory address. It then pops that many items from the stack to the memory address 1 byte
52 ARRAY_U8 Pops 2 items off the stack, the first being an array index, second being the pointer to an array. It then pushes the pointer to theindex in the array to the top of the stack. The size each item in the array is determined by the byte following the opcode 2 bytes
53 ARRAY_U8_LOAD Pops 2 items off the stack, the first being an array index, second being the pointer to an array. It then pushes the item at the array index to the top of the stack. The size each item in the array is determined by the byte following the opcode 2 bytes
54 ARRAY_U8_STORE Pops 3 items off the stack, the first being the value to set, second being an array index and last being the pointer to an array. It the sets the value at the index in the array to first value. The size each item in the array is determined by the byte following the opcode 2 bytes
55 LOCAL_U8 Pushes the pointer to the frame variable at the index specified by the byte following the opcode 2 bytes
56 LOCAL_U8_LOAD Pushes the value of the frame variable at the index specified by the byte following the opcode 2 bytes
57 LOCAL_U8_STORE Pops an item off the stack, then sets the frame variable at the index specified by the byte following the opcode to the item 2 bytes
58 STATIC_U8 Pushes the pointer to the static variable at the index specified by the byte following the opcode 2 bytes
59 STATIC_U8_LOAD Pushes the value of the static variable at the index specified by the byte following the opcode 2 bytes
60 STATIC_U8_STORE Pops an item off the stack, then sets the static variable at the index specified by the byte following the opcode to the item 2 bytes
61 IADD_U8 Adds the byte directly after the opcode to the integer value at the top of the stack, then pushes the new value to the top of the stack 2 bytes
62 IMUL_U8 Multiplies the byte directly after the opcode to the integer value at the top of the stack, then pushes the new value to the top of the stack 2 bytes
63 IOFFSET Pops an index and a pointer to a struct off the stack, pushes the pointer to the item at 8 times the index in the struct 1 byte
64 IOFFSET_U8 Pops an pointer to a struct off the stack, pushes the pointer to the item at the index specified by 8 times the byte following the opcode 2 bytes
65 IOFFSET_U8_LOAD Pops an pointer to a struct off the stack, pushes the value of the item at the index specified by 8 times the byte following the opcode 2 bytes
66 IOFFSET_U8_STORE Pops an value and a pointer to a struct off the stack, sets the item at the index specified by 8 times the byte following the opcode to the value popped of the stack 2 bytes
67 PUSH_CONST_S16 Pushes a signed short to the stack, the value is the 2 bytes following the opcode 3 bytes
68 IADD_S16 Adds the short directly after the opcode to the integer value at the top of the stack, then pushes the new value to the top of the stack 3 bytes
69 IMUL_S16 Multiplies the short directly after the opcode to the integer value at the top of the stack, then pushes the new value to the top of the stack 3 bytes
70 IOFFSET_S16 Pops an pointer to a struct off the stack, pushes the pointer to the item at the index specified by 8 times the 2 bytes following the opcode 3 bytes
71 IOFFSET_S16_LOAD Pops an pointer to a struct off the stack, pushes the value of the item at the index specified by 8 times the 2 bytes following the opcode 3 bytes
72 IOFFSET_S16_STORE Pops an value and a pointer to a struct off the stack, sets the item at the index specified by 8 times the 2 bytes following the opcode to the value popped of the stack 3 bytes
73 ARRAY_U16 Pops 2 items off the stack, the first being an array index, second being the pointer to an array. It then pushes the pointer to theindex in the array to the top of the stack. The size each item in the array is determined by the 2 bytes following the opcode 3 bytes
74 ARRAY_U16_LOAD Pops 2 items off the stack, the first being an array index, second being the pointer to an array. It then pushes the item at the array index to the top of the stack. The size each item in the array is determined by the 2 bytes following the opcode 3 bytes
75 ARRAY_U16_STORE Pops 3 items off the stack, the first being the value to set, second being an array index and last being the pointer to an array. It the sets the value at the index in the array to first value. The size each item in the array is determined by the 2 bytes following the opcode 3 bytes
76 LOCAL_U16 Pushes the pointer to the frame variable at the index specified by the 2 bytes following the opcode 3 bytes
77 LOCAL_U16_LOAD Pushes the value of the frame variable at the index specified by the 2 bytes following the opcode 3 bytes
78 LOCAL_U16_STORE Pops an item off the stack, then sets the frame variable at the index specified by the 2 bytes following the opcode to the item 3 bytes
79 STATIC_U16 Pushes the pointer to the static variable at the index specified by the 2 bytes following the opcode 3 bytes
80 STATIC_U16_LOAD Pushes the value of the static variable at the index specified by the 2 bytes following the opcode 3 bytes
81 STATIC_U16_STORE Pops an item off the stack, then sets the static variable at the index specified by the 2 bytes following the opcode to the item 3 bytes
82 GLOBAL_U16 Pushes the pointer to the global variable at the index specified by the 2 bytes following the opcode 3 bytes
83 GLOBAL_U16_LOAD Pushes the value of the global variable at the index specified by the 2 bytes following the opcode 3 bytes
84 GLOBAL_U16_STORE Pops an item off the stack, then sets the global variable at the index specified by the 2 bytes following the opcode to the item 3 bytes
85 J Performs a relative jump to a new location in the code, the relative offset is the signed short following the opcode 3 bytes
86 JZ Performs a relative jump to a new location in the code if the item at the top of the stack is zero, the relative offset is the signed short following the opcode 3 bytes
87 IEQ_JZ Pops 2 items off the stack, Performs an integer comparison on them and performs a relative jump to a new location in the code if the first is not equal to the second. The relative offset is the signed short following the opcode 3 bytes
88 INE_JZ Pops 2 items off the stack, Performs an integer comparison on them and performs a relative jump to a new location in the code if the first is equal to the second. The relative offset is the signed short following the opcode 3 bytes
89 IGT_JZ Pops 2 items off the stack, Performs an integer comparison on them and performs a relative jump to a new location in the code if the first is less than or equal to the second. The relative offset is the signed short following the opcode 3 bytes
90 IGE_JZ Pops 2 items off the stack, Performs an integer comparison on them and performs a relative jump to a new location in the code if the first is less than the second. The relative offset is the signed short following the opcode 3 bytes
91 ILT_JZ Pops 2 items off the stack, Performs an integer comparison on them and performs a relative jump to a new location in the code if the first is greater than or equal to the second. The relative offset is the signed short following the opcode 3 bytes
92 ILE_JZ Pops 2 items off the stack, Performs an integer comparison on them and performs a relative jump to a new location in the code if the first is greater than the second. The relative offset is the signed short following the opcode 3 bytes
93 CALL Calls a function within the script, and puts the return address on top of the stack. The location of the function is defined in the next 3 bytes after the opcode 4 bytes
94 LOCAL_U24 Added in v1.0.2802.0. Pushes the pointer to the frame variable at the index specified by the 3 bytes following the opcode 4 bytes
95 LOCAL_U24_LOAD Added in v1.0.2802.0. Pushes the value of the frame variable at the index specified by the 3 bytes following the opcode 4 bytes
96 LOCAL_U24_STORE Added in v1.0.2802.0. Pops an item off the stack, then sets the frame variable at the index specified by the 3 bytes following the opcode to the item 4 bytes
97 GLOBAL_U24 Pushes the pointer to the global variable at the index specified by the 3 bytes following the opcode 4 bytes
98 GLOBAL_U24_LOAD Pushes the value of the global variable at the index specified by the 3 bytes following the opcode 4 bytes
99 GLOBAL_U24_STORE Pops an item off the stack, then sets the global variable at the index specified by the 3 bytes following the opcode to the item 4 bytes
100 PUSH_CONST_U24 Pushes an unsigned 24 bit integer to the stack, the value is the 3 bytes following the opcode 4 bytes
101 SWITCH Pops the item to compare off the stack, and then jumps to location corresponding to that item. After the opcode byte it contains a byte defining the number of possible entries, and after that the number of possible entries times 6 are taken up with repeating instances of 4 bytes of the index identifier, and 2 bytes of the relative jump offset to jump to if that index is correct (note jump offset is unsigned unlike branch instructions) (Byte after opcode * 6) + 2 bytes
102 STRING Pops an integer off the stack, then returns the pointer to the string stored at that index in the String Table 1 byte
103 STRINGHASH Pops the pointer to a string off the stack. The computes a jenkins-one-at-a-time hash on the string and pushes the result to the stack 1 byte
104 TEXT_LABEL_ASSIGN_STRING Pops a pointer to a destination buffer and a string, copies the string to the destination buffer. The size of the buffer is specified in the byte following the opcode 2 bytes
105 TEXT_LABEL_ASSIGN_INT Pops a pointer to a destination buffer and a integer, converts the integer to a string and stores it in the destination buffer. The size of the buffer is specified in the byte following the opcode 2 bytes
106 TEXT_LABEL_APPEND_STRING Pops a pointer to a destination buffer and a string, appends the string to the string in the destination buffer. The size of the buffer is specified in the byte following the opcode 2 bytes
107 TEXT_LABEL_APPEND_INT Pops a pointer to a destination buffer and a integer, converts the integer to a string and appends it to the string in the destination buffer. The size of the buffer is specified in the byte following the opcode 2 bytes
108 TEXT_LABEL_COPY Pops 3 items off the stack. Copies the third item's memory into the first item's memory with repeating defined by the second item. It then appends a null terminator to the first item's memory 1 byte
109 CATCH Sets up a safe area that has the ability to catch errors - not used in release builds of GTA 1 byte
110 THROW Indicates an area that handles a script error relative to the catch opcode - not used in release builds of GTA 1 byte
111 CALLINDIRECT Calls a function within the script, and puts the return address on top of the stack. The location of the function is defined in the integer at the top of the stack 1 byte
112 PUSH_CONST_M1 Pushes the integer -1 to the top of the stack 1 byte
113 PUSH_CONST_0 Pushes the integer 0 to the top of the stack 1 byte
114 PUSH_CONST_1 Pushes the integer 1 to the top of the stack 1 byte
115 PUSH_CONST_2 Pushes the integer 2 to the top of the stack 1 byte
116 PUSH_CONST_3 Pushes the integer 3 to the top of the stack 1 byte
117 PUSH_CONST_4 Pushes the integer 4 to the top of the stack 1 byte
118 PUSH_CONST_5 Pushes the integer 5 to the top of the stack 1 byte
119 PUSH_CONST_6 Pushes the integer 6 to the top of the stack 1 byte
120 PUSH_CONST_7 Pushes the integer 7 to the top of the stack 1 byte
121 PUSH_CONST_FM1 Pushes the float -1.0 to the top of the stack 1 byte
122 PUSH_CONST_F0 Pushes the float 0.0 to the top of the stack 1 byte
123 PUSH_CONST_F1 Pushes the float 1.0 to the top of the stack 1 byte
124 PUSH_CONST_F2 Pushes the float 2.0 to the top of the stack 1 byte
125 PUSH_CONST_F3 Pushes the float 3.0 to the top of the stack 1 byte
126 PUSH_CONST_F4 Pushes the float 4.0 to the top of the stack 1 byte
127 PUSH_CONST_F5 Pushes the float 5.0 to the top of the stack 1 byte
128 PUSH_CONST_F6 Pushes the float 6.0 to the top of the stack 1 byte
129 PUSH_CONST_F7 Pushes the float 7.0 to the top of the stack 1 byte
130 IS_BIT_SET Added in v1.0.2612.1. Pops the value and the bit to test from the stack and pushes the result of value & (1 << bit). Replaced the IS_BIT_SET native 1 byte

Native Functions

All Natives used by the script are stored in a table of hashes (seemingly random unlike on X360/PS3 versions where they were a jenkins hash of the native name). The address of the table and size can be read from the YSC Header. The native hashes are rotated right by the size of the codeSize + the index of the native in the table. To get the hash of a native from a native table index use this. The nativeTable can be got from reading the pointer from the header and masking it with 0xFFFFFF. Then returning that position in the YSC File.

QWORD getNativeHash(WORD index, const QWORD* nativeTable, DWORD codeSize)
{
	BYTE rotate = (index + codeSize) & 0x3F;
	return nativeTable[index] << rotate | nativeTable[index] >> (64 - rotate);
}

Since game version b350. Rockstar began changing the hashes of the natives each game update. Its unsure if they are random or not, but either way trying to disassemble scripts in patch b350 or later will require translating all hashes from the nativeTable(after rotating them) into their vanilla game hash. This can be done using the translation tables Alexander Blade provides in here: GTA Net GTAForums: Script/Native Documentation