【游戏逆向笔记】 是 lxraa 的技术分享系列,记录”我”习得老爹的黑客技艺后,在游戏逆向领域的探索笔记,基于真实游戏逆向分析过程的半虚构日记式写作。
(友情提示)读完之后,你可能一时半会儿都不太想单纯「打游戏」了。
认识老张是在一年前的一个下午。
那天我出门溜达,路过一家咖啡馆,门口挂着横幅:”游戏AI的未来——技术沙龙”。
我本来对这种活动没兴趣,但看到门口桌子上摆着免费的咖啡和点心,就走了进去。
会场里坐了三四十人,台上有个西装革履的嘉宾正在讲PPT,满屏都是”深度学习”、”强化学习”、”神经网络”这种听起来很唬人的词。
台下一片昏昏欲睡。
我端了杯咖啡,拿了两块曲奇饼,打算找个角落坐着混一会儿就走。
结果刚坐下,旁边一个戴黑框眼镜的中年程序员突然凑过来,压低声音说:
“讲得都是屁话。真正的AI不在PPT里,在Roy*l M**的国王手里。”
我愣了一下,以为他在跟别人说话,没搭理,继续吃饼干。
结果他转过头盯着我:”你玩Roy*l M**吗?”
“玩啊,卡在64关三天了。”我随口回了一句。
他眼睛一亮,像是找到了知音:”那你有没有发现,螺旋桨棋子(propeller)很聪明?”
“还行吧,确实挺智能的。”我敷衍道,心想这人怎么回事。
“不是’还行’,是’太聪明了’。”他压低声音,神神秘秘地说,”我怀疑那个国王是真的AI。”
我笑了:”大哥,这就是个三消游戏,能有多智能?”
“你不懂。”他摇摇头,从口袋里掏出一张名片塞给我,”我叫张伟,做了十年游戏。Roy*l M**的螺旋桨棋子,不是规则引擎能做出来的效果。”
我看了眼名片:xx游戏,资深客户端开发工程师。
他说完又凑近了点,滔滔不绝地讲起他对Roy*l M**螺旋桨棋子的各种观察和猜测。
我本来想找个借口溜走,但他说得太投入了,根本不给我插话的机会。
就这样,我端着咖啡,硬生生听了他半个小时的”都市传说”。
散场的时候,他还意犹未尽:”加个微信吧,我还有很多东西想跟你讨论。”
我心想,赶紧应付完走人。
就扫了他的码,也没多说什么。
加完之后,两个月没联系过。
直到某天,我在一个游戏技术论坛上刷帖,看到一个题目很唬人的帖子。
楼主 ID 叫”老张不老”,头像一看,就是那天那个程序员。
他发了个很长的分析贴,从玩家角度推测某个热门手游用了什么”黑科技”,写得有理有据,下面一堆人点赞。
我看完笑了,直接回帖:用逆向分析验证了一遍,你想多了,就是普通的实现。
还把关键代码截图贴了上去。
老张当时估计挺尴尬的,但他没删帖,反而私信我:”你会逆向?”
“会一点。”
“能不能帮我看看别的?”
我想了想,反正闲着也是闲着,就答应了。
一来二去,我们就熟了。
老张这人,技术上确实有两把刷子,但有个毛病:特别喜欢脑补,总觉得游戏公司在背后搞什么骚操作。
不过他在游戏公司工作,经常能接触到一些很有意思的场景和案例。
我对这些挺感兴趣的——毕竟家里蹲能接触到的素材有限,能从一线开发者那里听到实际遇到的问题,还能自己动手验证,这对我来说挺有吸引力的。
所以每次他提出什么新的”推测”,我就帮他用逆向分析验证一下。
大多数时候,结果都是:”想多了。”
他也不生气,下次遇到新的”疑点”,还是会来找我。
直到一个月前。
任务名称:Roy*l M** 螺旋桨棋子算法验证
起因:老张的都市传说
目标:证明国王不是AI
预计耗时:两周

