Все страницы форматов ↓
Руководства и справка

Типичные подводные камни моддинга и разбора файлов

Источник: 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; }.

См. также