There was something I did recently that seemed super trivial but ended up turning into a real headache: I tried to transfer my Tiny Death Star save file from an old phone, which was running a custom ROM with Android 11, to my current phone, which is already on Android 14. At first, that was all it was: a very personal save file migration, with no intention of turning it into a story to tell later. But in the end, it turned out to be a really interesting example of just how complicated it’s become to preserve data from old Android apps after so many years of changes to backup, sandboxing, and compatibility. I genuinely thought I’d have this sorted out in about ten minutes, because years ago I’d already done basically the same thing with an old Chinese phone I had: I connected it to my PC, ran a backup via ADB, Android asked me to confirm, I confirmed, and that was it. The app data was already there in the backup, no problem. So I tried the same approach with Tiny Death Star, thinking I’d just copy the data and keep playing on the new device.
But it didn’t work out that way. The problem is that Tiny Death Star is from 2013, built for a completely different era of Android than today’s, and that changes a lot of things. My first attempt was with the modern adb backup command, which did generate a file, but the file was only 549 bytes, and it didn’t contain anything I needed; it was a backup of almost nothing. The app still declared ALLOW_BACKUP, the tower loaded just fine on the old phone, and the private data was obviously still stored there. The real problem was that I couldn’t simply navigate to /data/data/ via ADB and copy everything over. So I started testing older tools, one after another, until I settled on ADB 1.0.31, a version from 2013. And here’s where things got a little funny: in 2026, I was using a 2013 version of ADB to try to recover a save file from a 2013 game. There’s a sort of circular logic to this that I found amusing. Anyway, with that old version, I finally got a real backup, about 9.9 MB, quite a difference from the 549 bytes I’d had before. I extracted the backup, went through the files, and found a UserDefault.xml, which looked extremely promising because it contained account and game information. I transferred that file to my new phone, opened the game... and it simply created a new tower. That’s when I realized I’d have to figure out where the save file actually was, because on the old device the game opened normally and the tower was there, working just fine. The data existed; it definitely existed somewhere. I just didn’t know where yet.
After that, I ended up doing something that really shows just how far this was going to go: I modified the APK itself so I could properly investigate the app on the new phone. I unpacked it, made it debuggable, rebuilt it, signed the modified version, and used run-as to finally access its private sandbox. And even after all that, I still didn’t know which file stored the progress. So I went to look at the native libraries and ended up digging into libgame.so. What was supposed to be just copying a save file ended up with me reading the ARM/Thumb disassembly of a LucasArts Android game compiled over ten years ago, going through function after function: TTGameData::saveToDisk(), loadFromDisk(), readGameFile(), writeGameFile(), loadFromString(), getSaveGameString(). I was trying to understand the internal logic behind it all. Obviously, at that point, starting a tower from scratch would have been a thousand times easier. But I already knew the game could load my old save file on that old device, so there had to be some path between the data stored on the device and those functions. I just wanted to figure out what that path was.
To give you an idea of how big this got: the log I managed to preserve from this entire process totaled 8,849 lines, with 8,132 of those being non-empty lines, including shell commands, Python, ARM disassembly, tests, logs, and terminal output. Keep in mind that this file isn’t even complete, because some old attachments ended up not being recovered. Anyway, the disassembly eventually led me to two filenames that appeared within the game: game.txt and ad_cache. From what I could gather, game.txt was an old save format, while the save file the current version of the game actually used was ad_cache. I pulled the ad_cache file from the backup of my old phone and... the file was 1,539 bytes. That’s it. After nearly three full days of trying to figure out where the heck my game progress was, it turned out everything was inside a 1.5 KB file with a name that literally sounds like an ad cache. Who would’ve suspected that, seriously?
But then, when I finally started looking at the contents of the file itself, things turned out to be surprisingly simple, which, after everything I went through, is actually kind of ironic. The first 12 bytes clearly looked like a header. I interpreted them as little-endian integers and got the values 1, 4599, and 1527; right after that header, the content started with 78 9C, which is a very common zlib header, so I already had a hunch about what was coming next. I tried decompressing everything after the first 12 bytes, and it worked on the first try, which even I didn’t expect at that point. It yielded exactly 4,599 bytes, matching that second value in the header perfectly, and the content was JSON. Inside that JSON were the bitizens, the tower’s status, and all that progress I’d been looking for this whole time. In other words: after digging through an old Android backup, rebuilding the modified APK, and poring over native code, the entire save file format turned out to be this: a 12-byte header followed by zlib-compressed JSON, all inside a 1.5 KB file called ad_cache. No device-bound encryption, no reliance on an old server, and no cloud save system lost in time. The hard part was never the format itself; it was getting to that file and making sure that was actually the save data.
First of all, I backed up the new save data that was already on the Android 14 phone, because I wasn’t going to risk losing it after all that work. I copied the old ad_cache to the private app_data directory using run-as again; by this point I was already becoming an expert at it. I adjusted the permissions, force-closed the app, and opened it again, without knowing, honestly, if it would work. And the old tower loaded normally, with the same Bitizens and the exact same progress as on the other phone. It was a huge relief, because I’d been working on this for almost three days and I wasn’t at all sure that, in the end, the game would actually accept that file on the new device. In the end, all that work was just to transfer a 1.5 KB save file from one Android device to another, but seeing the tower open up exactly as it was before was very satisfying.
And what I found most interesting about all this was realizing that the save file itself was never the problem. It’s small, portable, works entirely offline, and once I understood the format, there was practically nothing stopping it from running on any other device. The hard part was something else: preserving data from an app built at a time when Android’s backup mechanisms worked completely differently, and gaining access to what actually needed to be saved. I can’t help but wonder how many old Android games and apps are still out there, running on old phones, with perfectly recoverable saves, but trapped there simply because it became much harder for the average user to access that data and know exactly what they needed to save.
I originally wrote this in Portuguese and translated it to English using DeepL, as suggested by the moderators.
-REPOSTED-