Zombie Cafe is an old mobile game from 2011, that I used to play on my little ipod touch. Unfortunately, at some point it was removed from app stores, and the servers have been offline for a long time.
There are a few available APK downloads for this game, but none of them are without issues. There is a download for the 1.1.2.0a
version, which crashes upon trying to open the game. And there is a download for the 1.7.0.0
version, which is mostly functional, but is also in Japanese.
Compounding these issues, the servers that this game is programmed to connect to are now either offline or redirecting elsewhere, which the game doesn’t know how to handle, and causes some crashing problems.
The goal of this project is to fix major crashes in the 1.1.2.0a
version of the game, reimplement the game servers, and backport content from the Japanese version of the game back to this english version. I would also like to remove the reliance on microtransactions from the game, since you can no longer purchase these and they lock away a significant amount of the game.
Admittedly I was somewhat nerdsniped by this project…
Getting started
The general workflow I’ll be using for this project is to extract the contents of the APK using apktool
, then making changes to the game data and smali code and packing it back to an apk also using apktool.
Fixing major crashes
Crash during startup
The first and most obvious crash happens right when loading the game. Running the game in an android VM and reading the log exposed the following message:
Permission Denial: not allowed to send broadcast android.intent.action.MEDIA_MOUNTED
This appears to be due to a permission change in the Android API, which must have happened after this version was released. I was able to find the smali code which was calling this API pretty quickly and remove it. After doing this, the game actually opens!
However, there is a clear graphical issue, a lot of objects appear to be rendering a very bright cyan color… We’ll get back to this in a bit
Random crashing during cafe load
The next most frequent crash sometimes happens shortly after opening the game. There wasn’t any super clear cause, but the android logs indicated that the crash was happening when the game made an HTTP request to fetch gifts. Zombie Cafe had a strong social aspect which integrated with Facebook, and you could send gifts to your friends who also played the game.
Following the HTTP request, the request gets redirected a few times, and returns some website, which clearly is not what the game is expecting, and thus crashes when attempting to parse the data.
As a quick workaround for this fix, I found a function in the smali for checking if there is an active internet connection. I just patched this function to always return false
. Though I will need to fix this properly later if I want to restore online functionality.
Crashing during animation/texture unload
This one was particularly hard to track down. The logs indicated that the crash was occuring on the graphics thread, and often occured after the log prints a string about deleting images.
|
|
The rest of the log was somewhat cryptic, but seemed to be pointing towards an issue with the game’s memory management regarding textures
|
|
I was stuck here for a while, not really sure how to progress. I couldn’t find any way to attach a debugger to this and the backtrace wasn’t showing a lot of information after calls to ndk_translation
. So all I could tell was there was a issue with freeing some memory somewhere.
I decided to research a little more in to what ndk_translation
was, and luckily for me it appeared to be something that only the Android VM uses for translating native calls. So I loaded the apk up on my phone and low and behold, the crash log contained a lot more information when running on an actual device.
|
|
Now I was easily able to see that this was occurring during calls to the destructor of CImage
. Great! I know where the crash is happening, but actually fixing this one was a bit harder. With other crashes, I could fix them easily by editing the smali, only touching the Java side of things. But this crash was occuring inside the native library.
I suspected from the start I would eventually need to find a good way to patch the native binary, but wasn’t too sure how to approach it. While I have done my fair share of patching x86/x64 binaries on PC, I had no idea how to best approach patching an ARM binary on mobile. I tried a few methods, from just manually patching the file with a hex editor, to using radare2
to try apply patches in command line. But ultimately I decided I should just compile my own library, and apply patches at runtime with memory manipulation.
libZombieCafeExtension
libZombieCafeExtension is the library I wrote to apply these patches. Initially, I wanted to create a ‘wrapper’ around the game’s original libZombieCafeAndroid
, where I just expose all the original exports of that library in my own library, and bridge the calls across accordingly. While I technically got this working, it was resulting in some interesting issues where the game would render and behave in slightly strange ways. It still doesnt make a lot of sense to me as to why, but I decided making the library in this way was changing an unnecessarily large surface area, and I was writing more code trying to fix these new issues I created, instead of just fixing the actual issues in the game.
Ultimately I ended up just writing a library that doesnt mess with any exports, and just patched the smali code to load my library directly after it loads the original libZombieCafeAndroid
library. This way I can make all my patches before anything else in the game has a chance to run.
Then I was able to “fix” the texture unloading crash. Ideally I would have written this to hook the games original texture allocating code and handle it myself, for now I have simply opted to NOP out the call to the texture destructor. Yes I know, this causes a memory leak. But textures dont get unloaded very frequently, and this is only deallocating a reference to a texture, its only 0x18 bytes. I think our modern phones can handle it… Maybe I will come back and fix this properly if I get bored, but for now a memory leak is better than a crash!
Fixing Cyan Rendering
This problem just about made me give up. There was seemingly no reason this should have been happening, and without fixing this the whole project was relatively useless, as I cant exactly claim to be ‘preserving’ a game if I have made it render so poorly.
The only thing with any indication of causing an error was a shader compile error during the game’s startup.
I got to work investigating this, replacing the shader code in the game’s binary but nothing I did seemed to have any effect at all. I tried changing the shader to always return White, but still things rendered as cyan. I replaced the shader with nonsense code that couldnt possibly compile. Still cyan. I replaced the shader with a different shader from other parts of the game. Cyan.
Now, I was solving this issue somewhat in parallel with trying to figure out where that texture unload crash was coming from. Right when I loaded the APK up on my actual phone, I noticed. THE CYAN WAS GONE. Without any shader tricks at all, the game rendered perfectly fine on my phone, and was cyan only in the virtual machine. So problem “solved”?
Anyways, with the most major issues in the game fixed, I could get started on the actual fun parts. Reversing the games proprietary file formats, and netcode. What I found will shock you! ;)
File Formats
For the most part, the file formats were relatively easy to figure out. They are mostly standard binary files, just containing raw data. With a bit of disassembly of the game code, just looking in the hex editor, and some trial and error with changing bytes and seeing what effect it has, I was able to figure out enough of these files to be able to make substantial changes to the game. I wont go in to a lot of detail for these files, I think if you’ve seen one C style struct you’ve seen em all. But there was one file format that stood out from the others…
.cct File
.cct.mid
aka CCTX
. The cct
files contain the majority of the games texture assets. but looking at the data didnt clearly show particulary much of anything. It started with file magic of 4 bytes making up the string CCTX
and then just seemingly random data. I couldn’t understand much of it, and decided to have a look around online.
This lead me to to a few sources saying that .cct
is a CAST file created in Adobe Director. A now discontinued software which wikipedia says:
Adobe Shockwave (formerly Macromedia Shockwave and MacroMind Shockwave) is a discontinued multimedia platform for building interactive multimedia applications and video games.
It certainly sounds like it could be our culprit.
With this information, I found a few projects people had already made for unpacking this file format, mainly ProjectorRays. I downloaded this project and had a try using it to decompile my files, but it wasn’t working.
I joined the ProjectorRays Discord server to ask if my file looked at all similar to Shockwave’s cast file format, where I was promptly told that no, unfortunately it didnt look similar at all.
Defeated, I went back to the hex editor and stared for a while. When suddenly, out of the ashes of my defeat a message comes in from the ProjectorRays server.
ZLib Compression
OH! 78 DA
of course this is zlib data! Even though my project turned out to be completely unrelated, the ProjectorRays / Adobe Shockwave arc turned out to be extremely beneficial. Funny how things work out.
With a quick hop skip and a jump I got to work decompressing this data, and immediately upon looking at the decompressed data in a hex editor I could see it was raw image data. The way the ascii representation made interesting patterns made it clear. So I took this data in to GIMP and tried to interpret it as raw data. After tweaking the settings a little, the following emerged
I mean yea, its not perfect but we are clearly moving in the right direction. With this new found momentum I was also able to figure out that some of the data in the .cct.mid
file before the compressed blob contained information about the image resolution and compressed data size. (Note that this data was stored in little endian, regardless of the fact that big endian is used in EVERY OTHER FILE FORMAT IN THIS GAME)
Regardless, with this extra information I was able to get the image to display somewhat better
But the colors are clearly wrong, and the image has been seemingly duplicated but with minor differences once to the left. I was stuck here for a while. I thought maybe the duplication could be some form of mip mapping? maybe this is some weird compression thing? Compression could make sense, since I would expect a 2048 x 2048 RGBA uncompressed image to be a bit over 16MB, and this is only 8MB.
Eventually I was able to trace the loading of this texture back to the following code:
glCompressedTexImage2D
? Maybe it is compressed after all? The number 0x8c01
lead me to the following formats:
I downloaded some tools that can display textures compressed with this file format but nothing seemed to be working. All the ‘decompressed’ versions were coming back even more broken than what I already was getting in gimp. I decided this must not be compressed, it wouldn’t make sense for a compressed file to even display close to what I was getting in gimp. There must be something else going on here. That’s when I realised I had made a bold assumption that a color must be represented with 4 bytes, 1 byte per channel. While maybe this is a fair assumption in the current year, this is a much older game, maybe they were using 16 bit color? a 2048 x 2048 image with 2 bytes per color would actually come out to about 8MB so it makes sense. In the end, this was exactly the case.
The data is an uncompressed image where every two bytes makes up one color. 4 bits per color RGBA. With this, I was finally able to read the data in all its 16 bit glory
You may think this is the end of the line for CCT files, but you would be wrong. The developers of this game had another trick up their sleeve, ready to stop me in my tracks.
Hard coded scaling factors per file and image offsets
As you can see in the previous image, a single CCT file contains an image with other textures packed in to it. “This is no problem” I thought to myself, as I already understood the file format the game uses to store information about texture offsets in this file. I was able to quickly extract individual textures from this file using the offsets data.
Huh? Thats weird, why was recipeImages
working perfectly while furnitureImages
wasn’t? Why are some of the coordinates in furnitureImagesOffsets
claiming to have an X coordinate of "X": 2327
when the image is only 2048x2048 in resolution?
Well as it turns out, the developers, in their infinite wisdom, decided to hard code scaling factors for the coordinates of these offsets on a per file basis. So there is no information in the files themselves about what scale factor it uses, the developer just needs to remember the correct one I guess? I think maybe its meant to accomodate for switching between different resolutions based on the user’s device?
|
|
But with this brain blast I was able to get textures unpacking properly.
Then with the help of some rectangle packing algorithms I was able to reverse the whole process and can now write my own CCT file, complete with custom offsets! Combine this with the characterData
file I have already reversed, I have all the information necessary for adding entirely new characters from the game, so I can start on backporting some characters from the Japanese version.
Though, the japanese version has 5 different character sheets wheras the english version is only built to handle 2. So I am limited by how many characters I can fit in to the sheet, unless I hook the game’s code and make the necessary modifications to load more data.
Networking
If you thought the implementation of the cct file format was questionable, well strap in because the networking is no better.
Thanks to libZombieCafeExtension
, I am eaily able to patch the URLs that the game makes requests to, and spin up a simple http server to listen for requests.
The first thing I found is that the game will periodically upload the user’s current save game, thats fine I can store that and use the data to restore functionality to allow people to visit other player’s cafes again.
An interesting thing I found is the Gifting “protocol” if you can call it that. A Gift is sent using a string like so:
|
|
They’re just parsing it based on line endings and a good old string.Split(":")
. I guess thats one way to do it, but come on guys, I know you already have a functioning JSON parser in the code because I’ve seen your requests to the facebook api. Why are we doing this?
Also interestingly, when the game fetches gifts from the server, it stores this in a file where gifts are seperated only by a new line. But I think maybe there is a bug in the code, unless maybe I’m not understanding it properly. But it appears that the game always skips the first line when parsing this file, and then continues to read to the next line, So I need to always prepend my responses with a new line for it to be consistent? It’s strange. I think it isnt exactly a great idea to split two parts of a single message only with newline, as if two messages are both written to the file and the newline isnt exactly where it should be, the game cant figure out which line is which part of data?
I decided to host this service using cloudflare workers, and a simple KV storage to store user data. While it’s not ideal, its an easy solution and I was able to make use of my existing data management code thanks to syumai/workers
Microtransactions Replacement
A major issue with this game is its reliance on microtransactions, which are no longer purchaseable as the game is no longer on any stores. In order to make this game more accessible, I needed to add a way to get items which would otherwise be locked behind this paywall.
In the game, it is possible to buy regular game currency with the premium currency. I decided patching this to make it so you can buy premium currency with regular currency would be the easiest way to make this more accessible. I was able to achieve this by editing the games resource files to change the prices and amounts of currency purchased via this method, and then patched the game binary so it would increase the premium currency instead of the regular currency.
Writing this patch was quite interesting, and I was able to discover what I assume is some kind of micro optimization in ARM compiler.
I was able to track this purchase down to this code here
|
|
With a bit of effort, we can see that (r1_10 + 0x1ac)
is where the game’s current money is stored. at 0x00ab020
we can see a number being added to this amount, and then being set back to the games money amount at 0x00ab022
. So if I can change this offset from 0x1ac
to point to the premium currency instead, we can make the game increase our premium currency.
When it came to patching this assembly though, things got interesting
|
|
There is a significant lack of the number 0x1ac
in the assembly that executes this code. But lines 2 and 3 are particularly interesting, as it loads the number 0xd6
in to r3
and then bitshifts once to the left. This results in 0x1ac
being stored in the register. Im not sure exactly why the compiler chose to use this method as opposed to loading 0x1ac
in to the register directly. I assume its either slightly faster or results in a smaller binary size?
My patch just changes the code to load 0xb8
in tor3
, which I figured out was the offset to the premium currency, and then I just NOP the bitshift so r3
keeps the value of 0xb8
thus updating the game’s premium currency amount!
Conclusion
Anyways, I’ve managed to research enough to be able to implement all the changes I would like to. If I have the time I would like to research the game’s animation file format, but its not particularly useful for anything at this time beyond purely academic purposes. Though, the academic value of researching Zombie Cafe Animation File Format is questionable.
Check out the project here