Формат файлов скриптов Fallout 2 (*.int)

Содержание:

  1. Структура
  2. Типы данных
  3. Переменные
  4. Функции
  5. Команды
    1. Системные команды
    2. Системно-прикладные команды
    3. Прикладные команды
  6. Загадки

Приложение А - список команд


1. Структура

Файл скрипта состоит из:

Единственным изменяющимся элементом заголовка является двойное слово по смещению 0Ch, которое указывает на начало кода скрипта. Таблице функций предшествует двойное слово - количество функций. Затем следует сама таблица, состоящая из 24-байтовых записей. Достоверно известно назначение трех полей такой записи:

Список идентификаторов начинается с двойного слова - размера списка (размер считается, исключая это двойное слово и завершающее список FFFFFFFFh). Завершается двойным словом -1 (т.е. FFFFFFFFh). Сам список представляет собой последовательность строк хранящихся следующим образом: сначала в слове записывается длина строки, включая завершающие нуль символы, но исключая само это слово. Затем хранится строка, которая завершается одним или двумя нуль символами с тем, чтобы каждая последующая строка имела четное смещение от начала файла (нарушение выравнивания делает скрипт неработоспособным). Адреса строк в скриптах вычисляются от начала раздела (т.е. считая с того адреса, по которому размещен размер раздела) и указывают не на размер строки, а на ее первый символ.

За списком идентификаторов следует список строк, имеющий аналогичную структуру. Если список строк пуст, то может быть записано только FFFFFFFFh.

Код представляет собой последовательность функций, состоящих из команд, которым ниже посвящена отдельная глава. Пока скажем лишь, что начинается раздел кода с последовательности, инициализирующей переменные скрипта. После инициализации происходит переход на начало функции с зарезервированным именем start. Вообще, имена функций в Fallout 2 могут быть совершенно произвольными и включать даже те символы, которые обычно в идентификаторах не допускаются. Например, имя первой функции почти любого скрипта состоит из четырнадцати точек. Кроме того, две функции в таблице скриптов, указывающие на разные строки в качестве имен, могут указывать на один и тот же код. Примером являются те же четырнадцать точек и checkPartyMembersNearDoor.

Важное замечание: все значения в файлах скриптов хранятся в формате Моторола, а не Интел.

 

2. Типы данных.

Чтобы понять, как в Fallout хранятся данные, нужно сначала уяснить, что все команды скриптов делятся на два типа:

  1. Команды помещения константы в стек
  2. Прочие

Дело в том, что движок Fallout использует распространенную для скриптовых движков стековую модель, когда операнды сначала помещаются в стек, а затем над ними производятся операции, результаты которых возвращаются в тот же стек. Наиболее адекватно такие действия описываются в языках с так называемой бесскобочной логикой, самым известным представителем которых является Форт.

Команды первого типа всегда занимают 6 байт, второго - 2 байта.

На данный момент известно три команды первого типа:

C001 XXXX XXXX - помещает в стек целое число XXXX XXXX

A001 XXXX XXXX - помещает в стек вещественное число в формате Single.

9001 XXXX XXXX - помещает в стек адрес строки. Нужно отметить, что адрес этот в разных контекстах воспринимается по-разному. Обычно это смещение от начала списка строк, но для некоторых команд может быть смещением от начала списка идентификаторов.

Стек в Fallout построен таким образом, что значения хранятся с указанием их типа, причем в 4 байтах с меньшими адресами хранится значение, а в двух байтах с большими адресами - C001, A001 или 9001. Вероятно, что список команд, помещающих значения в стек, не исчерпывается этими тремя. При исследовании обработчиков команд очень часто встречается обработка аргумента с типом 9801.

 

3. Переменные

Все переменные скриптов Fallout можно разделить по времени жизни на статические и динамические. Статическими будем называть те переменные, значения которых сохраняются в сейвах. Все остальные переменные (значения которых хранятся в памяти ограниченное время) - динамические.
Статические переменные делятся на три класса: глобальные переменные, переменные локации, переменные скрипта.
Список глобальных переменных с начальными значениями хранится в файле vault13.gam.
Списки переменных локаций с начальными значениями хранятся в файлах *.gam, одноименных с *.map.
Количество переменных всех скриптов хранится в scripts.lst.
Переменные всех трех классов можно добавлять путем изменения соответствующих файлов.

