Sitemap

Resurrecting a Dead Mobile Game in an Evening

6 min readApr 24, 2026

With Claude Code as my reverse-engineering buddy. Could have done it by hand. Wouldn’t have started.

Researcher: {alanh0}

Try again later, forever

I have an old Android tablet that’s been in a drawer for years. Last weekend I dug it out and went looking for an old favorite — an Angry Birds spin-off I enjoyed a lot. The icon was still there. I tapped it.

Asset downloading failed, please try again later!

I tapped retry for ten minutes before it dawned on me. There is no later. The game was delisted years ago. The server it’s trying to reach is gone, and it isn’t coming back.

If you search this exact error, you land on a Reddit thread where the only suggested fix is “join this Discord, someone has a working APK.” Maybe it works. Maybe it works and also has something extra installed in the background. You don’t have to take that bet — the technique to fix it yourself is small enough to do in an evening.

Press enter or click to view image in full size

Why these games die

Modern mobile games rarely ship the full game in the install. The APK is just the engine and a UI shell, and the heavy stuff (audio, sprites, levels) gets downloaded on first launch. Great for download size. Catastrophic for longevity. When the publisher pulls the plug, the asset bucket gets deleted, and even people who already have the game installed can no longer play. The bytes are on the phone. The game just refuses to start.

I could have shrugged. Instead I opened a terminal, started Claude Code, and said: “Help me figure out why this thing won’t start.”

Twenty minutes of compressed grep

The slow part of reverse engineering isn’t typing — it’s looking. Forming a hypothesis, scrolling through grep output, updating the hypothesis, scrolling some more. An agent in a terminal collapses the scrolling into “tell me what you found,” and you stay in hypothesis mode the whole time.

Claude grepped the smali for the error string. Nothing. Then — without me prompting — it tried strings on the native libraries, because "this looks like a thin Java shim around a native engine." That hop alone would have cost me half an hour of trial and error. It cost the agent twenty seconds:

Asset %s: doesn't have valid local copy -> download
exception during asset downloading: %s

The engine checks for a local file first; if it isn’t there it downloads. The crash is the download failing, not the lookup.

That was the whole game. If there’s a “local copy” code path, there’s somewhere to put files that short-circuits the network. A bit more grepping turned up the directory the engine wanted: assets_service/, inside the app's private files dir.

