Overview
This is not for the faint of heart! This section goes in-depth into the internals of how Dead Cells is ported to mobile, how it runs, and how to effectively mod it.
As you should (hopefully) already know, Dead Cells is a Hashlink game. However, production desktop builds of it use Hashlink bytecode, which is very easy to patch and decompile, thankfully for us! On mobile, though, we're not as lucky.
HL/C and ARM Cross-Compilation
HL/C is a transpiler that can take Hashlink bytecode and fully convert it to C source code that can be compiled and run with a lot less overhead than the full dynamic VM (or the profiling VM written in OCaml as part of the Haxe toolchain). As well as this, the Hashlink VM isn't supported on ARM at all - only on x86 and x64. This means that Playdigious had two options when porting HL games to mobile:
- Completely rewrite and reimplement the (incredibly complex and weird) HL VM so that it can run fully on ARM
Or:
- Use the tooling that already exists and already supports ARM (HL/C) to compile a faster native binary directly for the target platform.
Obvious, you can guess which option they picked. This is inconvenient for us, though, sine we lose a lot of high-level debug information from the bytecode, and we lose the ability to easily patch the bytecode, and are forced instead to patch the resulting compiled assembly more directly.
Android systems run either ARM (usually aarch64) or x86/x64, and iPhones are ARM (aarch64). Mobile builds of Dead Cells only target aarch64, which isn't too big of a deal considering their prevalence nowadays on the mobile market. This does raise some interesting questions about whether or not the HL VM could be more directly ported to Android running on x86, but that's a question for another day. The important takeaway from this section is that:
- Whereas the PC builds of DC are HL VM bytecode that can be inspected and patched easily...
- Mobile builds of DC are assembly directly compiled from HL/C translations of the same original HL VM bytecode
- This removes a lot of useful information and makes our ability to core mod/patch much more limited and low-level
Platform-Specific Wrappers
Great! Now, we have a piece of native code that can run on our target mobile system's CPU directly. However, we still need to give it input from the OS's interfaces, and route output back to the OS - this is where a wrapper comes in. On Android, this is written in Java, and can be very easily decompiled and inspected:
public class DeadCells extends SDLActivity {
static String LOG_TAG = "DEADCELLS";
private boolean initAssetsHasBeenCalled = false;
public static native void initAndroidUtils(String str, String str2);
public static native void initUtils(int i, boolean z);
public static native void togglePauseStatus(boolean z);
protected void onCreate(Bundle bundle) {
ReLinker.recursively().loadLibrary(this, "native-lib");
//...
ThirdPartySDK.onCreate(bundle);
//...
}
public void onBackPressed() {
log("onBackPressed called");
if (!"googleplay".equals("bilibiliuo")) {
super.onBackPressed();
}
ThirdPartySDK.onBackPressed();
}
// and a whole bunch more funtions like this...
As you can see, the main Activity class just basically proxies information to and from the native lib, libnative-lib.so
. This massive (~50MB) binary contains the entire compiled DC codebase, as generated by Hl/C. You can find this in the lib/arm64-v8a/
of the Android builds - we'll get to iOS in a seperate document, because handling it is infinitely more complicated than Android.
Remarkably, on Android builds, debug information wasn't stripped from the final build - so function names and type information are still there. It may, in fact, even be possible to recover a lot of bytecode metadata or even some opcodes through pattern matching and clever parsing of the debug symbols.
PAK Files
Ideally, you should have already read the other page on the PAK format, but you don't really need it. The mobile PAK files still work basically the same as those of the desktop version - they're just split between a few files. THe layered loading system used in the desktop versions of the game still very much applies.
It is still unclear if the game will find additional PAKs if they are added to the app bundle of the game. The paths for the PAKs may be hardcoded into the compiled binary.
So what can we do?
It's completely possible to repack the mobile versions of Dead Cells with custom changes to files. You can pretty easily:
- Apply small changes to PAK files and assets
- Replace PAKs altogether
You cannot, however, use DCCM or other modding frameworks at all, since they rely on being able to control the HL VM, which does not exist at all with HL/C compilation.