Динамические переменные содержатся в стеке. При загрузке скрипта выполняется код, инициализирующий динамические переменные скрипта. Аналогично в начале функции выполняется инициализация ее переменных.

 

4. Функции

Как уже было сказано, функции в скриптах Fallout могут именоваться совершенно произвольно. Но для организации реакции скрипта на происходящие в мире Fallout события определен набор специальных имен. Т.е. если, например, в скрипте есть функция с именем talk_p_proc, то она будет вызываться при попытке заговорить с собственником скрипта.

Всего таких предопределенных имен 25 (знак вопроса в скобках означает, что условия вызова данной функции выяснены не окончательно):

  1. start - при загрузке скрипта в память (инициализирует динамические переменные скрипта)
  2. look_at_p_proc - при наведении курсора на объект (краткое описание)
  3. description_p_proc - при выборе подробного описания (бинокль)
  4. create_p_proc - при создании объекта-собственника скрипта
  5. destroy_p_proc - при убийстве/уничтожении объекта
  6. map_enter_p_proc - при входе чузена в локацию
  7. map_exit_p_proc - при покидании чузеном локации
  8. map_update_p_proc (?)
  9. critter_p_proc - постоянно выполняется, отвечает за поведение в целом.
  10. talk_p_proc - организация разговора
  11. use_p_proc - при использовании
  12. use_obj_on_p_proc - при использовании другого объекта на этом
  13. use_skill_on_p_proc - при использовании скилла (лечения, воровства, ремонта и т.д.) на этом объекте
  14. pickup_p_proc - при поднимании с земли (?)
  15. drop_p_proc - при бросании
  16. is_dropping_p_proc (?)
  17. push_p_proc - при попытке сдвинуть криттера с дороги
  18. spatial_p_proc - срабатывает при прохождении чузена над объектом (для ловушек, например)
  19. timed_event_p_proc - с каким-то периодом, возможно, имеет отношении к взрывчатке (?)
  20. combat_is_starting_p_proc - в начале боя
  21. combat_is_over_p_proc - после боя
  22. combat_p_proc - выполняется перед каждым ходом криттера в бою
  23. damage_p_proc - при нанесении криттеру повреждений
  24. no_p_proc (?)
  25. none_x_bad (?)

 

5. Команды

Для целей классификации команд скриптов мной было принято условное разделение всех команд по степени их связи с собственно игровой моделью Fallout на системные, системно-прикладные и прикладные. В дальнейшем изложении для описания формата команды будет принята следующая нотация:

Код команды; ее условное наименование или обозначение; список аргументов в порядке их помещения в стек через запятую; возвращаемое командой значение.

5.1 Системные команды.

Команды арифметических, битовых и логических операций.

8039; +; первое слагаемое, второе слагаемое; результат сложения.

Эта команда обрабатывает значения практически любых типов. При сложении целых и вещественных значений возвращается вещественный результат. При сложении строк возвращается строка, являющаяся результатом конкатенации операндов. При сложении строки и числа происходит конкатенация строки со строковым представлением числа. Единственное ограничение данной команды - невозможность сложения числа и строки.

803A; -; уменьшаемое, вычитаемое; разность.

803B;*; первый множитель, второй множитель; произведение.

803C; /; делимое, делитель; частное.

803D; \; делимое, делитель; остаток.

8046; -; число; число с противоположным знаком.

8044; \; число; целая часть числа.

Все перечисленные операции допускают как целые, так и вещественные операнды. Последняя команда имеет смысл только для вещественных значений, но допускает и целые операнды, которые возвращает в неизменном виде.

8040;&; целое, целое; результат побитового И.

8041;|; целое, целое; результат побитового ИЛИ.

8042;^; целое, целое; результат побитового исключающего ИЛИ.

8043;!; целое; результат побитового НЕ.

