
TRANSFORMERS: Forged to Fight is a 3D combat game where you take over some of the most charismatic troopers straight out of the Transformers universe. We're talking big names of the likes of Optimus Prime, Megatron, Bumblebee, Ratchet, Soundwave and Grindor–no less. Yes, you read that right. Your favorite characters from any Transformers
This package contains a working offline boot of Transformers: Forged to Fight, plus every tool, patch, and reverse engineering note used to get there. It is meant to be picked up by someone who has the time and energy to take the next and much larger step, which is rebuilding the game's server side content from scratch. Everything here is documented so you do not have to start from zero the way I did.
Read this whole file before you touch anything. The "Gotchas" section in particular will save you days.
The game boots completely offline and reaches its real interactive home screen with no live servers anywhere. From the home screen the menus navigate without crashing: the base, the bots roster (with an owned bot present in the account), the fight mode select, the crystals screen, and the usual popups and tips. The full login flow completes, every online subsystem connects, and the first time experience and tutorial gates are cleared. The scripted intro fight (Optimus vs Starscream) even gets far enough to begin loading the battle, and the 3D character models render and animate.
This was the hard part and it is solved. The client itself is alive again offline.
Actual gameplay does not work. Story shows no missions, and fights cannot fully load. This is not a bug and it is not something a patch can fix.
Forged to Fight was fully server authoritative. The app on the phone is essentially a screen with controls. Almost nothing about the game lived in the app. Every mission, every fight, every enemy lineup, the entire roster's stats and abilities, the economy, and all the balance lived on Kabam's servers and were streamed to the device each session. When the servers were shut down in early 2020 that content database went with them, and it was never released or publicly archived anywhere I can reach.
So the situation is split cleanly in two. The art and audio survived, because they ship
inside the app (see re_notes/ASSET_INVENTORY.txt). Every character is a full Unity asset
bundle holding the model, textures, rig, animation clips, animator controllers, effects,
and audio. The environments, buildings, UI, portraits, cutscenes, and dialogue are all
there too. What did not survive is the data that told the game which of those assets to
use, how to assemble them into a fight or a mission, and what every bot's numbers actually
were. All the pieces are present. There is just nothing left that knows how to put them
together. Rebuilding that is the whole job that remains.
There are four moving parts. Together they let the unmodified game think it is talking to Kabam.
Native binary patches. The game is Unity IL2CPP, so the logic lives in a compiled ARM
library, libil2cpp.so, not in editable script files. patches/patch_il2cpp.py rewrites
six functions in that library to get past the dead server checks: it defeats two
certificate pinning paths so our own TLS cert is accepted, forces the manager
registration block to run even though the live config is null, lets login succeed with
our local device session, and silences the subsystem fatal errors that would otherwise
pop the "failed to log in" dialog. It also re-injects a single dependency entry (see the
Gotchas section) so the runtime hook actually loads. The output is libil2cpp.patched.so.
A fake Sparx server. server/fakeserver.py stands in for Kabam's backend. It listens on
TLS 443 and plain HTTP 80 and answers the game's API calls. Canned responses live in
server/responses/, one file per endpoint, named by method and path, for example
GET__account_data.json. A few endpoints are answered dynamically in code rather than
from a file, because the game expects them to echo values from the request (the tutorial
endpoints and the hero detail endpoint). The response envelope is
{"error":null,"result": ...}. Note that inside Sparx error payloads the field is spelled
err, not error. That detail matters and is easy to miss.
A native runtime hook. tools/nativehook/ builds libdothook.so, a small library that is
loaded into the game at startup and logs every data key the game reads, plus a couple of
targeted behavior nudges. This is the feedback loop that made everything else possible: it
tells you exactly what the game is asking for so you can synthesize a response and verify
it. It is a pure byte overwrite inline hook installed before execution, because the normal
tool for this (Frida) crashes under the emulator's ARM translation layer.
Device wiring. The emulator has to send Kabam's domains to the PC and trust the fake
cert. tools/provision_ldplayer.sh does this in one shot: it pushes the patched library
and the hook, redirects the Kabam hostnames to the PC's LAN address via the hosts file,
mounts the fake CA into the system trust store, and relaxes SELinux. Run it after every
emulator restart, because those mounts do not survive a reboot.
The data flow at runtime is: game makes an HTTPS call to a Kabam domain, the hosts file
sends it to the PC, the fake server answers with a response from server/responses/, the
patched library accepts the cert and the answer, and the hook logs what was read. That loop
is how every screen in this build was brought up.
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 is the single most valuable file for the work that remains. It is the
complete type model of the game: every class, every method, and crucially every data field
the client reads from the server. It is your map of the entire backend API. When you need
to know what shape a response should be, the answer is in there.
These were left out on purpose, because they are large, or copyrighted, or secret, or you should generate your own.