☰
异星工厂蓝图字符串解码与批量修改:实体与电路网络
2026/10/28 4:23:14 网站建设 项目流程

玩异星工厂玩到一定年头,迟早会走到这一步:手里攒了几十个蓝图,每个都想改一点——信号名换一套、阈值统一调、组装机配方整批替换,然后在游戏里一张一张点开、一个一个改,改完发现漏了两处,再重来。我最初也是这么硬点的,直到某天一个 800 实体的总线蓝图改到第四遍,才决定把蓝图字符串拆开来看。这篇文章讲的就是这条路:把压缩过的蓝图字符串还原成看得懂的结构,在结构层面做批量修改,再重新打包回能被游戏吃下去的字符串;电路网络那一块单独展开,因为它是整条链路上最容易改坏、也最容易省事的部分。不管你是刚学会导出蓝图、还没碰过字符串的新手,还是已经能写点脚本、想搞清楚字段结构的老玩家,下面的内容都能直接照着用。

1. 蓝图字符串里到底塞了什么东西

先把一个最基本的事实说清楚:那串看起来像乱码的长字符串,从头到尾只有三个处理步骤,没有任何加密。

版本字节 + base64 编码的 zlib 压缩数据

就这样。游戏导出蓝图时,会先把整个蓝图结构序列化成一段二进制,用 zlib 压一遍,再套一层 base64,最后在前面补一个字符表示格式版本。导入时反过来做一遍。明白了这条链路,剩下的一切都只是"那段二进制里有什么"的问题,而这个问题可以一层一层剥。

1.1 首字符不属于 base64,这是最常见的解码错误

我第一次写解码脚本时就栽在这里。直接拿整串去 base64 解码,报错;去掉首字符,成功。原因很简单:游戏在字符串最前面放了一个版本标记字符,早期是0,后来出现过别的值。这个字符参与 base64 的字符集,所以有些在线工具不报错,只是解出来一堆垃圾数据——这比直接报错更坑,因为你会以为压缩算法有问题,方向全跑偏。

正确顺序是:先去掉全部空白字符(复制粘贴经常带换行),再剥离首字符,剩下的才是真正的 base64 载荷。

import base64, zlib def decode_blueprint_string(s: str): s = "".join(s.split()) # 去掉换行、空格、制表符 ver = s[0] # 版本字节,单独保存 raw = base64.b64decode(s[1:]) data = zlib.decompress(raw) return ver, data

这里还有一个容易忽略的点:base64.b64decode默认会静默丢弃非法字符,所以字符串里混进了标点或者中文引号时它不报错,只是解出一段莫名其妙的数据。加上validate=True能强制校验,调试阶段建议打开,能省掉大量"明明代码没错结果就是不对"的时间。

反方向的打包同样简单,压缩级别用默认值即可,游戏侧不关心你用了几级压缩,只要 deflate 结果能被正确解压:

def encode_blueprint_string(ver: str, data: bytes) -> str: return ver + base64.b64encode(zlib.compress(data)).decode("ascii")

1.2 解开 zlib 之后看到的是一套带字段号的结构

压缩层剥掉,得到的就是一份二进制数据,它采用的是一种非常常见的序列化方式:每个字段前面带一个"字段号 + 数据类型"的标记,后面跟值。字符串和嵌套结构用长度前缀,整数用变长编码,浮点用定长。

这意味着什么?意味着你不需要拿到官方的字段定义文件,也能把整份数据完整解析出来。你不知道某个字段号叫recipe还是recipe_name,但你能看到字段 4 是一个字符串、值是"iron-plate",结合上下文基本就能猜出来。这是我在没有文档的情况下摸清结构的主要手段,也是我认为这条路最适合普通玩家的原因:完全可以靠样本反推。

一份蓝图顶层通常只有一二十个字段,其中绝大部分是重复出现的子结构。我按实际解码经验整理了一个高置信度的字段对照,其余字段建议自己拿一张小蓝图试出来:

顶层字段号含义类型备注
1item字符串普通蓝图是blueprint,蓝图书是blueprint-book
2label字符串蓝图显示名称,长度有上限
3label_color结构图标底色,可省略
4entities重复结构所有实体,这是批量修改的主战场
5tiles重复结构铺设的地砖,只有坐标和名称
6icons重复结构右上角图标,最多四个
7schedules重复结构列车时刻表,跨蓝图复制时经常被忽略

实体层里,字段 1 是实体编号(一个从 1 开始的整数)、字段 2 是实体名称、字段 3 是坐标(含 x 和 y 两个浮点)、字段 4 是朝向(0、4、8、12 分别对应四个方向)。这四个字段覆盖了 80% 的批量操作需求,剩下的配方、库存、控制行为、连线,字段号靠实测更靠谱。

1.3 用通用解析器"盲解",不用字段定义也能看懂

既然结构自描述,写一个通用的解析器就够了。核心是一个变长整数读取函数,加上按数据类型分支的循环:

def read_varint(buf, i): result, shift = 0, 0 while True: b = buf[i]; i += 1 result |= (b & 0x7F) << shift if not (b & 0x80): return result, i shift += 7 def parse_message(buf, depth=0): fields, i = [], 0 while i < len(buf): key, i = read_varint(buf, i) field, wire = key >> 3, key & 7 if wire == 0: val, i = read_varint(buf, i) elif wire == 2: ln, i = read_varint(buf, i) val = buf[i:i + ln]; i += ln elif wire == 1: val = buf[i:i + 8]; i += 8 elif wire == 5: val = buf[i:i + 4]; i += 4 else: raise ValueError(f"未知类型 {wire},数据可能已损坏") fields.append((field, wire, val)) return fields

对于wire == 2的那一类,值既可能是字符串也可能是嵌套结构。判断方法很土但有效:递归解析一遍,如果能完整消费且所有字段号都大于零,就当它是个子结构;否则按文本处理。这个启发式方法在蓝图数据上几乎不会误判,因为字符串内容里出现能凑成合法结构的字节序列的概率极低。

跑通之后,你可以选择把结果打印成一棵缩进树,或者转成 JSON 直接扔进任意编辑器。我个人的习惯是转 JSON 并用支持折叠的编辑器看,800 个实体的蓝图也就几兆体积,翻起来毫无压力。

2. 把二进制翻成可编辑结构:解码层的三个取舍

解析出字段只是第一步,真正决定后面好不好用的是"中间表示"怎么设计。同一个蓝图,你可以把它表示成带字段号的原始列表,也可以表示成带名字的对象树,两者的开发体验差得非常远。

2.1 我不建议从字段定义文件入手

社区里确实有整理好的字段定义,直接生成代码看起来很省事。我试过这条路线,结论是性价比不高,原因有三个。

第一是版本漂移。这套结构会随着游戏版本增加字段,定义文件往往滞后,你拿到的版本和你游戏里导出的蓝图对不上,解析时遇到未知字段要么报错要么静默丢弃,后者更危险。第二是定义文件通常只覆盖常用字段,冷门实体(比如某些带特殊控制行为的建筑)的字段可能缺失,而恰恰是这些地方你最需要改。第三,也是最重要的:你的需求是"改",不是"完整还原"。你只需要能定位到想改的那个字段,其它字段原样保留即可,根本没必要把它们全部理解清楚。

所以我最后采用的方案是"保留原始字节 + 附加解读":解析时把每个字段的原始字节切片也存下来,修改时只替换目标字段的值,其余字段原封不动地写回去。这样即使有完全不认识的字段,也不会在往返过程中丢失。

2.2 实体表、地砖表、连线表要分开建索引

蓝图里最容易出错的地方是引用关系。实体之间通过实体编号互相引用,连线、控制行为、参数化替换,全是靠编号串起来的。我在中间表示里建了三张索引表:

  • 实体索引:编号 -> 实体对象,用于连线解引用。
  • 坐标索引:(x, y) -> 实体编号列表,用于按区域框选、检查重叠。
  • 连线索引:实体编号 -> 出线列表,每条线记录对端编号、线色、连接点。