Для понимания тонкостей вычисления описываемых ниже команд сравнения и логических команд важно определить, какие значения в Fallout представляют истину, а какие - ложь. Забегая вперед, скажем, что команда ветвления выполняет переход, если ее аргумент-условие равен нулю. Т.е. нуль можно было бы принять за истинное значение. Но при анализе скриптов мы видим, что чаще всего переход осуществляется, чтобы НЕ выполнять какие-либо действия, которые нужно выполнить при соблюдении некоторого условия. Исходя из этого, гораздо удобнее было бы считать, что переход происходит при ложном значении условия, таким образом, любое неотрицательное целое число представляет собой истинное значение, и только нуль - ложь.

8033;=; число, число; истина, если аргументы равны.

8034;<>; число, число; истина, если аргументы не равны.

8035;<=; число, число; истина, если первый не больше второго.

8036;>=; число, число; истина, если первый не меньше второго.

8037;<; число, число; истина, если первый меньше второго.

8038;>; число, число; истина, если первый больше второго.

803E; AND; булево значение, булево значение; логическое И.

803F;OR; булево значение, булево значение; логическое ИЛИ.

8045; NOT; булево значение; логическое НЕ.

Булевы значения - целые числа, воспринимаемые в описанном выше смысле.

Команды манипуляций стеком.

801A; Del; любое значение; результат не возвращается.

Команда просто удаляет элемент с вершины стека.

801B; Dup; любое значение; результат - копия элемента.

Команда дублирует элемент на вершине стека. С ее помощью можно, например, умножение на 2 записать так:

801B 8039

8031; нет обозначения; новое значение переменной, номер переменной; результат не возвращается.

8032; нет обозначения; номер переменной; значение переменной.

Эти команды используются для чтения-записи динамических переменных функции, т.е. таких переменных, которые существуют только во время выполнения данной функции. Для того чтобы эти переменные существовали, сразу после инициализации функции командами помещения значений в стек помещаются начальные значений этих переменных (обычно целые нули). Нужно отметить, что любой переменной в стеке может быть присвоено значение типа, отличного от текущего. Т.е. стековые переменные можно рассматривать как переменные типа Variant.

8012; нет обозначения; номер переменной; значение переменной.

8013; нет обозначения; новое значение переменной, номер переменной; результат не возвращается.

Эти команды используются для чтения-записи динамических переменных скрипта, т.е. таких переменных, которые существуют столько, сколько сам скрипт (скрипт объекта в текущей локации существует до тех пор, пока игрок не выйдет из локации). Для того чтобы эти переменные существовали, они инициализируются командами помещения значений в стек в инициализационном коде скрипта.

Команды управления.

Команды управления можно подразделить на команды передачи управления, команды инициализации и завершения выполнения и команды вызова функций.

8004; goto; адрес перехода; результат не возвращается.

Команда безусловного перехода. Адрес перехода - смещение от начала файла.

802F; goto; адрес перехода, условие; результат не возвращается.

Условный переход, если условие ложно.

8030; while; адрес перехода, условие; результат не возвращается.

Условный переход, если условие ложно. От предыдущей команды отличается тем, что при истинном условии не удаляет адрес перехода из стека, что обеспечивает в скриптах механизм циклов (для следующей итерации производится безусловный переход на код, вычисляющий условие).

802C; нет обозначения; нет операндов; результат не возвращается.

С этой команды начинается инициализационный код любого скрипта. Действие ее достоверно не установлено. Но, предположительно, эта команда определяет, что с текущей вершины стека будут отсчитываться индексы динамических переменных скрипта. Инициализация локальных переменных скрипта завершается командой 8003 без аргументов и результата.

802B; нет обозначения; нет операндов; результат не возвращается.

С этой команды начинается код каждой функции скрипта. Действие ее достоверно не установлено. Но, предположительно, эта команда определяет, что с текущей вершины стека будут отсчитываться индексы динамических переменных функции.

800D; return (?); значение для помещения в стек возвратов; результат не возвращается.