图:某三消游戏
那天晚上我正吃着泡面,电脑上放着视频。
微信突然弹出来,老张发了条语音:
“在吗?”
我看了眼时间,凌晨2:17。
“在,怎么了?”我回了条消息。
十秒后,语音电话打过来。
“我验证了。”他的声音有点兴奋,”Roy*l M**的国王,真的是AI。”
“大哥,你半夜不睡觉研究这个?”我把手机夹在肩膀上,左手按着键盘快进视频,右手扒拉着泡面。
“你听我说完。”他语速很快,”我Hook了螺旋桨棋子的目标选择函数,跟踪了它的决策过程。你知道我发现了什么吗?”
“发现它很聪明?”
“不止!”他声音提高了八度,”它的决策路径,不是简单的’找分数最高的位置’,它会……它会计算后续的连锁反应!”
“等等等等。”我放下手机,切换到免提,”老张,我现在给你上一课。社会主义核心价值观第一条——唯物主义。”
电话那头沉默了两秒。
“什么意思?”
“意思是,”我清了清嗓子,切换到”教育模式”,”游戏公司不是搞科研的,他们是做生意的。你知道做一个真正的AI要多少钱吗?”
“可是……”
“没有可是。”我继续说,”Roy*l M**是个三消游戏,不是自动驾驶,不是医疗诊断,更不是什么AlphaGo。它需要的智能,用规则引擎加权重表就够了。”
“规则引擎做不到这个效果!”
“做得到。”我很肯定,”给每个棋子类型打个分,冰块80分,木箱50分,目标棋子10000分,然后遍历棋盘,找分数最高的位置,螺旋桨棋子飞过去。就这么简单。”
“但是它……”
“而且啊,老张。”我打断他,”你做了十年游戏,难道不知道AI这个词有多值钱吗?真要是用了什么黑科技,游戏公司早就拿出来吹了。你看Roy*l M**的宣传里提过’AI’吗?”
电话那头又沉默了。
我以为他被说服了,正准备挂电话,他突然问:
“那你解释一下,为什么它能找到那种’特别刁钻’的路径?”
我手指停在屏幕上。
“什么路径?”
“就是那种……“他顿了顿,像是在组织语言,”你知道吗,有时候螺旋桨棋子会选一个看起来’很蠢’的目标,但消除之后,棋子下落,触发连锁,最后反而是最优解。”
“那可能只是你运气好吧。”
“不是运气。”他的声音很平静,”我专门观察过好几局,每次它都能找到那种’不直观’的最优路径。你自己玩的时候有没有这种感觉?明明觉得应该打A,结果它打了B,然后你发现B确实更好。”
我沉默了两秒。
还真有。
上次玩64关,螺旋桨棋子飞向了一个角落里的普通方块,我当时还以为是bug,结果那一下触发了连锁反应,直接通关了。
“那可能是……“我想找个解释,但话卡在喉咙里。
“你知道要实现这个,需要什么吗?”老张继续说,”需要模拟消除后的棋盘状态,需要对每个可能的未来状态打分,需要算法来筛选最优路径。这不是简单的if-else,这是……”
“搜索算法。”我替他说完,”预判,评估,剪枝。”
“对。”他说,”所以我说它是AI。不是深度学习那种新AI,是……”
“经典AI。”我说,”下棋那一代的技术。”
电话两端都沉默了。
半晌,我说:”你别发癫了,睡觉去吧。”
“你不验证一下?”
“验证什么?”我嘴硬,”就算它用了搜索算法,也只能证明程序员写得好,不能证明’国王是AI’。”
“那你验证完了告诉我,”电话那头传来键盘敲击声,”到底是国王聪明,还是程序员聪明。”
我没回话,直接挂了电话。
但盯着64关的界面,我心里确实有点不踏实。
老张是神神叨叨,但他说的那个现象,我确实见过——
螺旋桨棋子有时候选择的目标,真的很”反直觉”。 但事后看,又确实是最优解。
这要是真的有搜索算法……
那成本也太高了吧?
一个三消游戏,至于吗?
除非……它真的需要这么”智能”?
第二天晚上,我还是没忍住,从柜子深处翻出了老爹的那块旧硬盘。
3.5英寸机械硬盘,IDE接口,外壳生了锈,标签都磨得看不清了。我从阁楼那台老爷机上拆下来的,当时还费了半天劲焊转接头。
接上电脑,硬盘转动的声音有点响,但还能读。我打开那个叫notes/unity-reverse/的文件夹,翻到一个很早以前的文档:
## Unity IL2CPP逆向基础
Unity5之后引入IL2CPP,把C#代码先编译成C++,再编译成机器码。
对逆向来说,难度提高了十倍。
核心机制:
1. global-metadata.dat 包含完整的类型系统
- 命名空间、类名、方法签名、字段定义
- 这部分可以完整还原
2. libil2cpp.so 是最终的二进制代码
- 所有函数体都变成了ARM/x86汇编
- 没有符号,只有地址
3. 两者配合才能分析
- metadata告诉你"有什么"
- so文件告诉你"怎么做"
基础工具:
- Il2CppDumper:自动提取类型信息
- IDA Pro:静态分析so文件
- 需要把两者关联起来
难点:
函数体完全丢失,必须从汇编逆推C#逻辑。
一个300行的函数,手动分析要3-5天。
一个复杂系统,少说要一个月。
——某个通宵写下的笔记
我盯着最后那句话。
“一个复杂系统,少说要一个月。”
老爹的笔记讲的是Unity IL2CPP的基础知识。
Roy*l M**用的也是IL2CPP。老张说螺旋桨棋子”很聪明”,那背后一定有一套复杂的决策系统。
如果真像老张说的那样,有”连锁预判”、”状态模拟”,那代码复杂度……
按老爹笔记里说的,一个月可能都不够。
但我还是想试试。
我打开微信,给老张发了条消息:
“发你的测试数据过来。”
他秒回:”你要验证了?”
“嗯。”我敲字,”但先说好,我是为了证明你错了。”
“好。”他秒回,”我等你结果。”
五分钟后,他发来一个压缩包,里面是他Hook的日志文件,还有一个简短的说明文档。
我解压,打开日志,开始看。
然后我愣住了。
老张的日志文件很详细。
他Hook了一个叫ExplodeTargetFinder.FindForExploder()的函数,记录了每次螺旋桨棋子选择目标时的完整调用栈。
日志节选:
============================================================
[调用栈] ExplodeTargetFinder.FindForExploder()
└─ ExplodeTargetFinder.FillScores()
[高分格子Top5]:
位置[4:0]: 456976分
位置[5:0]: 456976分
位置[6:0]: 456976分
位置[7:0]: 456976分
位置[8:0]: 456976分
└─ ExplodeTargetFinder.FindSingleTarget()
└─ [重定向] 目标已重定向
[FindForExploder] 目标查找完成
============================================================
我看到一连串的函数名:
ExplodeTargetFinder.FindSingleTarget()
ExplodeTargetFinder.FillScores()
ExplodeTargetMediator.CalculateScore()
CellModel.GetExplodeScore()
StaticItemModel.GetExplodeScore()
ItemModel.GetExplodeScore()
...
还有更多。
光是计算一个位置的分数,就调用了十几个类。
但这还不是最诡异的。
最诡异的是,老张在日志里标记了几个”反直觉决策”的案例:
案例一:底部收集关卡
案例二:收集青蛙关卡
案例三:重定向机制
案例四:实际Hook日志
从老张的日志中可以看到,一次螺旋桨棋子触发会计算整个棋盘的分数,并选择最优位置。许多格子的分数都是456976分(青蛙权重),但最终的选择还会经过重定向机制调整。
我盯着这些案例。这不是简单的分数排序。
我关掉日志,开始标准流程。
标准流程:
# 解包
java -jar apktool.jar d royxxx_mxxxx.apk -o apk/
# IL2CPP元数据提取
./Il2CppDumper.exe `
apk\lib\arm64-v8a\libil2cpp.so `
apk\assets\bin\Data\Managed\Metadata\global-metadata.dat
29秒后,工具吐出一堆文件:
dump.cs:所有类定义script.json:IDA脚本元数据DummyDll/:C# DLL骨架
图:Il2CppDumper导出的文件
我用ILSpyCMD反编译Assembly-CSharp.dll:
ilspycmd -p -o re-code/ DummyDll/Assembly-CSharp.dll
三分钟后,re-code/目录里出现了完整的C#工程。
我打开编辑器,搜索”ExplodeTargetFinder”。
找到了。
一个1200多行的类,几十个方法,函数体全是空的——IL2CPP不保留实现。
但这就够了。
类定义、方法签名、字段类型,这些信息足够我定位到IDA里的对应汇编代码。