有了这三张表,批量操作就从"遍历数组"变成了"查表 + 改字段",代码可读性完全不是一个级别。举个具体例子:把某个区域内的所有实体整体平移 10 格,需要改动的是所有实体的坐标,而连线不需要动——因为连线引用的是编号,编号不变,拓扑关系自动跟着走。这个特性非常关键,它意味着平移、复制、旋转这类操作在结构层面是廉价的,真正昂贵的只有"删实体"和"改实体类型",因为它们会破坏编号引用。

2.3 编码回写时最容易翻车的三处

打包回去的时候,有三件事必须检查,我基本每次写新脚本都会在其中至少一项上栽一次。

版本字节必须和内部记录一致。顶层结构里通常有个字段单独记录格式版本,字符串首字符是另一个。这两个值不匹配时,有些版本会直接拒绝导入,有些不报错但字段解释错位,表现是蓝图导进去之后颜色全乱或者实体错位。我的做法是:解码时把首字符和内部版本号都存下来,编码时用解码得到的首字符,绝不自己编一个。

坐标浮点精度要够。坐标是浮点数,而游戏里的实体位置大量出现.5这种半格偏移。如果你在中间表示里用了单精度或者只保留两位小数,往返几次之后位置就会漂移,最终表现为实体重叠、传送带接不上。用双精度,或者更稳妥的做法是把坐标量化成 1/256 格的整数,全程整数运算,回写时再除以 256。

空字段和默认值不要瞎填。有些字段存在与否会改变游戏行为,比如控制行为的disabled标志。你的脚本如果无脑给每个实体都写一个false,绝大多数情况下没问题,但某些实体类型不接受这个字段,导入时就会报实体无效。原则很简单:只改你确认要改的字段,其余保持原样。

3. 批量修改:把"逐个点"变成"写一条规则"

批量修改的价值不在于省时间,而在于保证一致性。手动改 200 个实体,一定会有几个改漏或者改错,而这种错误在游戏里往往要跑上几十分钟才会暴露。写成规则跑一遍,要么全对,要么全错,至少你知道该从哪查。

3.1 实体替换的适用边界

最常见的需求是替换实体类型。比如把所有某型号的组装机换成另一种,或者把一批箱子统一换成某种规格。这个操作在结构层面就是改实体名称字段,看起来毫无难度,但有几个隐藏约束。

新实体必须能容纳原有实体的所有附加信息。原来的机器里有配方、有模块、有库存,换成新实体之后如果不支持这些字段,游戏导入时会清洗掉或者直接判无效。我的经验是先拿一个样本改一次、导入验证,确认没问题再批量跑。另外占地尺寸变化会导致重叠:把一个 3x3 的实体换成 5x5,周围原本紧挨着的实体就会压在一起。脚本里加一个简单的包围盒检测,把所有潜在重叠的实体编号列出来人工过一遍,比导入之后在游戏里找哪两块红了要高效得多。

配方的批量替换更安全一些,因为只改字符串字段,不涉及几何。但要注意配方和机器的匹配关系:把配方改成某个机器不支持的类型,游戏会接受这份蓝图但机器是空转的,等于埋了个隐形雷。

3.2 平移与复制必须做编号重映射

复制一段实体(比如把一条产线复制成四条)是批量操作的典型场景,也是最容易写出 bug 的地方。核心步骤只有三步,但每一步都不能省:

  1. 遍历选中实体,坐标按偏移量整体平移。
  2. 为每个新实体分配全新的实体编号,并建立"旧编号 -> 新编号"的映射表。
  3. 遍历选中实体内部的所有连线,把两端的编号按映射表替换,指向选区外部的连线直接丢弃。

第三步经常被忘掉,忘掉之后的表现是:导入成功,但新复制出来的实体带着一堆指向旧实体的连线,红绿线在空中乱拉,整个电路网络全乱。我建议把这三步封装成一个函数,任何复制操作都走它,别图省事直接改坐标。

还有一个细节:平移量必须是整数或者半整数。有些实体(比如奇数格宽的机器)的中心点在整数坐标上,偶数格的则在半格上。平移时如果加了 0.3 这种量,实体会被判为无法放置。

3.3 参数化蓝图和图标会连带失效

