哈喽大家好,我是JasonMoonW。这是一个非常特别的项目,直白了说,我通过数月的准备,写出的程序,可以直接读取皇室战争的内部状态。 我的程序通过读取皇室战争的内存,可以定位出许多关键游戏信息存储的位置。可以直接来获得游戏状态——这包括圣水、游戏时间、手牌、皇家塔血量还有场上单位信息。在我这个项目之前,想要获得游戏状态,只能通过计算机视觉(也就是截屏然后喂给AI来识别)的方法进行,但是这种方法准确率低(在部队多的时候完全看不清每个单位),延迟高(可能高达1-2s)。
现在,我写的这个监听程序已经找到了各个游戏对象存储的位置,可以1秒读取30次游戏状态然后更新在我的屏幕上,而且不会出现『不准』的情况。
项目介绍视频:https://www.youtube.com/watch?v=1RJ5_5xopkI B站补档:https://www.bilibili.com/video/BV1B63z6BEa2/
我真的没有想到这个视频在B站上有如此的热度,在我几乎没有粉丝的情况下,7小时就破了一万播放,但是接踵而来的就是限流和撤稿。真可惜啊。
首先,你要下载Android Studio并且在安卓模拟器里安装最新版本的Null's Royale. 然后你要在这个虚拟手机上安装frida-server。
然后你需要把deploy/run_raw_capture.py里指向frida-server的路径改成实际的路径。
首先要打开监听程序minimal_visualizer.py,还有安卓模拟器(上面启动私服Nulls Royale)。在没有进行对战的时候,这个监听程序的界面就是黑屏。我们接下来进入一次对战。
当竞技场和我们的手牌加载出来的那一刻,我们监听程序的屏幕就亮了起来,屏幕上的皇家塔和部队单位就会变成熟悉的红色和蓝色。
这真是一个棘手的问题。我们使用的Nulls Royale用到的核心文件libg.so只在ARM架构的模拟器上才能正常加载,而Windows系统运行的x86_64架构模拟器必须要让游戏文件经过一层翻译,这让frida完全失效。
开始我是不想支持Windows的,但是感觉大家还是更常用Windows,我就花了两天多时间研究了一下在没有frida的情况下还能不能获取游戏状态。只剩下了一种途径,那就是真的直接读取游戏内存,可行但是非常低效。
如果你真的很想在Windows上使用,我只在x86_64文件夹里提供一个原型版本,使用步骤是:
- 安装mumu模拟器,添加一个android 12设备。配制好root权限和adb(默认端口5557)
- 运行
setup.ps1 - 运行tools文件夹下的
capture_native_entity_stream.py
这个代码非常丑陋,因为是GPT写的,欢迎大家来改进,我实在没有精力去手动修改了。
如果你对逆向工程知之甚少,可以考虑看看我项目介绍视频后半部分的原理介绍。我承认我后半部分写的比较简略。
如果你想自己试试,那么本目录下的My Own Findings.md里面的内容可以给你使用的大模型作为参考。为了保证上下文整洁,我全文使用的是英文而不是中文,你可以自行翻译。
接下来我用自己的话解释读取原理:经过很详细的汇编分析和追根溯源,我抓取到两个非常重要的东西。
- 一个叫做
holder,在游戏每一刻被更新时都会被调用,在它的地址附近,连接着许多游戏状态的指针,皇家塔血量、圣水和当前手牌等就是通过holder附近的寻址直接读出来的。 - 另一个叫做
hpComponentAccessor。游戏会实时查询场上每一个单位的血量,我们通过勾住这个函数的入口和出口,可以获得场上所有实体的信息。
在启动监听程序之后,我们会源源不断的接受数据流,里面包含每一个实体的信息和每隔一段时间就会刷新的皇家塔血量等信息。问题是,这些游戏实体不是被打包发过来的,而是分别单独传送到Python端进行接收的。比如说某一段时间你的程序输出可能是这样:
{"event": "entity_observed", "ptr": "0xb40000705f985700", "kind_30": 15, "side_78": 0, "pos_x_7c": 11552, "pos_y_80": 12061, "pos_x2_84": 11489, "pos_y2_88": 11998, "card_id_ac": 26000010, "level_index_120": 15, "hp_10": 130, "max_hp_14": 130, "max_hp_18": 0, "t_ms": 1785588304275}
{"event": "entity_observed", "ptr": "0xb40000705f91ac60", "kind_30": 15, "side_78": 0, "pos_x_7c": 12382, "pos_y_80": 9382, "pos_x2_84": 12340, "pos_y2_88": 9340, "card_id_ac": 203000014, "level_index_120": 15, "hp_10": 1153, "max_hp_14": 1153, "max_hp_18": 0, "t_ms": 1785588304275}
{"event": "entity_observed", "ptr": "0xb40000705f8806a0", "kind_30": 15, "side_78": 1, "pos_x_7c": 12985, "pos_y_80": 26885, "pos_x2_84": 12972, "pos_y2_88": 26943, "card_id_ac": 26000069, "level_index_120": 15, "hp_10": 3672, "max_hp_14": 3672, "max_hp_18": 0, "t_ms": 1785588304276}
{"event": "entity_observed", "ptr": "0xb40000705f9ab4e0", "kind_30": 15, "side_78": 0, "pos_x_7c": 3268, "pos_y_80": 17955, "pos_x2_84": 3269, "pos_y2_88": 17835, "card_id_ac": 26000021, "level_index_120": 15, "hp_10": 2711, "max_hp_14": 2711, "max_hp_18": 0, "t_ms": 1785588304276}这就是实体数据流式传输和我们理想中游戏状态间隔更新的矛盾。这需要在我们Python的接收端进行解决。
虽然现在的大模型能力很强,但是在这种级别的项目上(特别是逆向工程类的)它们的知识储备也不支持它快速的解决问题。我采用了一些比较特殊的方法,经过四五次的尝试,才一步步摸索到了答案。接下来我分享一下我用大模型的经验。
- 避雷GPT-5.6-sol等最新大模型。它们具有非常严格的安全防护机制,虽然模型本身愿意回答,但是其输出会被系统标记并且隐藏。我全程使用的是GPT-5.5。
- 这个项目非常庞大,而codex只支持258k的上下文长度,因此,必须珍惜你的上下文。上手项目之后,我做的第一件事就是创建项目需求文件,里面详细阐述了我需要大模型做的事情。同时,我要求大模型遵循Polya的问题解决步骤,即先研究明白问题本身,然后做计划,接着一步一步执行,并且在每一步都标记自己的进度和收获,存储到一个文件中形成系统性的知识。这个知识库便构成了最初版本的"My Own Findings.md"。
- 让大模型慢下来,不要一次尝试太多,以及在一个方向上猛扎太深。我明确告诉大模型我有数月的时间完成项目,因此可以一小步一小步的来。所以你会发现我还有闲心去分析安装包本身和程序入口点,而不是一上来就进行网络抓包。同时,告诉大模型你可以提供很关键的帮助,那就是进行对战并且记录下牌位置等,这样大模型可以告诉你需要运行什么监听脚本,你去按照它的规定下牌,大模型就可以自己分析网络数据和内存数据。
- 必须非常仔细的关注大模型的进度。有时候因为上下文压缩,大模型的表现会下降,在一个错误的方向越陷越深。你需要把它揪出来:『我们还能通过哪些其他方法来解决这个问题?』通常大模型此时给出的答案就会正确很多。
- 清理上下文,尽量避免大模型做过多不必要的反汇编、环境测试等工具调用,这样很费tokens和时间。非必要不要使用xhigh的推理强度,我一般只使用medium。xhigh的响应速度过慢,而且模型喜欢自顾自的干很多没用的工作浪费上下文。
- 在模型反复让你去做同一件事情的时候,多半情况下是模型已经一头扎进了错误的方向。这时候最好的办法是清空上下文,让模型重新审视现在的状况,看看应不应该采取其他的思路。例如模型在完成下面的子项目的时候,一直让我抓包然后给他丢结果,重复了二十次左右都没有进展。于是我停止抓包,反问模型是不是应该做静态分析。模型同意了我的观点,进行了两分钟的静态分析,然后立刻取得了重大的突破,实现了整个项目最大的转折点。
- 我们也需要接受进度卡壳。逆向工程很难,而手机游戏是在所有应用中,逆向难度最大的那一种类型。所以,你要珍惜模型的每一丝进度,如果模型做不出来,有可能就是真的做不出来了。我这个项目能够取得这样的成果,真的是非常幸运。
B站介绍视频链接:https://www.bilibili.com/video/BV1B2GP66EMy
之前海森堡被指控使用外挂来提前看到对手下牌。我对为什么能提前看到对面下牌这件事感到非常好奇,所以就又高强度研究了好几天,最后实现了一个大致效果相同的插件。我个人感觉就是非常神奇,因为这完全就是利用了皇室战争的部署队列机制,来消除双方的网络延迟影响,让我感叹于这款手机游戏的巧妙。
当然,我把代码放出来,是因为我已经从理论上证实它不具备实战可用性,无法成为真正的外挂。那些对逆向非常感兴趣,同时又爱玩皇室战争的朋友,可以让这个代码作为一个参考。我个人对视频里的讲解不是很满意,虽然播放量还可以,但是因为上个视频被撤稿,这个视频我的讲解过于矜持,几乎完全没有提及代码原理。以后有空的话,我会把详细原理放在这里。
我不在这里写原理的另一个原因是我自己也不太懂大模型写的代码。不过,我正在努力尝试读懂这些代码并且把它们的思路尽量简化,让它们不再是史山,而是对后人的宝贵参考资料。
你可以到B站上直接给我发私信。我非常欢迎大家对这个项目作出贡献,把它写成一个完整的框架,改善卡牌检测机制,等等。
当然,你也可以直接把观察到的问题提交到issue里来让我解决。

