geamztheangrybirds727/Transformers-Forged-To-Fight-Offline-Version

GitHub: geamztheangrybirds727/Transformers-Forged-To-Fight-Offline-Version

该项目通过原生库补丁、模拟服务器和 runtime hook,让已停服的 Unity IL2CPP 手游《Transformers: Forged to Fight》实现离线启动,为后续重建服务端奠定基础。

Stars: 1 | Forks: 0

# Transformers: Forged to Fight,离线复活交接 本压缩包包含了 Transformers: Forged to Fight 的一个可运行的离线启动方案,以及 为实现这一目标所使用的所有工具、补丁和逆向工程笔记。它的目的是 交给那些有时间和精力去进行下一步——也是更庞大的一步——即从零开始 重建游戏服务端内容的人。这里的所有内容都 有详细记录,这样你就不必像我当初那样从零开始。 在动任何东西之前,请先完整阅读本文件。特别是“踩坑提示”部分,它将 为你节省大量时间。 ## 目前实际可用的功能 游戏可以完全离线启动,并在没有任何 真实服务器的情况下,进入其真实的交互式主屏幕。从主屏幕开始,菜单导航不会崩溃: 基地、机器人名册(账号中包含一个已拥有的机器人)、战斗模式选择、 水晶屏幕,以及常见的弹窗和提示。完整的登录流程已完成,所有 在线子系统均已连接,首次体验和新手引导门槛均已清除。 scripted 介绍战斗(Optimus 对战 Starscream)甚至已经能够开始加载 战斗,并且 3D 角色模型可以渲染并播放动画。 这是最困难的部分,现在已经解决了。客户端本身在离线状态下又复活了。 ## 无法运行的功能及其原因 实际的游戏玩法无法运行。剧情不显示任何任务,战斗也无法完全加载。 这不是一个 bug,也不是通过补丁就能修复的问题。 Forged to Fight 是一个完全由服务器控制权威的游戏。手机上的应用程序本质上是一个 带有控制功能的屏幕。游戏的内容几乎没有任何部分存在于应用程序本身。每一个任务、每一场 战斗、每一个敌方阵容、整个名册的属性和技能、经济系统以及所有的 平衡性数据都存在于 Kabam 的服务器上,并在每次会话时以流的形式传输到设备中。当 服务器在 2020 年初被关闭时,这些内容数据库也随之消失,并且 从未被发布过,或在我能接触到的任何地方进行过公开存档。 因此,情况明确地一分为二。美术和音频资源得以保留,因为它们被打包在 应用程序内部(参见 `re_notes/ASSET_INVENTORY.txt`)。每个角色都是一个完整的 Unity asset bundle,其中包含模型、贴图、骨骼绑定、动画片段、动画控制器、特效 和音频。环境、建筑、UI、头像、过场动画和对话也都 在那里。没有保留下来的是那些告诉游戏该使用哪些资源的数据,比如如何将它们组合成一场 战斗或一个任务,以及每个机器人的具体数值是多少。所有的部件 都在。只是再也没有任何东西知道如何将它们组合在一起了。重建这些就是剩下所有 工作的全部。 ## 离线启动是如何工作的 这里有四个核心运作部分。它们共同让未经修改的游戏认为自己正在与 Kabam 通信。 1. 原生二进制补丁。游戏使用的是 Unity IL2CPP,因此其逻辑存在于一个编译好的 ARM 库 `libil2cpp.so` 中,而不是在可编辑的脚本文件中。`patches/patch_il2cpp.py` 会重写 该库中的六个函数,以跳过失效的服务器检查:它攻破了两条 certificate pinning 路径,从而接受我们自己的 TLS 证书;强制 manager registration 块在 live config 为 null 的情况下也能运行;允许通过我们的本地设备会话成功登录;并屏蔽那些 子系统的致命错误,否则这些错误会弹出“登录失败”的对话框。它还重新注入了一个 dependency 条目(参见 “踩坑提示”部分),以便 runtime hook 能够实际加载。其输出文件为 `libil2cpp.patched.so`。 2. 模拟的 Sparx 服务器。`server/fakeserver.py` 作为 Kabam 后端的替代品。它在 TLS 443 和纯 HTTP 80 端口上监听,并回应游戏的 API 调用。预设的响应内容位于 `server/responses/` 中,每个 endpoint 对应一个文件,并以方法和路径命名,例如 `GET__account_data.json`。有几个 endpoint 是在代码中动态响应的,而不是 从文件中读取,因为游戏希望它们回显请求中的值(如教程 endpoint 和英雄详情 endpoint)。响应的信封格式为 `{"error":null,"result": ...}`。请注意,在 Sparx 错误 payload 内部,该字段拼写为 `err`,而不是 `error`。这个细节非常重要且极易被忽略。 3. 原生 runtime hook。`tools/nativehook/` 用于构建 `libdothook.so`,这是一个小型库,会在 游戏启动时加载,并记录游戏读取的每一个数据键,同时还包含一些 针对性的行为引导。正是这个反馈循环使得其他所有工作成为可能:它 会准确地告诉你游戏正在请求什么,这样你就可以合成一个响应并验证 它。它是一个在执行前安装的纯字节覆盖的内联 hook,因为用于此目的的常规 工具 (Frida) 在模拟器的 ARM 翻译层下会崩溃。 4. 设备连接配置。模拟器必须将 Kabam 的域名发送到 PC,并信任 伪造的证书。`tools/provision_ldplayer.sh` 可以一步完成此操作:它会推送已打补丁的库 和 hook,通过 hosts 文件将 Kabam 主机名重定向到 PC 的局域网 IP 地址, 将伪造的 CA 挂载到系统信任库中,并放宽 SELinux。每次 模拟器重启后都要运行它,因为这些挂载在重启后无法保留。 运行时的数据流如下:游戏向 Kabam 域名发出一个 HTTPS 调用,hosts 文件 将其发送到 PC,模拟服务器使用来自 `server/responses/` 的响应进行回答, 已打补丁的库接受该证书和响应,而 hook 则记录读取的内容。这个循环 就是当前构建版本中每个屏幕得以成功显示的方式。 ## 本压缩包包含的内容 ``` README.md this file TECHNICAL_NOTES.md the deeper technical reference: patches, recovered data shapes, findings patches/ patch_il2cpp.py the six native patches plus the dependency re-injection disasm_fn.py helper: disassemble a function at an offset find_callers.py helper: find callers of a function find_str_ref.py helper: find references to a string server/ fakeserver.py the fake Sparx server gen_certs.sh regenerate the TLS cert and CA (run this, see below) setup_device.sh device side network and trust setup reference iterate.sh quick restart and capture loop responses/ one JSON file per endpoint the game calls tools/ provision_ldplayer.sh one shot re-provision of the emulator to the working state setup_arm64.sh toolchain setup notes decompile_targets.py drive the Ghidra headless decompiler at chosen offsets find_xrefs.py cross reference search over the binary apply_labels.py apply IL2CPP symbol labels light_analyze.py lightweight static analysis helpers frida_attach.py Frida helpers (kept for reference, see the libnb note) frida_run.py hook_dot.js nativehook/ hook.c source of libdothook.so, the runtime hook libdothook.so prebuilt hook, arm64 deploy.sh build and deploy the hook relaunch_and_capture.sh relaunch the game and capture logs hook/dothook.c earlier hook variant, kept for reference re_notes/ dump.cs the full IL2CPP dump: every class, method, and field in the game decomp_out.c decompiled bodies of key functions decompile_targets.txt the offsets worth decompiling ASSET_INVENTORY.txt what art and audio already ships inside the app ``` `re_notes/dump.cs` 是后续工作中最具价值的单个文件。它是 游戏的完整类型模型:每一个类、每一个方法,至关重要的是,客户端从服务器读取的每一个数据 字段。它是你整个后端 API 的地图。当你需要 知道响应应该是什么形状时,答案就在里面。 ## 本压缩包未包含的内容及其获取位置 这些内容被刻意排除在外,因为它们体积庞大,或者受版权保护,或是机密,亦或是你应该自己生成它们。 - APK 本身(`com.kabam.bigrobot`,版本 9.2.0)。大小约为 800 MB。请自行寻找 副本。包名和版本记录在 `TECHNICAL_NOTES.md` 中。 - 原始的 `libil2cpp.so` 和游戏资产。两者都可以直接从 APK 中提取。解压 APK,该库位于 `lib/arm64-v8a/` 下,资产位于 `assets/` 下。 - TLS 证书和 CA。请勿分发私钥。运行 `server/gen_certs.sh` 来生成你自己的 匹配对,然后将设备信任库指向新的 CA。 - 已打补丁的库。请重新生成它:使用 APK 中的原始 `libil2cpp.so` 运行 `patches/patch_il2cpp.py`。 - Frida server 和 Il2CppDumper。两者都是公开工具。Il2CppDumper 就是使用 APK 的库和 global metadata 生成 `re_notes/dump.cs` 的工具。 - Android NDK(使用了 r26 版本)和 JDK 21,这是构建 hook 和运行 Ghidra 无头反编译器所必需的。 ## 如何运行目前的环境 你需要将 APK 安装在支持 ARM 翻译的模拟器上(使用了 LDPlayer 9,需开启 root 且系统可写),在 PC 上安装 Python,并准备好上一节中提到的各项内容。 1. 生成一次证书:`bash server/gen_certs.sh`。 2. 构建一次已打补丁的库:`python patches/patch_il2cpp.py path/to/original/libil2cpp.so --apply`。 3. 如果你想自行构建,请构建一次 hook,否则请使用预构建版本。参见 `tools/nativehook/deploy.sh`。 4. 在 PC 上启动模拟服务器:`python server/fakeserver.py`。必须确保模拟器能够访问 443 和 80 端口。 5. 配置设备:`bash tools/provision_ldplayer.sh <你的-PC-局域网-IP>`。每次 模拟器重启后请重新运行此命令。 6. 等待大约 45 秒,然后点击标题屏幕登录。你应该会进入主 屏幕。 如果卡在登录界面,请优先检查“踩坑提示”部分的第一项。 ## 会耗费你时间的踩坑提示 这些是曾耗费了我数个小时的问题。把它们写下来是为了让你不再重蹈覆辙。 - runtime hook 只能通过一个 dependency 条目加载,而原始库中并不包含该条目。补丁脚本是基于原始库构建的,因此如果不重新添加该条目, hook 将永远无法加载,登录也会一直卡住。补丁脚本现在会在每次构建时重新注入它。如果 hook 似乎失效了,首先要检查的是已打补丁的库是否实际引用了 `libdothook.so`。确切的字节和偏移量已记录在 补丁脚本和 `TECHNICAL_NOTES.md` 中。 - 如果你使用 LDPlayer9/Bluestacks 进行测试,Frida 将无法工作。模拟器将 ARM 转换为 x86,而 Frida 在这种转换下会崩溃。本项目使用纯字节覆盖的内联 hook 的全部原因就在于,它能在 Frida 无法工作的地方存活下来。不要浪费时间试图让 Frida 正常运行。 - 设备网络挂载无法在模拟器重启后保留。hosts 重定向和 CA 信任都是 bind mount。在模拟器进行任何重启之后,你必须重新运行 `provision_ldplayer.sh`,否则所有连接都将失效。 - 在 Sparx 错误 payload 内部,字段名为 `err`,而不是 `error`。使用错误的字段会产生客户端 会默默忽略或错误处理的响应。 - 交互式教程提示状态在离线时会无限循环。不要试图通过回答教程 请求来满足它。相反,应首先移除触发教程的条件。护盾教程卡死的问题就是这样解决的:通过给予玩家其缺失时触发该问题的资源,而不是去回应教程请求。 - 在模拟器下渲染实时的 3D 内容非常脆弱。模型确实可以渲染,但这是 最不稳定的领域,并且对模拟器的图形后端和纹理设置很敏感。 这是模拟器图形的问题,而不是数据的问题。 ## 如果你想真正复活它:重建后端 这才是真正的工作,而且非常庞大。以下是它的基本轮廓和切入点。 目标是通过手工重建过去由服务器以流的形式传输给 客户端的服务端内容:任务和使命、地图及其敌方阵容、包含每个机器人属性和技能的完整名册、战斗公式以及经济系统。这些内容已经全部丢失, 因此必须按照客户端所期望的确切结构重新编写。 行之有效的方法正是本项目建立的基础循环。在附加 hook 的情况下运行游戏。 hook 会记录客户端读取的每一个键。当客户端请求你尚未提供的内容时,你可以确切地看到它需要什么。然后,你可以按照 正确的格式合成一个响应,将其放在 `server/responses/` 中,或者将其添加到 `fakeserver.py` 的动态处理程序中, 重启,并验证客户端是否接受它并继续运行。不断重复这一过程。当前构建版本中的每一个 屏幕都是通过这种方式实现的。`re_notes/dump.cs` 会在你运行之前告诉你每个 结构的具体形状,因为它列出了客户端读取的每一个字段。 一个合理的推进顺序: 1. 让一个完整的战斗能够加载并从头到尾运行。这是最高价值的目标, 因为战斗是游戏的核心,并且它会一次性调用最多的服务器数据。你需要参与者定义、 它们的属性和技能,以及战斗初始化路径所请求的任何内容。开场战斗已经开始加载,因此那是首先要推进的切入点。 反编译战斗初始化和战斗数据路径(使用 `decompile_targets.py`)并读取 确切的字段。 2. 完整地重构名册数据模型,一次一个机器人,包括属性和技能。 每个机器人的美术资产已经存在于 `ASSET_INVENTORY.txt` 列出的 bundle 中,因此你 只需编写数值和技能定义,而不必制作资产。 3. 重建任务和地图结构,使剧情(Story)不再为空。基础结构 已经被部分破解,相关的键请参见 `TECHNICAL_NOTES.md`。 4. 最后补充经济和进度系统,等有了可以消耗它们的战斗和任务之后。 要对规模有现实的预估。即使对于那些在停服前由玩家保存了真实服务器数据的游戏来说, 搭建一个私人服务器也是一项漫长的工程。在目前的情况下,我们没有可用的已保存数据作为起点,因此每一个数值和每一种 技能都必须经过研究或重新创造,然后再通过客户端进行验证。如果目标是 还原真实的游戏,这将是一项需要多人参与、历时数年的工作。话虽如此,前进的道路已不再是一个谜。离线启动已经解决,反馈循环 已经存在,类型模型已被 dump,而资产也完好无损。剩下的只是 非常庞大且细致的数据重构工作,而不是要对更多未知的部分进行逆向工程。 从 `TENICAL_NOTES.md` 开始。它是更深层次的技术参考资料,包含了确切的 补丁、恢复的数据结构以及具体发现,内容比本 README 更详细。然后运行该循环。 祝你好运。它现在是一台真正运转的机器了。它只需要重建其内容。
标签:云资产清单, 游戏服务端, 游戏模拟, 游戏私服, 离线补丁, 逆向工具, 逆向工程