如果你的蓝图里用了参数化(把某些信号、配方、数量做成可以在放置时选择的变量),批量修改必须同步处理相关的引用字段。这类蓝图里的参数用特殊名字标记,实体里凡是引用到参数的地方都存着这个名字。你如果只改了实体里的实际值,参数定义没跟着改,导入后参数选择就失效了,蓝图退化成一堆固定值。

图标和标签也值得顺手处理。批量生成的系列蓝图(比如"一级到五级"),如果标签不跟着流水号改,最后你会得到五个名字一模一样的蓝图,找起来非常痛苦。标签字段是纯字符串,加个自增后缀就行;图标也是一个重复结构,通常按索引顺序引用物品,改动不大。

4. 电路网络的批量调整

电路部分是我认为整条链路上收益最高的地方。原因很实在:电路逻辑是"参数化"的,同样的结构换个数值就能复用,而手动改数值恰恰是最枯燥、最容易改错的。

4.1 控制行为是一棵条件树,不是扁平字段

控制行为是每个实体内部的一个子结构,它内部按实体类型分成不同的分支:常量箱用一套、判断器用一套、算数运算器用一套。批量调整时,脚本的第一步永远是按实体名称筛选,而不是按字段号硬改。

信号本身的表示是个小结构,里面通常有信号名、信号类型(物品、流体、虚拟信号)这两个关键字段。这里的信号名对虚拟信号来说是形如signal-A、signal-each这样的字符串,跟真实物品名混在一起。做批量替换时,最容易犯的错是把signal-A换成signal-a之类的大小写错误,或者把物品信号和虚拟信号张冠李戴——类型字段不改,只改名字,导入时这个信号就变成未知信号,游戏会静默把它变成问号。我的做法是维护一张"名字 -> 类型"的对照表,所有替换都查表进行,查不到的直接报错中断,宁可失败也不要不声不响地写进去。

常量箱还额外多一层结构:较新的版本里它被拆成了若干"分组",每组有自己的开关和一组常量。批量修改时如果只遍历最外层,会漏掉分组里的内容。判断方法是先拿一个多组常量箱的样本解码一次,看清楚层级再写循环。

4.2 连线的红绿两色是独立编号

电路连线的两个端点,各自带一个编号来区分红线和绿线。这意味着同一个实体对之间可以同时有红线和绿线两条连接,脚本在处理重连时必须把颜色一起带上,否则会出现"想加一条红线,结果把原来的绿线覆盖了"的情况。

批量重连的典型场景是:你有几十个判断器,全部接到了同一个网络的信号源上,现在想把其中一半挪到另一个信号源。做法是先按坐标范围筛出要改的实体,收集它们的出线,把指向旧源头的线删掉,再按新源头重新生成。删线的时候要注意线是双向记录的还是单向记录的——我遇到的实现基本都是单个端点记录对端编号,也就是成对存在,只删一侧会留下悬空引用。写脚本时统一用"先收集所有待删的配对,再一次性双向删除"的流程,比边遍历边删稳。

4.3 一个实际改造:整版阈值统一放大

说个具体的例子。我有一张按信号阈值触发开关的控制蓝图,里面三十多个判断器,阈值都是> 1000。后来上游产能扩大,需要全部改成> 2500。

流程是这样的:解码,按实体名称筛出判断器,遍历每一个的条件树,找到条件里那个数值字段,判断它是否等于 1000,等于才替换成 2500,其它一律不动。跑完重新编码,导入游戏验证。

这里的关键在于"等于才替换"这个判断。如果无脑把所有数值乘以 2.5,那些本来就不该动的常量(比如输出信号里的固定值)也会被改掉,一版脑子正常运转的电路瞬间变成噪声发生器。批量操作永远应该有精确的匹配条件,而不是模糊的变换规则——这是我在被自己的脚本坑过好几次之后定下的规矩。

验证环节也有技巧:改完之后先别急着导入主存档。开一个测试存档导进去,用信号发生器把各种边界值打一轮,看输出对不对。这个过程大概五分钟,比在主存档里发现问题再回滚要省事得多。