Эта команда используется в двух описываемых ниже контекстах. А именно: при возвращении результата и при вызове одной функции из другой. Особенности ее применения заставляют предположить, что кроме основного стека в движке Fallout реализован еще и стек возвратов, через который передаются адреса возврата и результаты функций. Вообще говоря, любая функция скрипта возвращает результат. Обычно тело функции завершается последовательностью (подразумевается, что на вершине основного стека находится результат функции):

800D 8019 802A 8029 800C 801C 802A 8029 801C

Кроме того, в теле функции могут быть еще несколько вариантов возвращения результата, тогда используется укороченная последовательность:

800D 8019 802A 8029 800C 801C

Точное действие остальных команд этих последовательностей не установлено, да это и не требуется, т.к. последовательности стандартны и команды эти используются только в них.

Вызов одной функции из другой строится с использованием команд 800D и 8005:

8005; call; аргумент1, аргумент2, … , аргументN, количество аргументов, индекс функции в таблице функций (считается от нуля); результат функции.

Аргументов может не быть вообще, тогда количество их устанавливается в ноль. Индекс может быть указан как константа командой C001 XXXX XXXX или вычислен из имени функции командой 8028:

8028; @; указатель на строку - имя функции; индекс функции.

Указатель помещается командой 9001 XXXX XXXX и смещение считается от начала списка строк (а не идентификаторов).

Вызову функции предшествует помещение адреса возврата в стек возвратов. Пример:

Предположим, что адрес команды, следующей за вызовом функции, равен 15E6h. Функция не имеет аргументов и индекс ее равен 5. Тогда вызов функции записывается следующей последовательностью:

C001 0000 15E6 800D C001 0000 0000 C001 0000 0005 8005

Большинство функций в скриптах используются скорее как процедуры, т.е. возвращают всегда нулевой результат, не используемый в дальнейшем. Для удаления такого результата из стека используется описанная выше команда 801A, следующая непосредственно за 8005.

Команды манипуляции внешними переменными.

Для организации взаимодействия между скриптами различных объектов в локации используется механизм внешних переменных. Такие переменные объявляются в инициализационном коде скрипта локации и скрипт любого объекта в текущей локации может обратиться к ним по имени.

8014; нет обозначения; указатель на строку - имя переменной; значение переменной.

8015; нет обозначения; новое значение переменной, указатель на строку - имя переменной; результат не возвращается.

8016; нет обозначения; указатель на строку - имя переменной; результат не возвращается.

Первые две команды используются для чтения-записи значений внешних переменных. Причем базой для указателей служит начало списка идентификаторов, а не строк. Третья команда используется для объявления внешних переменных в скриптах локаций. Пример:

Предположим, что мы хотим объявить внешнюю переменную, имя которой в списке идентификаторов начинается со смещения 01DAh, и присвоить ей начальное значение 100. Тогда в инициализационном коде скрипта нужно применить такую последовательность:

9001 0000 01DA 8016 C001 0000 0064 9001 0000 01DA 8015

5.2 Системно-прикладные команды.

Команды получения адреса объекта.

Для дальнейшего изложения необходимо уточнить, что каждый объект в Fallout имеет идентификатор типа (ObjectID) и адрес (ObjectAddr). Концепция идентификаторов типа описана в документации по протофайлам, а адрес представляет собой просто смещение в сегменте данных. Ниже описываются команды, возвращающие именно ObjectAddr.

80BF; Player; нет аргументов; ObjectAddr игрока.

Сам Избранный тоже является объектом Fallout, причем наиболее интересным J .

80BC; Self; нет аргументов; ObjectAddr объекта, которому принадлежит данный скрипт.

80BE; Self1; нет аргументов; ObjectAddr объекта, которому принадлежит данный скрипт.

Назначение команды 80BE точно не выяснено, но в большинстве случаев она возвращает то же значение, что и 80BC.

80BD; Sender; нет аргументов; ObjectAddr объекта, вызвавшего данный обработчик.

Например, если выполняется функция use_skill_on_p_proc, то 80BD возвращает адрес того, кто применил данный скилл.

80C0; UsedObj; нет аргументов; ObjectAddr использованного на объекте-собственнике скрипта предмета.

Имеет смысл в контексте use_obj_on_p_proc.