SCO
Материал из GTAModding.ru
Версия от 13:20, 31 мая 2009; Listener (обсуждение | вклад)
Эта статья требует полного или частичного перевода. Часть этой статьи написана на иностранном языке. Если вы знаете его, пожалуйста, помогите с ее переводом на русский язык. |
Файлы формата SCO (с расширением *.sco) содержат скрипты игры GTA 4. Это новый формат, пришедший на смену соответствующих файлов *.scm в предыдущих версиях.
Содержание |
Формат файла
Файл SCO разделен на 4 сегмента. Первый является заголовком и содержит информацию о файле. Второй сегмент - это сегмент кода, он состоит из последовательности опкодов, которые определяют как скрипт будет интерпретирован игрой. Третий сегмент содержит статические переменные - данные, доступные только этому скрипту. Четвертый сегмент присутствует в одном и только в одном скрипте (как правило, это startup.sco). В нем содержатся глобальные переменные, доступные из любого скрипта.
Заголовок
SCO файлы могут быть кодированные, некодированные и упакованные. Заголовок файла всегда не кодированный и неупакованный. Размер заголовка 24 байта для непакованной версии и 28 - для не упакованной.
4b - UINT32 - SCO идентификатор 4b - UINT32 – Размер сегмента кода в байтах 4b - UINT32 – Размер сегмента статических данных в DWORD-ах (чтобы получить размер в байтах, нужно умножить на 4) 4b - UINT32 – Размер сегмента глобальных переменных в DWORD-ах 4b - UINT32 – Количество аргументов у основной функции скрипта 4b - UINT32 – Версия сегмента глобальных данных 4b - UINT32 - Размер упакованных данных (только для упакованных файлов) UINT32 – это беззнаковое целое 32 бит (= 4 байта), оно может принимать значения от 0 до (2^32 – 1)
SCO идентификатор может быть "SCR\x0d" (или 0xD524353) в не кодированной версии, "scr\x0e" (или 0xE726373) в кодированной и "Scr\x0e" (0xE726353) в упакованной.
В кодированных неупакованных файлах, каждый сегмент декодируется отдельно. Чтобы декодировать файл, из файла считываются два или три сегмента, после чего применяется специальный метод GTA IV AES криптографии. Сегмент кода начинается со смещения 24 байта, сегмент статических данных - со смещения (24 + размер кода), сегмент глобальных данных (если он приссутствует) - со смещения (24 + размер кода + (4 * размер статтических данных)).
Чтобы прочитать упакованный файл, нужно выполнить сначала декодирование, а потом распаковать данные. Данные начинаются со смещения 28 байтов. Размер упакованных данных берется из заголовка, размер распакованных - вычисляется как (размер кода + (4 * размер статических данных) + (4 * размер глобальных данных)).
Сегмент кода
Сегмент кода состоит из последовательности опкодов, которые определяют как скрипт будет интерпретирован игрой.
Опкоды
Любая команда состоит из кода операции (опкода) и опциональных параметров. В PC-версии используется 78 опкодов (1..78), в консольной - 75 (1..75). Опкоды 80..255 используются для сокращенной записи ipush (поместить на стек int). При выполнении таких опкодов, на стек помещается число (opcode-96). Неопределенные опкоды (79 на PC, 76..79 на консолях) приводят к аварийному завершению скрипта.
ID | Имя | Определение | Длина |
---|---|---|---|
0 | invalid | Не используется (текущая версия помещает на стек 160) | 1 byte |
1 | iadd | Складывает 2 верхних элемента в стеке (int) | 1 байт |
2 | isub | Вычитает 2 верхних элемента в стеке (int) | 1 байт |
3 | imul | Умножает 2 верхних элемента в стеке (int) | 1 байт |
4 | idiv | Делит нацело 2 верхних элемента в стеке (int) | 1 байт |
5 | imod | Дает остаток от деления 2 верхних элементов в стеке (int) | 1 байт |
6 | inot | Логическая инверсия верхнего элемента стека (0 если он ненулевой, 1 если он равен нулю) | 1 байт |
7 | ineg | Меняет знак верхнего элемента в стеке (int) | 1 байт |
8 | icmpeq | Сравнивает два целых сверху стека на равенство | 1 байт |
9 | icmpne | Сравнивает два целых сверху стека на не равенство | 1 байт |
10 | icmpgt | Сравнивает два целых сверху стека на то что первый больше второго | 1 байт |
11 | icmpge | Сравнивает два целых сверху стека на то что первый больше или равен второму | 1 байт |
12 | icmplt | Сравнивает два целых сверху стека на то что первый меньше второго | 1 байт |
13 | icmple | Сравнивает два целых сверху стека на то что первый меньше или равен второму | 1 байт |
14 | fadd | Складывает 2 верхних элемента в стеке (float) | 1 байт |
15 | fsub | Вычитает 2 верхних элемента в стеке (float) | 1 байт |
16 | fmul | Умножает 2 верхних элемента в стеке (float) | 1 байт |
17 | fdiv | Делит 2 верхних элемента в стеке (float) | 1 байт |
18 | fmod | Дает остаток от деления 2 верхних элементов в стеке (float) | 1 байт |
19 | fneg | Меняет знак верхнего элемента в стеке (float) | 1 байт |
20 | fcmpeq | Сравнивает два элемента сверху стека на равенство (float) | 1 байт |
21 | fcmpne | Сравнивает два элемента сверху стека на не равенство (float) | 1 байт |
22 | fcmpgt | Сравнивает два элемента сверху стека на то что первый больше второго (float) | 1 байт |
23 | fcmpge | Сравнивает два элемента сверху стека на то что первый больше или равен второму (float) | 1 байт |
24 | fcmplt | Сравнивает два элемента сверху стека на то что первый меньше второго (float) | 1 байт |
25 | fcmple | Сравнивает два элемента сверху стека на то что первый меньше или равен второму (float) | 1 байт |
26 | vadd | Складывает 2 верхних вектора[1] в стеке | 1 байт |
27 | vsub | Вычитает 2 верхних вектора[1] в стеке | 1 байт |
28 | vmul | Умножает 2 верхних вектора[1] в стеке | 1 байт |
29 | vdiv | Делит 2 верхних вектора[1] в стеке | 1 байт |
30 | vneg | Меняет знак верхнего вектора[1] в стеке | 1 байт |
31 | iand | Оператор And для 2 первых целых в стеке | 1 байт |
32 | ior | Оператор Or для 2 первых целых в стеке | 1 байт |
33 | ixor | Оператор Xor для 2 первых целых в стеке | 1 байт |
34 | jmp | Перейти по адресу в коде, использует следующие после опкода 4 байта как адрес | 5 байт |
35 | jmpf | Перейти по адресу в коде, если сверху стека 0, использует следующие после опкода 4 байта как адрес | 5 байт |
36 | jmpt | Перейти по адресу в коде, если сверху стека 1, использует следующие после опкода 4 байта как адрес | 5 байт |
37 | itof | Переводит верхнее целое в стеке в дробное и кладет это дробное в стек | 1 байт |
38 | ftoi | Переводит верхнее дробное в стеке в целое и кладет это целое в стек | 1 байт |
39 | ftov | Переводит верхнее дробное в стеке в вектор[1] (содержащий три числа, равных этому дробному) и кладет указатель на вектор[1] в стек | 1 байт |
40 | ipush2 | Короткая форма ipush для значений в диапазоне -32768..32767 (два байта) | 3 байт |
41 | ipush | Помещает в стек число (int), которое следует в коде за командой (4 байта) | 5 байт |
42 | fpush | Помещает в стек число (float), которое следует в коде за командой (4 байта) | 5 байт |
43 | dup | Копирует верхний элемент стека | 1 байт |
44 | drop | Выбрасывает верхний элемент из стека | 1 байт |
45 | native | Вызов native функции. Количество аргументов определяется вторым байтом. Количество значений (как правило, 0 либо 1) определяется 3 байтом. Последние 4 байта из 7 содержат хэш имени функции. | 7 байт |
46 | call | Вызов функции в скрипте. Параметр команды - адрес функции (4 байта) | 5 байт |
47 | enter | Создать стековый фрейм (блок локальных переменных) функции. Байт после опкода определяет количество аргументов функции, которые будут взяты из стека. Следующие 2 байта определяют размер фрейма (и то, и другое - в DWORD-ах) | 4 байт |
48 | ret | Завершить текущую функцию и вернуть значение. Байт после опкода определяет количество аргументов, которые будут извлечены из стека. Следующий байт содержит размер результата, возвращаемого функцией | 3 байта |
49 | pget | Извлекает указатель из стека и помещает в стек значение по адресу указателя. | 1 байт |
50 | pset | Извлекает 2 элемента из стека и записывает второй элемент по адресу обозначенному в первом элементе (должен быть указателем). | 1 байт |
51 | ppeekset | Извлекает из стека два элемента. Записывает первый элемент по адресу из второго. Возвращает второй элемент в стек | 1 байт |
52 | pnget | Извлекает 2 элемента из стека. Первый содежит адрес, второй - количество элементов. Помещает указанные элементы на стек | 1 байт |
53 | pnset | Извлекает из стека два элемента. Первый содержит адрес, второй - количество. Извлекает из стека указанное количество элементов и записывает их в память, начиная с указанного адреса | 1 байт |
54 | pframe0 | Помещает указатель на 1 локальную переменную функции в стек. | 1 байт |
55 | pframe1 | Помещает указатель на 2 локальную переменную функции в стек. | 1 байт |
56 | pframe2 | Помещает указатель на 3 локальную переменную функции в стек. | 1 байт |
57 | pframe3 | Помещает указатель на 4 локальную переменную функции в стек. | 1 байт |
58 | pframe4 | Помещает указатель на 5 локальную переменную функции в стек. | 1 байт |
59 | pframe5 | Помещает указатель на 6 локальную переменную функции в стек. | 1 байт |
60 | pframe6 | Помещает указатель на 7 локальную переменную функции в стек. | 1 байт |
61 | pframe7 | Помещает указатель на 8 локальную переменную функции в стек. | 1 байт |
62 | pframe | Извлекает из стека число. Помещает в стек указатель на локальную переменную функции с этим номером | 1 байт |
63 | pstatic | Извлекает индекс статической переменной из стека, помещает указатель на эту переменную скрипта в стек. | 1 байт |
64 | pglobal | Извлекает индекс глобальной переменной из стека, помещает указатель на эту переменную в стек. | 1 байт |
65 | parray | Извлекает указатель на начало массива, размер элемента и индекс из стека. Помещает указатель на элемент массива в стек | 1 байт |
66 | switch | Следующий за командой байт - количество case. За ним следуют пары "значение (4 байта):адрес (4 байта)". Команда извлекает из стека число и последовательно сравнивает его со всеми значениями. При совпедении, выполняется переход по адресу, соответствующему этому значению | (Байт после опкода * 8) + 2 |
67 | spush | Помещает строку в стек. Байт после опкода обозначает длину строки. Дальше следуют символы строки. | (Байт после опкода)+2 |
68 | null | Помещает указатель на пустую память в стек. | 1 байт |
69 | scpy | Извлекает 2 указателя из стека и копирует второй элемент в первый. | 1 байт |
70 | itos | Извлекает целое из стека и помещает массив-строку, соответствующую числу, в стек. | 1 байт |
71 | sadd | Извлекает 2 указателя из стека, и прибавляет второй элемент к первому. | 1 байт |
72 | saddi | Извлекает 2 элемента из стека, переводит второй в из целого в строку (IntToStr) и добавляет эту строку к первому элементу. | 1 байт |
73 | catch | Устанавливает верх области сохранения, содержащей состояния перехваченных ошибок. (не используется) | 1 байт |
74 | throw | Индикатор области хендлов скриптовых ошибок относительно перехваченных скриптов. (не используется) | 1 байт |
75 | sncpy | Извлекает 3 значения из стека, копирует содержание по 3 адресу в первый элемент (по адресу) с повторением, определенным во втором элементе. Затем добавляет null в содержание по первому адресу. Получается строка в (1) = строке (3) дублированной (2) раз и завершенная нулевым символом. | 1 байт |
76 | getxprotect | Извлекает адресс памяти из стека и добавляет в стек не поддерживаемое XLive значение (только для PC) | 1 байт |
77 | setxprotect | Извлекает расположение памяти из стека извлекает другое значение из стека. Преобразует/поддерживает втрое значение используя XLive и записывает новое значение в память по адресу. (только для PC) | 1 байт |
78 | refxprotect | Извлекает адресс памяти из стека, извлекает другое значение для определения флагов преобразования, и наконец извлекает следующее значение, которое содержит количество элементов для обработки. Если первый бит в флаге установлен все элементы в памяти по адресу будут преобразованы в не поддерживаемое XLive состояние. Если 2 бит установлен они будут XLive поддержаны. (только для PC) | 1 байт |
^ Вектор на стеке это указатель по адресу, который содержит полный адрес. Т.е. при передачи через стек (например при вызове функций) передается не весь вектор/массив, а только указатель (как это делается в С). Вектор это синоним следующей структуре:
4b - FLOAT32 - X 4b - FLOAT32 - Y 4b - FLOAT32 - Z
Статические переменные
Сегмент, содержащий данные, доступные только этому скрипту (любой его функции). Размер любой переменной, кратен 32-м битам. Если переменная занимает меньше, ее размер округляется вверх до кратного четырем байтам.
Глобальные переменные
Глобальные переменные доступны из любого скрипта. Сегмент глобальных переменных должен присутствовать в одном и только в одном скрипте (startup.sco). Поскольку обращение к переменным производится не по имени, а по адресу, важно, чтобы порядок глобальных переменных совпадал для всех скриптов. Для достижения этого, при компиляции, генерируется версия сегмента глобальных данных, которая записывается в заголовок скрипта. Если эта версия не совпадает с той, которая записана в заголовке того скрипта, из которого был загружен сегмент глобальных данных, загрузка скрипта не выполняется.
Высокоуровневое представление
При переводе ассамблера SCO файлов в высокоуровневое представление возникает несколько моментов. Массивы и структуры могут быть определены только на основе следующих типов: целых int, дробных float, строк string или уже определенных структур. Интересно заметить что опкоды содержат типы целых и объединения в строки, это свойство роднит язык SCO с java где строки определяются также (равно как и в С строки – массивы целых типа char). Еще кажется что SCO файлы не имеют низкоуровневой поддержки классов, однако это не значит что они не могут быть введены в SCO также как они были введены в C++.