这条在讲怎么把 Factorio 的随机数状态整个算出来,然后造一个全自动生产传奇物品的系统。出处是 gegell.github.io,标题叫 Reversing Factorio's RNG,2026 年 8 月底发的。这类"破解游戏随机数"的帖子我一般直接跳过,大部分写着写着就变成作弊教程,点开就后悔。这篇我读到作者拆 RandomGenerator::getInt 那一段才确认要推。
他没用 CE 扫内存改值那套。把游戏二进制里的调试符号翻出来,先跑 Ghidra,后来换 Binary Ninja,在代码里找到随机数生成器类。关键发现是 getInt 就是拿三个 seed 异或一下。这个细节不点开看不到,光看现象你只知道"品质会随机升级",看不到底层是三个 LFSR 在转。
追回来源头的过程我挺喜欢。不是从二进制硬啃,是先找到 2014 年 Wube 前开发者在论坛里的回帖。Cube 说选 taus88 就是因为它是在 Boost 生成器里跑得最快的,还提过想砍掉一个 LFSR,后来发现随机数不是性能瓶颈就没动。等于作者不是从零逆向,是把十年前一句"我们当年图快"当线头,再回去验证二进制。这个链条读起来顺,而且让整件事不像技术魔术——就是有人把一句早已公开的线索认真往下追了两年多。
数学部分我其实有点走神。taus88 三个 LFSR 分别用 31、29、28 位,但寄存器按 32 位字存,所以完整 32x32 矩阵秩不够。作者把它缩到对应位数才恢复满秩,然后说观察三次连续完整输出就能解线性方程组恢复状态。我看到这儿满脑子想的是——如果 Wube 一开始换个有密码强度的 PRNG,这套活是不是全白干。但我压下没细想,继续读后面工程部分,反而更值得点。
因为实际在游戏里你根本拿不到"连续三次完整输出"。随机调用被各种东西打散,而且很多接口不给你看到完整 32 位。作者在这块像解一道信息论应用题。他用铀加工观察概率:U-235 概率 0.7%,U-238 概率 99.3%,每次加工两次 RNG 查询。如果没出 U-235,就能推出第一次 roll 的高位信息。再往下,他把每个配方的期望信息量当香农熵离散化算一遍,做了张表。废料回收每次操作给 2.05 bit 信息,炼油厂 4.5 bit。最后选定一个单元每次 2 bit,要 44 次才凑够 88 bit 重建状态。这个选择标准很务实——不是信息峰值最高,而是能稳定规模化复制。
到这儿我还觉得是个理论可行的东西。但作者真在游戏里搭了 44 个回收单元,用 1 tick 延迟和同 tick 技巧控制查询顺序,再预计算伪逆矩阵,最后在游戏内用决定器和红绿线做 GF(2) 矩阵向量乘法。这套东西已经不像正常玩家搞的了,像直接在游戏基址上搭了台小计算机。
然后是那个让我停下来叹气的段落。作者说想预测品质,就在装配机旁放了个像 Tetris Next Up 队列的显示,把下一次升级会不会出、出什么品级提前画出来。不是改随机数,是看得比系统早一步。看到这儿我有点羡慕,不是技术上的,是这种把一个纯粹为了乐趣的东西做到这种完整度的劲儿。文章还给了世界下载和蓝图。你打开一个存档,旁边一排组合器在那儿推你下一次制造会不会爆传奇,这事本身就很赛博。
2.1 的部分必须单独说。底层 taus88 还在,理论也没废,但游戏内实现被改岔了。物品制造逻辑重写,复用单次调用处理多个输出,导致那种"用废料回收跳过 RNG"的办法不复用。而且每次制造都会消耗 RNG,即使结果是确定性的。干过 mod 的都能懂这种痛:底层没变,可接口全变了,原来精巧的工程直接腰斩。作者没说漂亮话,直接写这方法对 2.0 有效,2.1 失效,理论仍适用。读到这种说明我反而更信整篇文章,因为他没为了故事漂亮把断掉的地方藏起来。
还有一个很小但重要的细节。文章提到 2.1 早期实验分支曾经让 generalRandomGenerator 控制鱼的运动,后来在 2.1.13 拆成独立的 fishRandomGenerator。我当八卦看的,但突然就理解了为啥 2.1 之后整个 RNG 状态跟踪那么难——所有东西都从同一个状态里抓,抓的人多了,你这边刚算好,一条鱼晃一下就把调用数带走了。原作者没点明这个,但确实是块有趣的拼图。
这篇最大的价值不是"预测品质"。是它把 PRNG 在纯本地游戏里的信任边界摊开了。Factorio 的随机不是加密,Wube 也没把它当秘密,所有随机都睡在 Map 的六个生成器里,谁读了谁就能推。作者做的事像把游戏里一扇从没人锁的门推开,然后回头量了量门到底能通到哪。
我对"全自动无需赌博"这个结论没什么激动。要做到他那个程度,得从开局就一路规划,后面再改造几乎没戏。更像一次景观级别的工程练习,不是能塞进你普通存档里的技巧。但这个景观本身值得花钱买门票看一次。
我没装 2.0,不能顺手跑一遍那个 LFSR 状态恢复的代码。有点遗憾,矩阵乘法的具体实现在我看来比结论有用。先记一笔,等哪天我手头有 2.0 存档,试试作者给的最小子集能不能只恢复状态不干别的。
