所有格式页面 ↓
教程与参考

制作模组与解析格式时的常见陷阱

来自 coreprime/kbot @ 0ab97bf · 在 GitHub 上编辑

这是一页速查表,汇集了散布在 kbot 各格式文档中、最常让人中招的坑。如果你遇到了一个莫名其妙的 bug,就从这里开始。每一条都链接到相应格式页面上更深入的解释。

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)。
  • 横扫千军:王国使用 HPI v2(Version == 0x00020000),布局不同——没有 XOR,每个文件只有一个 SQSH 块。在 TAK 压缩包上尝试 v1 解析器之前,请先阅读横扫千军:王国 HPI v2 一节。
  • 带 BANK 标记的游戏存档完全是另一回事——它们沿用 HPI 的封装,但内容是序列化的游戏状态,而不是文件系统。kbot 不解码它们。
  • .gp3 需要 rev3.1 补丁。 它内部仍然是 HPI,但未打补丁的 TA 拒绝挂载它。

TDF / FBI / OTA

  • 每个值都必须以 ; 结尾。 漏掉分号会悄无声息地把多行的值合并在一起,产生错得离谱的属性。这是“我的单位生命值很怪 / 描述丢了 / 造价不对”的头号原因。
  • 键不区分大小写,值区分。 Side=arm 对大多数查找都有效,但任何对 Side 做字符串相等比较的模组代码都会出错。
  • 列表值以空格分隔(Category=COMMANDER MOBILE WEAPON)——而不是逗号。例外是 OTA 的 numplayers=2,4,6,8,10,它用逗号。
  • 布尔值是整数 1/0,而不是 true/false。引擎只接受整数形式。
  • 制作模组时,绝不要为了添加单位而修改 sidedata.tdf。 改用 download/<UNITNAME>.tdf 中的 [MENUENTRY] 注册——即 Core Contingency 所用的同一机制。见 TDF:[MENUENTRY] 和模组教程。
  • Bmcode=0 会把单位从建造菜单中隐藏,哪怕其他一切都有效。你想要的几乎总是 Bmcode=1。
  • 没有列在任何建造者名下的单位无法被建造,即使它的 FBI 在其他方面都有效。这是最常见的“我的新单位存在但造不出来”的 bug。见模组教程第 5 步。
  • 嵌套节的深度是惯例,不是规范。 有些工具假定最多 2 层;武器 TDF 有 3 层([WEAPON][DAMAGE][category])。

TNT / SCT——地图地形

  • TNT 的 Width 和 Height 以 16 像素的属性格为单位,既不是图块也不是像素。除以 2 得到图块数;乘以 16 得到像素尺寸。见 TNT 文件头。
  • SeaLevel == 0 并不表示“没有水”——它表示最小的可能值恰好在阈值上,所以没有任何地方在水下。真正干燥的地图请使用 SeaLevel = 1。
  • TNT 的地图物件字只有小于地图物件表条目数时才会放置地图物件。 0xFFFC 是空白格;0xFFFE(Lava Run、AC02)、0xFFFD 以及其他任何达到或超过表大小的字都不放置任何东西,该格子仍然可以建造。
  • TNT 小地图的填充使用调色板索引 0x64,地图区域的大小由地图本身算出(较长的一边为 252,保持可见地图的宽高比),而不是通过扫描填充来确定。只有当文件头中 0x2c 处的字的第 0 位置位时,游戏才会读取存储的小地图。见 TNT。
  • 横扫千军:王国的 .tnt 使用 IDVersion == 0x4000,而不是 TA 的 0x2000(TA 也能读取较早的 0x1020 布局)。kbot tnt image 对 TAK 的 TNT 只能输出乱码;图块解码器是 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 渲染 bug。
  • 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。lint 规则 signal-never-signalled 会捕捉设置了掩码却从来没有人发出相应信号的脚本。
  • Cavedog 的 Scriptor 把 CURRENT_SPEED 内联为字面量 29, 导致 MoveWatcher 一类的脚本在往返之后出错。kbot 的反编译器会恢复符号名;如果必须经过 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;游戏把这样的帧显示一个 tick。不要把它们“修正”成某个默认值。
  • 调色板索引 0 本身并不透明。 只有未压缩帧的透明色键和压缩帧的跳过游程才是透明的。
  • 压缩的像素行可能短于 Width。 游戏会继续读进后面的字节;kbot 用透明像素补齐该行并给出警告。没有任何原版文件存在这样的行。
  • 图层原点就是热点:合成帧的每个图层的放置方式,是让它的原点落在该帧的原点上。
  • anims/terrain.gaf 和 anims/vismasks.gaf 的文件头中 Version == 0。 游戏忽略版本字。
  • 横扫千军:王国的 .taf 有两种。 带调色板的 .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 只能容纳 1 位镂空和 255 种颜色,因此 ARGB4444 的柔和 alpha 会变成硬边。要保留真正的 alpha 通道请用 APNG(kbot taf export --format apng)。
  • 帧名称字段和 0x0B 标志字节可能含有垃圾数据。 要实现逐字节一致的往返,就原样保留它们;不要规范化。
  • Delay / Duration 为 0 是合法的——与 GAF 一样,零 tick 的帧由事件驱动(静态的菜单背景就这样用)。不要“修正”它。