图:ExplodeTargetFinder类的反编译代码
我打开IDA Pro,拖进libil2cpp.so。
107MB的二进制文件,IDA开始分析。进度条缓慢爬升,我去泡了杯咖啡。
20分钟后,分析完成。
我点击菜单:
File → Script File → ida_with_struct_py3.py
对话框弹出,我选择:
il2cpp.hscript.json点击确定。
IDA开始导入符号。状态栏显示:”Processing symbols…”
这一步需要一整天。
我抬头看了眼时钟,凌晨1点。
第二天晚上,我回到电脑前。
IDA状态栏显示:”Ready”。
我按Shift+F12打开字符串窗口,搜索”ExplodeTargetFinder”。找到了——一个函数名。
双击,跳转。
ARM64汇编代码铺满屏幕:
ExplodeTargetFinder__FindSingleTarget:
SUB SP, SP, #0x80
STP X29, X30, [SP,#0x70]
ADD X29, SP, #0x70
STR X0, [SP,#0x68]
STR X1, [SP,#0x60]
...
300多行汇编。

图:IDA Pro 中导入元数据脚本的界面
我往下滚动,又看了几个方法:
FindSingleTarget() - 300多行汇编
FillScores() - 200多行汇编
CalculateScore() - 400多行汇编
TryRedirect() - 150多行汇编
…
还有数十个棋子类,每个都有自己的GetExplodeScore()方法。
还有十几个Helper类,每个都有复杂的影响因子计算逻辑。
我粗略估算了一下:
如果要完整还原螺旋桨棋子的决策逻辑,至少需要还原:
总共接近2万行C#代码。
每一行都需要从ARM64汇编逆推。
我盯着屏幕,心里开始打退堂鼓。
按老爹笔记里说的,手动逆向一个300行的函数,需要3-5天。
2万行代码,就算我每天8小时全职投入,也需要1年。
而且这还不是简单的代码翻译。
螺旋桨棋子的权重计算涉及大量条件判断、配置读取、动态调用,汇编代码复杂度是普通函数的5-10倍。
更要命的是,老张说他要验证的是”反直觉决策”——这意味着我不能只还原代码结构,还必须保证逻辑精度。
一个权重系数搞错了,整个分析就前功尽弃。
我揉了揉太阳穴。这活儿根本不可能完成。
我关掉IDA,打开浏览器,搜索”Unity IL2CPP 自动化逆向工具”。
出来的要么是收费商业工具,要么是学术界的研究项目,精度都达不到我的要求。
我靠在椅子上,盯着天花板发呆。
这时,老爹端着茶杯路过。
他扫了眼我的屏幕,看到IDA里的汇编代码,还有我浏览器里搜索的关键词。
停顿了两秒,丢下三个字:
“试试AI。”
然后端着茶杯走了。
我愣在原地。
老爹从来不会多说一句废话。他能开口,说明这事儿有门。
AI……
我一直在用AI写脚本、补全代码,但从来没想过——
它能不能逆向汇编?
不过在尝试之前,我先推导了一下可行性。
IL2CPP的编译流程是:C# → IL字节码 → C++代码 → 机器码。关键点在于,IL2CPP为了支持反射和调试,会在编译后保留完整的符号信息——类名、函数签名、字段类型、泛型参数,这些都在global-metadata.dat里。
这意味着,从汇编还原到C#,丢失的信息并不多:
真正需要还原的,只有代码控制流——把jmp、cmp、call这些汇编指令,翻译回if、for、return。
理论上可行。
值得一试。
我打开编辑器,新建了一个测试文件。
把IDA反编译出来的伪代码和C#类定义一起丢进去,输入指令:
“根据这段伪代码和类定义,还原C#函数体。”
等了一会儿。
代码出来了。
还原前(Il2CppDumper导出的空函数体):
关键代码节选:
public void FindForExploder(GuidedExploderItemModel exploder)
{
}
private IExplodeTarget FindSingleTarget(GuidedExploderItemModel guidedExploder)
{
return (IExplodeTarget) null;
}
private void FillScores(GuidedExploderType guidedExploderType, bool canExploderSpreadJelly)
{
}
还原后(AI辅助还原的完整实现):
ExplodeTargetFinder_re.cs - 还原后完整代码(3306行)
关键函数实现示例:
public void FindForExploder(GuidedExploderItemModel exploder)
{
if (exploder == null) return;
var explodeData = exploder.TargetExplodeData;
var trigger = explodeData.trigger;
var guidedExploderType = TriggerExtensions.AsGuidedExploderType(trigger);
// 根据不同的trigger类型执行不同的查找策略
switch (trigger)
{
case Trigger.TntPropeller:
target = FindPowerCubeOrSoilTargetForWinCondition(explodeData);
if (target == null)
target = FindAreaTarget(explodeData, canSpreadJelly);
break;
case Trigger.VerticalPropellerRocket:
target = FindColumnTarget(explodeData, guidedExploderType, canSpreadJelly);
break;
// ... 更多策略
}
exploder.TargetFound(target);
}
我对照着IDA的汇编代码,逐行检查。
函数调用顺序,对。 条件判断逻辑,对。 返回值类型,对。
精度……85%以上。
“还真行。”
我开始搭建AI还原工作流。
第一步,在Cursor里加载ILSpy导出的C#骨架工程。Cursor的RAG会给AI提供完整的上下文。
第二步,配置IDA Pro MCP。装了个开源插件,让Cursor能直接读取IDA数据库里的伪代码,不用再复制粘贴。
第三步,批量还原函数体。在Cursor里输入指令,AI读取IDA伪代码,结合工程上下文,输出C#代码。我负责检查和修正错误。
第四步,处理编译器生成类。C#的yield return、async/await这些语法糖,编译后会生成状态机类。所有函数还原完之后,用AI把这些状态机类还原回语法糖形式。
这套流程跑通之后,我有了汇编代码->C#代码的能力,而不需要理解抽象的汇编代码。
但还原代码只是第一步。
拿到81个类、16个系统、228行权重计算逻辑之后,我面对的是另一个问题:这些代码想干什么?
技术实现里充斥着各种字段名、常量、状态机,但策划的设计意图被埋在了代码噪声里。我需要把这些C#代码翻译成人话——或者说,翻译成老张能看懂的业务文档。
老爹笔记里有句话:“逆向不只是还原代码,更是还原设计者的思维。”
我开始搭建第二套AI工作流:从C#代码还原业务逻辑。
第一步,模块拆分。把81个类按照真实设计意图重新组织——不是按代码目录结构,而是按业务功能划分。比如”单棋子权重计算”“组合奖励权重”“全局调控因子”。这一步需要反复和AI确认理解,因为一旦模块边界划错,后面全是错的。
第二步,子任务拆分。每个模块拆成多个上下文独立的小任务。比如”单棋子权重”模块拆成:”基础权重规则”“优先级系统”“特殊状态加成”。关键是让每个子任务的上下文足够小,AI能一次性处理完。
第三步,细节填充。让AI读代码,过滤掉技术噪声(类型转换、状态机逻辑、异常处理),用流程图、数学公式、表格这些人类友好的形式输出。AI在这一步会发现一些有趣的细节——比如某个优先级权重是676,恰好是低优先级权重26的26倍。
第四步,逻辑关联。把各个文档之间的关联关系标注清楚,用超链接连接起来。这样看文档的人能快速跳转,理解整体架构。
静态逆向只是揭开了冰山一角。
代码可以还原,但它真的按照我理解的方式运行吗?
要回答这些问题,我需要测试。
→ 未完待续
代码会说真话。
本文为基于真实技术分析的虚构故事,文中截图、图片如有展示,均作技术示意,与具体厂商和项目无直接指向关系。
未经授权 禁止转载。
版权所有 © lxraa 2025