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

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

该项目通过补丁、模拟服务器和 runtime hook,使已停服的「Transformers: Forged to Fight」手游能够离线启动并游玩部分内容,同时为后续完整重建服务端提供了逆向工程笔记与工具链。

Stars: 1 | Forks: 0

# Transformers: Forged to Fight,离线复活交接 本软件包包含了 Transformers: Forged to Fight 可运行的本地离线复活版本, 以及用于构建它的工具、补丁和逆向工程笔记。这是一次交接, 旨在继续完成一项更为庞大的任务:从零开始编写替代用的服务器端内容。 此处不包含或重建原始的在线服务数据。 在你动手修改任何东西之前,请完整阅读此文件。特别是“避坑指南”部分, 将为你节省大量时间。 ## 目前实际可用的功能 游戏可以完全离线启动,并在没有 live Kabam 服务的情况下 进入真实的交互式主屏幕。基地、阵容、战斗模式选择、水晶屏幕、 弹窗和提示均可正常导航且不会崩溃。登录和首次体验门槛在本地完成。 脚本化的擎天柱对阵红蜘蛛开场战斗可以通过其轻攻击 教程进行游玩,并具有实时 3D 角色和战斗控制。 本地服务器还提供了一个完整且编写好的 STORY 1.1.1 循环:选择一个小队, 进入原始棋盘,在可到达的节点之间移动,触发最终 boss, 在原生战前屏幕上选择一个机器人,与 Sharkticon 战斗,判定胜利, 并返回棋盘。精心编写的 `Light`、`Medium`、`Heavy` 和 `Ranged` 攻击行 以及战斗护甲调整使得命中的攻击能够减少生命值。阵容、英雄详情、 团队选择和战斗模型 ID 均生成自相同的原始数据源, 因此战斗使用的是匹配的 3D 网格,而不是通用的占位符。 ## 无法运行的功能及其原因 这是一个可玩的保护沙盒,并不是原游戏的完整替代品。 目前仅编写了一个小型 STORY 路径和一个 boss 遭遇战。对战结果判定目前 使用返回棋盘所需的最小成功响应;已完成的进度、 奖励和任务状态不会被持久化。每个机器人特有的能力效果、额外的任务 和敌方阵容、经济系统以及大多数进度系统仍有待编写。 Forged to Fight 是完全由服务器主导的。手机上的应用程序本质上只是 一个带有控件的屏幕。游戏几乎没有任何内容存在于应用程序中。每个任务、每场 战斗、每个敌方阵容、整个阵容的属性和能力、经济系统以及所有的 平衡性设定都存在于 Kabam 的服务器上,并在每次会话时流式传输到设备上。当 服务器在 2020 年初被关闭时,这些内容数据库也随之消失,而且它们 从未在我能接触到的任何地方发布或公开存档过。 客户端可以从操作者提供的应用程序副本中加载美术和音频资源。所 缺失的是用于选择这些资源、组建战斗和任务 以及定义属性和能力的服务器数据。本项目仅提供客户端可解析的 格式的新增、手工编写的数据;它不包含 APK、游戏资源、原始二进制文件 或捕获的游戏视听材料。扩展此项目前请参阅 `COMPLIANCE.md`。 ## 离线启动的工作原理 这里有四个动态部分。它们共同让未经修改的游戏认为自己正在与 Kabam 通信。 1. 原生二进制补丁。该游戏基于 Unity IL2CPP 构建,因此逻辑存在于已编译的 ARM 库 `libil2cpp.so` 中,而不是可编辑的脚本文件中。`patches/patch_il2cpp.py` 重写了 该库中的六个函数,以绕过失效的服务器检查:它破解了两条 证书绑定路径,从而接受我们自己的 TLS 证书;强制 manager 注册块运行(即使 live config 为 null);允许通过我们的 本地设备会话成功登录;并屏蔽了子系统的致命错误,否则 这些错误会弹出“登录失败”对话框。它还重新注入了一个依赖项条目(参见 避坑指南部分),以便 runtime hook 能够真正加载。输出文件为 `libil2cpp.patched.so`。 2. 模拟 Sparx 服务器。`Server/fakeserver.py` 充当了 Kabam 后端的替代品。它在 TLS 443 和普通 HTTP 80 端口上监听并响应游戏的 API 调用。预设响应位于 `Server/responses/` 中,每个端点对应一个文件,按方法和路径命名,例如 `GET__account_data.json`。少数端点是在代码中动态响应的,而不是 从文件中读取,因为游戏期望它们回显请求中的值(教程 端点和英雄详情端点)。响应封装格式为 `{"error":null,"result": ...}`。请注意,在 Sparx 错误 payload 内部,该字段拼写为 `err`,而不是 `error`。这个细节至关重要且很容易被忽略。 3. 原生 runtime hook。`tools/nativehook/` 构建了 `libdothook.so`,这是一个小型库, 在启动时加载到游戏中,并记录游戏读取的每一个数据键, 此外还包含了几个针对性的行为引导。正是这个反馈循环使得其他一切成为可能:它 会准确地告诉你游戏正在请求什么,以便你可以合成响应并 验证它。它是在执行前安装的纯字节覆写 inline hook,因为通常用于此目的的 工具(Frida)在模拟器的 ARM 转换层下会崩溃。 4. 设备配置。模拟器必须将 Kabam 的域名发送到 PC,并信任伪造的 证书。`tools/provision_ldplayer.sh` 可以一次性完成此操作:它会推送已打补丁的库 和 hook,通过 hosts 文件将 Kabam 主机名重定向到 PC 的局域网地址, 将伪造的 CA 挂载到系统信任库中,并放宽 SELinux。在每次 模拟器重启后运行它,因为这些挂载在重启后无法保留。 运行时的数据流如下:游戏向 Kabam 域名发起 HTTPS 调用,hosts 文件 将其发送到 PC,模拟服务器使用来自 `Server/responses/` 的响应进行回复, 已打补丁的库接受该证书和响应,而 hook 会记录读取的内容。这个循环 就是这个版本中每个屏幕得以成功启动的方式。 ## 本软件包包含的内容 ``` README.md this file COMPLIANCE.md copyright, trademark, and security boundaries for the project 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 gamedata.py hand-authored roster, battle balance, mission, and tuning data test_gamedata.py verifies generated roster, combat, tuning, and mesh mappings gen_certs.sh regenerate the TLS cert and CA (run this, see below) run_local.py run the server on unprivileged phone-friendly ports build_phone_apk.py create an unsigned local-server APK for a stock phone provision_phone.sh install, launch, and configure USB or Wi-Fi phone use 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 inventory of asset identifiers in an operator-supplied app ``` `re_notes/dump.cs` 是剩余工作中最有价值的文件。它是 游戏的完整类型模型:每个类、每个方法,最关键的是 客户端从服务器读取的每个数据字段。它是你整个后端 API 的地图。当你需要 知道响应应该采用什么结构时,答案就在里面。 ## 本软件包未包含的内容及其获取方式 这些内容被故意排除在外,因为它们体积庞大,或受版权保护,或属于机密,或者 你应该自己生成。 - APK 本身(`com.kabam.bigrobot`,版本 9.2.0)。请仅从你有权 使用的副本中获取源文件并使用。包名和版本记录在 `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 headless decompiler 所必需的。 - `media/` 或 `probe/` 下的任何内容。这些是本地的截图和录屏,可能包含 受版权保护的游戏视听内容,它们会被 Git 忽略, 且绝不能被添加或提交。 ## 如何运行目前的版本 你需要将 APK 安装在支持 ARM 转换的模拟器上(使用的是 LDPlayer 9, 需开启 root 且系统可写),在 PC 上安装 Python,并准备上一节中提到的各项物品。 1. 生成一次证书:`bash Server/gen_certs.sh`。 2. 构建一次已打补丁的库:`python3 patches/patch_il2cpp.py path/to/original/libil2cpp.so --apply`。 3. 如果你想重新构建 hook,请构建一次;否则使用预构建的版本。参见 `tools/nativehook/deploy.sh`。 4. 在 PC 上启动模拟服务器:`python3 Server/fakeserver.py`。必须确保模拟器能够通过 443 和 80 端口访问它。 5. 配置设备:`bash tools/provision_ldplayer.sh `。在 每次模拟器重启后重新运行此命令。 6. 等待约 45 秒,然后点击标题屏幕登录。你应该能进入主 屏幕。 ### 通过 Wi-Fi 在未 root 的手机上运行(游玩时无需 USB) 手机和笔记本电脑可以通过同一个 Wi-Fi/LAN 直接通信。APK 必须使用 笔记本电脑的局域网 IPv4 地址进行构建,因为普通手机无法覆盖已失效的 Kabam DNS 名称。例如,如果笔记本电脑的 IP 是 `192.168.0.139`: 1. 构建局域网版本: `python3 Server/build_phone_apk.py --server-host 192.168.0.139 "Transformers 9.2 offline.apk" build/phone-wifi-unsigned.apk`。 2. 使用 Android build-tools 对齐并签名: `zipalign -f -p 4 build/phone-wifi-unsigned.apk build/phone-wifi-aligned.apk`,然后 `apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android --key-pass pass:android --out build/Transformers-9.2-offline-phone-wifi.apk build/phone-wifi-aligned.apk`。 3. 在笔记本电脑上启动 `python3 Server/run_local.py`。它将在每个网络接口上的 HTTPS 端口 8443 和 HTTP 端口 8080 上监听。如果启用了 防火墙,请允许这两个 TCP 端口通过笔记本电脑的防火墙以连接专用局域网。 4. 通过 USB 安装一次: `CONNECTION_MODE=wifi UPDATE_APK=1 APK=build/Transformers-9.2-offline-phone-wifi.apk Server/provision_phone.sh`。 如果已安装的应用使用相同的签名密钥,更新时会保留游戏数据。你也可以 通过其他可信的本地方法传输并安装已签名的 APK。 5. 断开 USB。在游玩时,请保持模拟服务器运行,并确保手机和笔记本电脑保持在同一个 局域网中。 内嵌的地址必须始终分配给该笔记本电脑。建议使用 DHCP 预留; 如果地址发生变化,请使用新地址重新构建并更新 APK。访客 Wi-Fi 网络 通常会隔离客户端之间的通信,因此无法使用。原生的验证补丁接受 本地服务器证书,因此无需在手机上安装 CA。 ### 通过 USB 在未 root 的手机上运行 零售手机无法使用模拟器中专供 root 使用的 hosts 和系统 CA 绑定挂载。请构建一个 手机变体,将内嵌的后端名称重定向到 loopback 并使用 Android 所有者安装的 CA 信任,然后使用 ADB reverse 通过 USB 传输流量: 1. 运行 `python3 Server/build_phone_apk.py "Transformers 9.2 offline.apk" build/phone-unsigned.apk`。 构建器还会注入当前的 `tools/nativehook/libdothook.so`,因此手机构建版本 包含了与在模拟器上测试过的相同的游戏性修复。 2. 使用 Android `apksigner` 对结果进行签名,生成 `build/Transformers-9.2-offline-phone.apk`。生成的本地构建版本使用你的 Android debug key。如果已经安装了由不同签名构建的同名包,则 必须先卸载它;这样做会清除该安装版本的本地的游戏数据。 3. 在笔记本电脑上启动 `python3 Server/run_local.py`。 4. 连接并授权恰好一部物理手机,然后运行 `Server/provision_phone.sh`。 如果 Play Protect 以 `INSTALL_FAILED_VERIFICATION_FAILURE` 拒绝了这个本地修改过的 APK, 请检查 APK 并重新运行首次安装,命令为 `ALLOW_UNVERIFIED_ADB=1 Server/provision_phone.sh`。这仅针对该次 ADB 安装临时禁用验证, 并在安装后立即恢复这两项设置。后续运行会检测到 已安装的包,并仅恢复端口转发/启动它。重新构建 APK 后,请使用 `UPDATE_APK=1 Server/provision_phone.sh` 原地更新它,同时保留游戏数据。 手机 APK 保留了原始的 target SDK。其捆绑的原生验证补丁接受 本地服务器证书;降低 target SDK 以继承用户安装的 CA 信任会导致 现代的 Play Protect 拒绝该 APK。 必须保持 USB 连接处于活动状态:手机的 8443 和 8080 端口被转发到笔记本电脑的 相同端口。使用明确的非特权端口是必要的,因为原生 Android 的 ADB 无法绑定设备的 443 或 80 端口。在重启或重新连接 USB 调试后, 请重新运行 `provision_phone.sh`。 如果在登录时卡住,在进行任何其他检查之前,请先查看避坑指南部分的第一项。 ## 会耗费你时间的避坑指南 这些都是曾耗费了我数个小时的问题。把它们写下来,这样它们就不会再耗费你的 时间了。 - runtime hook 只能通过原始库中不存在的依赖项条目加载。补丁脚本基于 原始库构建,因此如果不重新添加该条目, hook 就会被静默地不予加载,登录也会卡住。现在补丁脚本在每次 构建时都会重新注入它。如果 hook 似乎失效了,首先要检查的是已打补丁的 库是否实际引用了 `libdothook.so`。确切的字节和偏移量记录在 补丁脚本和 `TECHNICAL_NOTES.md` 中。 - 如果你使用 LDPlayer9/Bluestacks 进行测试,Frida 将无法工作。模拟器将 ARM 转换为 x86, 而 Frida 在这种转换下会崩溃。该项目之所以使用纯字节覆写 inline hook,整个原因就在于它在 Frida 无法生存的地方存活了下来。不要浪费时间去尝试让 Frida 正常工作。 - 设备网络挂载在模拟器重启后无法保留。hosts 重定向和 CA 信任都是绑定挂载。在模拟器进行任何重启后,你都必须重新运行 `provision_ldplayer.sh`,否则将无法连接任何设备。 - 在 Sparx 错误 payload 中,该字段是 `err`,而不是 `error`。使用错误的字段会 导致客户端静默忽略或错误处理响应。 - 交互式教程提示状态在离线时会无限循环。不要试图通过 响应教程请求来满足它。相反,应首先移除触发该教程的条件。 盾牌教程冻结问题正是通过这种方式修复的,即给予玩家 因缺失而触发该问题的资源,而不是通过响应教程请求。 - 模拟器下的实时 3D 内容渲染非常脆弱。模型确实能够渲染,但这 是最不稳定的区域,并且对模拟器的图形后端和纹理设置很敏感。 这是模拟器的图形问题,而不是数据问题。 ## 如果你想真正复活它:重建后端 这是真正的工作,而且非常庞大。以下是它的整体结构以及从哪里开始。 目标是手工重建曾经流式传输到 客户端的服务器端内容:任务和关卡、地图及其敌方阵容、包含每个机器人 属性和能力的完整阵容、战斗公式以及经济系统。这些都不复存在了, 因此所有内容都必须以客户端期望的精确格式重新编写。 行之有效的方法是围绕该项目的循环。在附加 hook 的情况下运行游戏。 hook 会记录客户端读取的每个键。当客户端请求你 尚未提供的内容时,你可以准确地看到它需要什么。然后你合成 正确格式的响应,将其放入 `Server/responses/` 中,或者将其添加到 `Server/fakeserver.py` 中的动态处理程序中,重启,并验证客户端是否接受它并继续推进。重复此操作。当前 构建版本中的每个屏幕都是通过这种确切的方式启动的。`re_notes/dump.cs` 在你运行之前就会告诉你 每个结构的形状,因为它列出了客户端读取的每个字段。 合理的推进顺序: 1. 持久化任务完成状态,并为现有的 STORY 战斗编写奖励契约。 2. 添加原始的逐个机器人能力定义,并测试它们在战斗中的效果。普通攻击 和特殊伤害数据在 `Server/gamedata.py` 中已经拥有了一个可用的生成路径。 3. 每次添加一小部分路径的任务、地图和对手阵容,并使用 runtime hook 和 `re_notes/dump.cs` 来确立客户端契约。 4. 仅在使用它们(需要用到它们)的任务和奖励之后,再添加经济系统和更广泛的进度系统。 要对规模保持现实的认知。即使对于那些在关闭前由玩家保存了 live 服务器数据的游戏来说, 建立私人服务器也是一个漫长的过程。在这里没有任何已保存的数据可供 作为起点,因此每一个数字和每一个能力都必须经过研究或重新发明,然后 针对客户端进行验证。如果目标是重现真实的游戏体验,这将是一项需要多人、 耗时数年的工作。话虽如此,实现路径已不再是未解之谜。离线启动、首次 STORY 战斗 和反馈循环均已正常工作;类型模型也已导出。剩下的就是大量 细致的、原创的数据编写和验证工作。 从 `TECHNICAL_NOTES.md` 开始。它是更深层的技术参考,包含了确切的 补丁、恢复的数据结构以及具体的发现,比这份 README 更为详细。然后运行这个循环。 祝你好运。它现在是一台真正的机器了。只是需要重建它的内容。
标签:云资产清单, 单机化, 底层编程, 游戏修补, 游戏存档, 游戏私服, 逆向工具, 逆向工程