Lucky break: the same old tablet still had the game’s data directory from back when it worked. adb backup plus an .ab unpacker got me a 50 MB files/ folder with exactly the structure the engine expected. The plan wrote itself:

  1. Ship that files/ directory inside the APK at assets/preload/.
  2. Add a tiny class that copies assets/preload/* into the real files/ dir on first launch.
  3. Drop a marker so it only runs once.
  4. Repackage and re-sign.

No engine modification. No native binary patching. Just preempt the download.

The smali shortcut nobody tells you about

The copier wants to be Java. The catch: the rest of the decompiled APK is smali — Dalvik bytecode in human-readable form — and you have to ship a .smali file that matches. Smali is fine for one-line tweaks; it's miserable for anything with a loop or a try/finally. Get the register count wrong and you get a runtime crash that's hard to debug.

You could ask the LLM to write smali directly. It would probably be right. “Probably right” is a terrible property for compiled bytecode.

So: write the copier in Java, and bounce it through the official toolchain so the compiler generates the smali for you:

javac -bootclasspath $ANDROID_SDK/platforms/android-29/android.jar \
-d classes src/com/example/PreloadAssets.java
$ANDROID_SDK/build-tools/29.0.2/d8 --min-api 16 \
--output dexout classes/com/example/PreloadAssets.class
java -jar baksmali.jar disassemble dexout/classes.dex -o smali_out

Out comes a .smali file with registers correctly numbered and exception ranges correctly bracketed. Drop it into the right package directory under decoded/smali/. Done.

This — not “write the answer,” but “compose existing tools into a pipeline guaranteed to give the correct answer” — is the most interesting kind of LLM use I’ve found. The agent picked the toolchain, ran the steps, and chased down the standalone baksmali.jar when the bundled one didn't work. I didn't have to remember any of it.

One line of smali, then ship it

The launcher activity’s onCreate follows a familiar pattern: call super.onCreate(...), build the view tree, hand the file path to the native side. I wanted my copier to run after super.onCreate (activity is initialized) but before the native side gets the path (files exist when it goes looking). One line:

invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
invoke-static {p0}, Lcom/example/PreloadAssets;->extract(Landroid/content/Context;)V

The agent verified the existing .locals 3 had room — the kind of detail I'd absolutely have missed at midnight. That's the entire engine modification.

Then the mechanical bit: apktool b, zipalign, generate a self-signed key, apksigner with v1+v2+v3, uninstall the old copy (signature changed, so adb install -r refuses), adb install the new one.

Tap the icon. Splash. Loading. Music starts. Title screen.

What the LLM actually changed

Not “AI did the reverse engineering for me.” I made every decision: don’t patch the native binary, ship the files, copy at first launch, use the Java→dex shortcut, inject after super.onCreate. The agent didn't conjure those — it executed and verified them.

Not “this is just a faster grep” either. The bottleneck on tasks like this isn’t capability, it’s attention. Every minute scrolling through grep output is a minute you’re not forming the next hypothesis. An agent that handles “find this,” “summarize that,” “verify this against the code” lets you stay in the part of the work that actually requires you.

The honest version: this kind of project went from “I really should set aside a Saturday for this” to “let me try it after dinner.” That shift in activation energy is the thing. Not “AI does my job” — AI lowers the bar for the kind of side quest worth starting at all.

If you want to try this on your own old game

You’ll need: the APK (adb pull it from your device), the app's /data/data/<package>/ from when it worked (adb backup + android-backup-extractor), and a machine with apktool, the Android build-tools, a JDK, and Claude Code.

Three prompts, roughly in this shape:

1. Diagnose. Old Android game APK crashes at startup with ‘asset downloading failed.’ I have the game’s data directory from when it worked. Decompile with apktool d, grep smali, run strings over the native libs. Tell me where the error originates and which on-device directory the engine expects assets in. Don't change anything yet."

2. Plan. Approach: bundle the captured files into assets/preload/, add a first-launch copier in Java, generate the smali via d8 + baksmali (don't hand-write smali), inject the call into the launcher activity's onCreate right after super.onCreate. Use a marker file so the copier runs only once. Write me a short plan first."

3. Execute. Go. Rebuild with apktool b, zipalign, sign with a self-signed key (v1+v2+v3). Tell me the final APK path."

Things worth insisting on: always make the agent show you the diagnosis before it changes anything. Keep the scope small (“fix the asset download crash” is a goal; “also re-enable multiplayer” is how you brick the APK at 2am). Never accept hand-written smali — always go through javacd8baksmali.

This won’t work on every game. License checks, server-keyed asset formats, and anti-tamper systems all break the pattern. But for a surprisingly large fraction of abandoned mobile games, this same shape works.

One last thing

How much of the software we love is one quietly terminated cloud account away from being unbootable? Mobile games are an extreme case, but the dynamic shows up in always-online single-player titles, in firmware that pings a server before booting, in “smart” appliances that brick when their company is acquired.

If you have a beloved old app on an old device, my unsolicited advice: back up its data directory now, while it still works. You won’t be able to recreate those bytes later. The files I needed for this resurrection were already on my own device, from a happier time. Without them, the story ends at try again later, forever.

Some things deserve to be allowed to keep working. And it’s gotten cheap enough — in time, in attention, in activation energy — to make sure they do.

Tools: apktool, d8, baksmali, zipalign, apksigner, Claude Code in a terminal. No download links to APKs or assets — this is a description of a process, not a distribution. To repeat it, bring your own APK and your own captured data directory.

--

--

VXRL
VXRL

Written by VXRL

VXRL Team is founded by group of enthusiastic security researchers, providing information security services and contribute to the community. https://www.vxrl.hk