Типичные подводные камни моддинга и разбора файлов
Источник: coreprime/kbot @ 0ab97bf · Редактировать на GitHub
Шпаргалка на одну страницу с самыми коварными подводными камнями, разбросанными по документации kbot о форматах. Если вы столкнулись с ошибкой, у которой нет никакого смысла, начните отсюда. Каждый пункт ведёт к подробному объяснению на странице соответствующего формата.
Tip
Ищете ответ на «мой мод не загружается»? Правило точки с запятой в TDF (#tdf--fbi--ota, первый пункт) объясняет больше сообщений о «молчаливых поломках», чем все остальные подводные камни вместе взятые.
HPI / UFO / CCX / GP3
- Файлы вне архивов в корне установки перекрывают любой архив.
Положите на диск
units/MYUNIT.fbi, и движок предпочтёт его всему, что лежит вtotala1.hpi. Полную иерархию приоритетов показывает схема наложения слоёв в hpi.md. - Среди архивов побеждает тот, что смонтирован первым, а
*.hpiмонтируется всего десять. TA 3.1c монтируетrev31.gp3, затем*.ccx,*.ufoи первые десять*.hpi, которые удалось открыть, каждую группу — в порядке имён в верхнем регистре ASCII. Мод, выпущенный какzz_mod.hpi, оказывается ниже всех оригинальных архивов, а в установке, где уже есть десять*.hpi, идущих по алфавиту раньше него, монтируется только при сканировании диска — ниже всего остального. Выпускайте моды как.ufo. - Указатели HPI v1 отсчитываются от начала файла, а не от начала
каталога. Ничего не вычитайте. Приём канонического парсера —
выделить буфер размером
DirectorySizeбайт и расшифровать каталог в него по смещениюStart, чтобы указатели индексировали буфер напрямую. См. Дерево каталогов. - Начальное значение шифра HPI v1 меняется с позицией в файле. Расшифровать буфер в отрыве от файла нельзя — нужно знать, с какого абсолютного смещения в файле он взят. См. Вывод ключа шифра.
- Если
DecryptKey == 0, шифр XOR не применяйте вовсе. Некоторые самодельные архивы хранятся открытым текстом; если всё равно прогнать шифр, уже читаемые данные превратятся в мешанину. - Контрольная сумма фрагмента SQSH считается по зашифрованным байтам. Проверяйте её до расшифровки. См. Сжатые фрагменты (SQSH).
- TA: Kingdoms использует HPI v2 (
Version == 0x00020000) с другим устройством — без XOR, один фрагмент SQSH на файл. Прежде чем натравливать парсер v1 на архив TAK, прочитайте раздел TA: Kingdoms, HPI v2. - Сохранённые игры с маркером
BANK— совсем другой зверь: они используют оболочку HPI, но внутри сериализованное состояние игры, а не файловая система. kbot их не декодирует. - Для
.gp3нужен патч rev3.1. Внутри это всё тот же HPI, но TA без патча отказывается его монтировать.
TDF / FBI / OTA
- Каждое значение должно заканчиваться на
;. Без точки с запятой значения молча сливаются через строки, и характеристики получаются совершенно неправильными. Это причина № 1 жалоб вида «у моего юнита странные очки прочности / нет описания / неверная цена». - Ключи не зависят от регистра, значения — зависят.
Side=armработает в большинстве случаев, но ломает любой код мода, который сравниваетSideкак строку. - Значения-списки разделяются пробелами (
Category=COMMANDER MOBILE WEAPON), а не запятыми. Исключение —numplayers=2,4,6,8,10в OTA, где используются запятые. - Булевы значения — целые
1/0, а неtrue/false. Движок принимает только целочисленную форму. - В модах никогда не правьте
sidedata.tdf, чтобы добавить юниты. Вместо этого регистрируйте их через[MENUENTRY]вdownload/<UNITNAME>.tdf— тем же механизмом пользуется Core Contingency. См. TDF:[MENUENTRY]и руководство по моддингу. Bmcode=0прячет юнит из меню строительства, даже если всё остальное верно. Почти всегда вам нуженBmcode=1.- Юнит, которого нет в списке ни у одного строителя, построить нельзя, даже если его FBI в остальном верен. Это самая частая ошибка вида «мой новый юнит существует, но его не построить». См. шаг 5 руководства по моддингу.
- Глубина вложенности секций — соглашение, а не спецификация.
Некоторые инструменты рассчитывают на 2 уровня; TDF оружия уходят на
3 уровня (
[WEAPON][DAMAGE][category]).
TNT / SCT — рельеф карты
WidthиHeightв TNT — это ячейки атрибутов по 16 пикселей, а не тайлы и не пиксели. Разделите на 2, чтобы получить число тайлов; умножьте на 16, чтобы получить размеры в пикселях. См. Заголовок TNT.SeaLevel == 0НЕ означает «воды нет» — это значит, что наименьшее возможное значение лежит на пороге, и под водой ничего не оказывается. Для по-настоящему сухих карт используйтеSeaLevel = 1.- Слова объектов TNT ставят объект, только если они меньше числа
записей в таблице объектов.
0xFFFC— пустота;0xFFFE(Lava Run, AC02),0xFFFDи любое другое слово, равное размеру таблицы или больше, ничего не ставят, и в ячейке можно строить. - Заполнение миникарты TNT использует индекс палитры
0x64, а область карты вычисляется по размеру карты (длинная сторона 252, пропорции видимой карты), а не ищется по заполнению. Игра читает сохранённую миникарту, только если установлен бит 0 слова заголовка по смещению0x2c. См. TNT. .tntв TA: Kingdoms используетIDVersion == 0x4000вместо0x2000из TA (TA также читает более старое устройство0x1020).kbot tnt imageна TNT из TAK выдаёт мусор: декодер тайлов рассчитан только на TA. См. Карты TA:K.- Секции SCT должны быть кратны 4 по обоим измерениям, иначе TAE их не загрузит. Формат на диске допускает и другие размеры, но редактор отвергает их с сообщением «section misaligned».
- В SCT V2 ячейки высот занимают 8 байт, в V3 — 4 байта. kbot принимает обе версии; моды, сделанные в редакторе, видят только V3.
3DO — модели
- Вверх смотрит Y, а не Z. Если импортировать 3DO в движок, где вверх смотрит Z (Blender, современный Unreal), не поменяв оси, каждый юнит окажется лежащим на боку.
- Примитивы без текстуры невидимы, если только не
IsColored != 0— псевдокодvisible = (texture != "") || (IsColored != 0)учитывает оба случая. Самая частая ошибка отрисовки 3DO. OffsetToSelectionPrimимеет смысл только у корневого объекта; дочерние объекты должны записывать в него-1(0xFFFFFFFF).- Масштаб фиксированной точки —
1 / 65536. Вершина, записанная как0x00010000, = 1,0 единицы мира; младшие 16 бит — дробная часть. - Вершины локальны для объекта, а не глобальны. У двух объектов могут быть одинаковые массивы вершин — это зеркальная геометрия, а не общие данные.
COB / BOS — скрипты
- Опкод
call-script— это0x10062000, а НЕ0x10063000, как утверждают некоторые документы до версии 3.1. Старые декомпиляторы с0x10063000выдают бессмыслицу — см. справочник опкодов. - Номера деталей в опкодах — индексы в таблице имён деталей, а не строки. COB хранит имена в пуле строк, а во время выполнения ради скорости используются индексы.
SET_SIGNAL_MASKтолько регистрирует интерес — он ничего не завершает. Скрипты, в маске которых установленX, останавливает отправкаsignal X. Правило линтераsignal-never-signalledнаходит скрипты, которые устанавливают маску, но этот сигнал никто никогда не посылает.- Scriptor от Cavedog подставляет вместо
CURRENT_SPEEDлитерал29, из-за чего скрипты в стилеMoveWatcherломаются после round-trip. Декомпилятор kbot восстанавливает символьное имя; если без round-trip через Scriptor не обойтись, ставьте скобки:(get CURRENT_SPEED) > 5. - Смещения переходов — это смещения в байтах относительно
OffsetToScriptCode, а не относительно текущей инструкции.JUMP 0x4Cозначает «перейти кOffsetToScriptCode + 0x4C». - Номера портов
GET_UNIT_VALUEв TA и TA:K различаются. Каталог TA (декомпилятор kbot подписывает порты 1–20) описан в Приложении B.
GAF — анимации спрайтов
- Последовательность зацикливается, только если установлен младший байт её слова по смещению +2; при 0 она проигрывается один раз и останавливается. Во всех стандартных последовательностях хранится 1, так что инструмент, записывающий ноль, замораживает анимированные объекты.
- Кадры в
textures/*.gafиanims/vismasks.gafдолжны быть без сжатия. Игра читает их как простые массивы пикселей. - У некоторых последовательностей длительность кадров равна
0; такой кадр игра показывает один тик. Не «исправляйте» их на значение по умолчанию. - Индекс палитры 0 сам по себе не прозрачен. Прозрачны только ключевой цвет кадра без сжатия и серии пропусков сжатого кадра.
- Строки пикселей сжатого кадра могут быть короче
Width. Игра читает дальше, в следующие байты; kbot дополняет строку прозрачными пикселями и выдаёт предупреждение. В стандартных файлах таких строк нет. - Точки привязки слоёв — это активные точки (hotspot): каждый слой составного кадра ставится так, чтобы его точка привязки совпала с точкой привязки кадра.
- У
anims/terrain.gafиanims/vismasks.gafв заголовкахVersion == 0. Игра слово версии игнорирует. .tafв TA: Kingdoms бывает двух видов..tafс палитрой — это и есть GAF: тот же формат на диске, только TAK берёт палитру из.pcxстороны, а не из глобальной палитры TA. Полноцветный.tafиспользует тот же контейнер, но хранит 16-битные пиксели ARGB, и палитра ему не нужна. Решающее значение имеет байт формата кадра (0/1— с палитрой,4/5— полноцветный), а не расширение — см. TAF / TSF.
TAF / TSF — полноцветные анимации
- У
.tafдва значения — с палитрой (читается как GAF) и полноцветный (читается черезkbot taf). Всегда проверяйте байт формата кадра, а не имя файла, хотя большинство полноцветных файлов TAK по соглашению носят суффикс_4444/_1555. - Экспорт в GIF идёт с потерями. GIF хранит только однобитную маску
и 255 цветов, так что мягкая альфа ARGB4444 превращается в жёсткие
края. Чтобы сохранить настоящий альфа-канал, используйте APNG
(
kbot taf export --format apng). - В поле имени кадра и байте флагов
0x0Bможет быть мусор. Сохраняйте их как есть ради побайтно точного round-trip; не нормализуйте. Delay/Duration, равные0, допустимы — как и в GAF, кадр длиной ноль тиков управляется событиями (так сделаны статичные фоны меню). Не «исправляйте» его.
PCX — изображения
- TA игнорирует
BytesPerLine. Стандартные читатели отбрасывают заполнение, когдаBytesPerLine > Width; TA 3.1c декодирует ровноWidthбайт на строку, поэтому в файлах с заполнением строки в игре съезжают. ЗаписывайтеBytesPerLine == Widthи проверяйте черезkbot pcx describe. - TA загружает только версию 5 и декодирует любой файл как 8-битные данные в одной плоскости.
- Литеральные байты с установленными двумя старшими битами нужно
экранировать через RLE. Голый байт со значением
0xC0–0xFFкак литерал недопустим; записывайте его как серию длиной 1 (0xC1, 0xFFдля одного0xFF). Если этого не сделать, декодирование рассинхронизируется после первой же строки. - Игра берёт последние 768 байт как палитру — с маркером
0x0Cили без него; стандартным читателям маркер нужен.
PAL / ALP / LHT / SHD — палитры и таблицы поиска
- Четвёртый байт записи — НЕ альфа-канал. Это неиспользуемый байт
заполнения, который Cavedog всегда ставила в
0x00. Открытые инструменты для палитр, считающие его альфой, отказываются отображать палитру (alpha=0 → всё прозрачно). - Индекс 0 — чёрный, а не прозрачный. Рельеф, миникарты и фоны рисуют его непрозрачным; спрайты сами выбирают свой ключевой цвет прозрачности.
- Пустой или отсутствующий
palette.palзаменяется наpalette.pcx(его последние 768 байт);.palдлиннее 1 024 байт читается до 1 024-го байта. .alp/.lht/.shd— НЕ палитры RGB:.alp— таблица 256×256, а.shd/.lht— таблицы 32×256 из индексов палитры, и таблицу любого другого размера игра игнорирует (и строит заново). Отрисовка черезkbot pal describeпокажет мусор; используйтеkbot pal lookup.- В палитре TA 13 повторяющихся троек RGB. Если ваш редактор при импорте удаляет повторы (некоторые так делают), записи пропадут, и палитра незаметно станет палитрой на 243 записи.
FNT — растровые шрифты
Offsets[c] == 0означает «нет глифа», а не «глиф по смещению 0». Смещение 0 попадает в середину заголовка — Cavedog использует ноль как сигнальное значение.- Порядок битов — от старшего (MSB-first) внутри каждого байта. В современных растровых шрифтах чаще встречается LSB-first, так что не полагайтесь на догадки.
- Битовый поток непрерывен между строками — в конце строки развёртки нет выравнивания.
Профили ИИ
- В файлах ИИ TA: Kingdoms нет директив
plan— только строкиweight/limit. Парсер kbot открывает неявный планdefault; читатели должны спокойно относиться к отсутствию этого блока. См. TA:K — профили без plan. - Никакой проверки нигде нет. Имена юнитов с опечатками, сломанные
значения весов, строки вне
plan— всё молча игнорируется. Первый признак сломанного профиля — «ИИ ведёт себя странно», и никаких сообщений в журнале. - Псевдонимы категорий (
PLANT,LEVEL3) «существуют», только если хотя бы один юнит объявляет их вCategory=. Ошибитесь в написанииLEVL3— и директива молча ничего не сделает.
Звуки
- Отсутствующий WAV — это тишина, а не ошибка. Опечатайтесь в
select1=cmsdel1(переставлены буквы), и движок ничего не воспроизведёт — ни записи в журнале, ни замены. - Расширение
.wavв значениях TDF подразумевается — никогда не пишите его.select1=cmdsel1.wavпревращается вsounds/cmdsel1.wav.wavи ничего не находит.
Особенности TA: Kingdoms
- TAK требует аргумент командной строки
-disablecavedogverification, чтобы загружать большинство изменённых файлов. Без него движок отвергает всё, чей хеш не входит в его список разрешённых. - В данных TAK 8 кодов сторон, а не 4: ARA / TAR / VER / ZON для четырёх играбельных сторон, плюс CRE (Creon, Iron Plague), MON (монстры), LIF (дикая природа), NPC (персонажи кампании). Фильтрация списков юнитов должна учитывать их все.
- Оружие TAK описано прямо в каждом FBI — подсекциями
[WEAPON1]–[WEAPON3], а не в отдельном каталогеweapons/*.tdf. - Меню строительства в TAK линейные, а не 2×3. У каждого строителя
есть каталог
canbuild/<builder>/<unit>.tdfс единственным блоком[Menu] { Priority = N; }.
См. также
- Руководство по моддингу — пошаговое создание мода с юнитом для TA от начала до конца.
- Глоссарий — определения всех общих для форматов терминов, упомянутых выше.
- Быстрый старт — пять команд kbot для начала.