5. 可视化这一层,我最后是怎么搭的

"可视化"这个词听起来唬人,实际做起来比你想象的简单——因为你不需要做得多好看,你只需要"能看明白自己在改什么"。

5.1 渲染与编辑的最小可用形态

我的第一版编辑器只有两个面板:左边一个画布,右边一个树形结构。画布做的事非常朴素:按坐标把每个实体画成一个带颜色的方块,方块上写实体名称的首字母缩写,鼠标悬停时在状态栏显示完整信息。就这样一个东西,已经能解决 90% 的问题——你不用在几百个实体里靠编号猜哪个是你想改的。

坐标到屏幕的映射是纯线性的,缩放倍数控制在 0.25 到 4 倍之间。这里有一个坑:网格线一定要画,而且一定要和实际坐标对齐。我一开始没画网格,结果平移操作之后完全无法判断有没有偏移半格,因为这个错误在视觉上非常隐蔽。

选中逻辑用矩形框选,框选结果除了返回实体编号列表,还顺带返回它们占据的包围盒,这个包围盒后面批量平移直接能用上。右键点击实体弹一个小菜单,里面就几个动作:改名称、改配方、看连线、删掉。功能越少越好,因为真正复杂的批量操作我会切回脚本里写规则,图形界面只做快速定位和单点修改。

5.2 导入前的自检清单

不管用什么方式改,重新编码之后我都会跑一遍自检。这份清单是从无数次导入失败里攒出来的:

检查项判据不通过的后果
实体编号唯一性无重复、无零值连线错乱或实体丢失
连线引用完整性所有端点在实体索引里存在悬空连线,导入报错
实体重叠包围盒两两不相交导入后被判无效或自动剔除
坐标量化全部落在 1/256 格点上位置漂移,传送带接不上
版本字节一致首字符与内部版本号匹配导入被拒或解释错位
标签与图标数量图标 1 到 4 个图标显示异常

这些检查本身都不复杂,加起来不到一百行代码,但它们能把"导入失败"这个模糊的结果变成一条明确的信息。花这半小时非常值。

6. 文档里不会写的几个手感问题

前面讲的是怎么做,这一节讲的是我做完之后回头看,觉得当初应该早点知道的东西。

别用真实存档做实验。这条听起来像废话,但我真的见过改坏蓝图之后还在同一个档里反复导入的情况。批量操作的正确姿势是:所有验证都在测试存档里做,主存档只用最终确认过的成品。蓝图本身不占多少空间,多存几份中间版本,出问题能立刻回退。

游戏对空白字符的容忍度比你想的高,对你的脚本却不一定。从聊天框或者网页复制蓝图字符串时,中间很可能会混进换行和空格。游戏自己会清洗,所以你看不出问题;但你的脚本如果没做清洗,就会在后面某个环节莫名其妙地失败。解码入口第一行就把所有空白字符去掉,成本为零。

大蓝图会撞到实体数量上限。批量复制的时候尤其要注意,一个原本 2000 实体的蓝图复制四份变 8000,再叠一层就超了。具体上限随版本变化,但那种"编码成功、导入失败、提示信息还很含糊"的情况,八成是这个原因。脚本里加一个数量断言,比事后排查省心。

改动日志比你想的重要。我给自己的脚本加了个简单的日志:每次修改都记录"改了什么、改了哪些实体编号、原值是什么"。一开始觉得多余,直到有一次网络调错了想回滚,靠日志三分钟就定位到了问题所在。

从最小样本开始。学习这套结构最有效的方法,是拿一个只有两三个实体的蓝图反复解码、修改、导入,把每个字段改一次看效果。拿大蓝图练手的结果是你根本不知道是哪一处改动导致的异常。

这套东西搭起来之后,我改蓝图的流程彻底变了:以前是打开游戏、找实体、点开面板、改数值,现在是把字符串粘进脚本、写一条筛选规则、跑一遍、粘回游戏。一个下午的工作量压缩到几分钟,而且改完之后的一致性有保证。真正花时间的部分反而前移了——变成"想清楚到底要改什么",而这个部分本来就不该交给工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询