PCX——图像

  • TA 忽略 BytesPerLine。 当 BytesPerLine > Width 时,标准读取器会丢掉填充;TA 3.1c 每行恰好解码 Width 个字节,所以带填充的文件在游戏中会错位。请写入 BytesPerLine == Width,并用 kbot pcx describe 检查。
  • TA 只加载版本 5,并把每个文件都当作 8 位单平面数据解码。
  • 最高两位被置位的字面字节必须经过 RLE 转义。 值为 0xC0–0xFF 的裸字节不能作为字面量出现;要把它写成长度为 1 的游程(单个 0xFF 写作 0xC1, 0xFF)。处理不当会使解码在一行之后失去同步。
  • 无论有没有 0x0C 标记,游戏都把末尾的 768 字节当作调色板;标准读取器则需要这个标记。

PAL / ALP / LHT / SHD——调色板与查找表

  • 每个条目的第 4 个字节不是 alpha。 它是一个未使用的填充字节,Cavedog 总是把它设为 0x00。把它当作 alpha 的开源调色板工具会拒绝渲染这个调色板(alpha=0 → 全部透明)。
  • 索引 0 是黑色,不是透明。 地形、小地图和背景都把它画成不透明;精灵图各自选择自己的透明色键。
  • palette.pal 为空或缺失时,会由 palette.pcx(它的最后 768 字节)代替;长于 1,024 字节的 .pal 只读到第 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);不要想当然。
  • 位流在行与行之间是连续的——每条扫描线的末尾没有填充。

AI 配置文件

  • 横扫千军:王国的 AI 文件没有 plan 指令——只有光秃秃的 weight/limit 行。kbot 的解析器会隐式打开一个 default plan;读取器应当容忍缺失的块。见 TA:K——没有 plan 的配置文件。
  • 任何地方都没有校验。 拼错的单位名、损坏的权重值、写在 plan 之外的行——全都被悄无声息地忽略。配置文件损坏的第一个迹象是“AI 行为古怪”,而且没有任何日志消息。
  • 类别别名(PLANT、LEVEL3)只有在至少一个单位在 Category= 中声明了它们时才“存在”。 把它拼错成 LEVL3,这条指令就会悄无声息地什么也不做。

音效

  • 缺失的 WAV 只会无声,不会报错。 把 select1=cmsdel1 拼错(字母颠倒),引擎就什么也不播放——没有日志,也没有后备。
  • TDF 值中隐含了 .wav 扩展名——绝不要写出来。select1=cmdsel1.wav 会变成 sounds/cmdsel1.wav.wav,什么也找不到。

横扫千军:王国的特有问题

  • 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 命令。