Skip to content

3 · The iOS runtime

The problem: the translated game is correct C++, but it still thinks it's running on an iPhone in 2012. It calls malloc, opens BSD sockets, draws with OpenGL ES, asks UIKit for the screen size, reads files from an app bundle. None of those calls mean anything on their own.

The recompiler gives us the game's own code. Everything that code calls into — the C library, the C++ runtime, the Objective-C runtime, and the iOS frameworks — has to be supplied by us. That's the runtime: a portable re-implementation of the slice of iOS that Clash of Clans 1.70 actually uses, plus a thin platform backend that maps it onto whatever host we're running on.

Two halves: external calls in, platform calls out

flowchart TB
    G["Translated game code<br/><i>(the lifted C++ from chapter 2)</i>"]
    G -->|"malloc, socket, glDrawArrays,<br/>objc_msgSend, fopen, …"| STUB

    subgraph RUNTIME["runtime"]
        STUB["Extern stubs<br/><i>one C++ handler per external function</i>"]
        EMU["Emulator core<br/>memory · files · sockets · threads · Obj-C"]
        STUB --> EMU
    end

    EMU --> PLAT
    subgraph PLAT["Platform backend (one per host)"]
        GL["Graphics<br/>(GL / EAGL / EGL / WebGL2)"]
        AU["Audio"]
        NET["HTTP"]
        UI["Windowing & input"]
    end

    PLAT --> OS["Host OS<br/>macOS · iOS · Android · browser"]

External calls in: the extern stubs

When the original binary called malloc, that was a jump to the system C library. In our build there is no system C library for ARMv7 — so the lifter emits the call as a reference to _extern_malloc, and the runtime provides a handler with that name. At link time the runtime's strong definition wins, and the call lands in our code.

There are a few hundred of these handlers, grouped by what they emulate:

Group Examples Emulates
C library malloc, memcpy, fopen, read, socket libc / libSystem
C++ runtime operator new, static guards, COW std::string GNU libstdc++
Objective-C objc_msgSend, class/selector lookup the Obj-C runtime
Graphics glDrawArrays, glTexImage2D, … OpenGL ES
Audio OpenAL / AudioToolbox entry points iOS audio
UIKit / Foundation UIScreen, UIDevice, NSURLConnection the iOS frameworks

The C++ runtime is worth a note: the game was built in 2012 with Apple's llvm-gcc, so it links the GNU C++ standard library (libstdc++) — not the libc++ that modern Apple toolchains use. You can still see its fingerprints in the binary: copy-on-write std::string, __gnu_cxx helpers, the gxx set-jump exception personality. Our re-implementation has to match that older ABI, not today's one.

Each handler reads the call's arguments out of the guest CPU state, does the real work through the emulator core, and writes the result back — exactly the contract the calling convention defines.

Platform calls out: the backends

Some of those handlers need something only the host can provide — a real window, a real GPU, a real audio device, a real network. The runtime keeps those behind small interfaces and links in one concrete implementation per platform:

  • Graphics — desktop OpenGL on macOS, EAGL/OpenGL ES on iOS, EGL on Android, WebGL2 in the browser. The game's OpenGL ES calls are forwarded to whichever one is linked.
  • Audio — OpenAL + AVAudioPlayer on Apple, OpenSL ES on Android, Web Audio in the browser.
  • HTTPNSURLSession on Apple, HttpURLConnection (via JNI) on Android, the fetch API in the browser.
  • Windowing & input — a Cocoa window on macOS, a UIView on iOS, the native activity on Android, a <canvas> on the web — all funnelling touches and key presses back into the game.

Choosing the platform is purely a matter of which backend library gets linked — there are no #ifdefs scattered through the emulator. That's what makes the same game binary run on five very different hosts.

The emulator core

Behind the stubs sits a small operating-system-like core. The pieces worth calling out:

  • Memory. The 32-bit game expects a flat 4 GB address space. The runtime reserves a matching region on the host and hands out a fast allocator on top of it for the game's malloc/new. (On 32-bit hosts and the web the mirror is smaller, with guard checks.)
  • Filesystem. Guest paths are remapped to host paths — game assets resolve to a read-only bundle, while saves and caches go to a writable sandbox directory.
  • Networking. The game's BSD socket calls are bridged to real host sockets, carefully kept inside the file-descriptor range the game's own select() loop can handle.
  • Threads. The game is multi-threaded; each guest thread gets a real host thread, its own CPU state, and its own guest stack.
  • Objective-C. A minimal Objective-C runtime resolves objc_msgSend by walking the class/method tables the original binary still carries, then tail- calls into the (lifted) method implementation.

Who owns the main thread?

A subtlety that shapes the whole design: on a desktop or in headless CI, our code owns main() and drives the render loop. But on iOS, Android, and the web, the host platform owns the main thread (UIKit, the Android activity, the browser event loop). On those platforms the game runs on a background thread and the platform pumps it.

The render loop is also where asynchronous work is stitched back into the single-threaded game. HTTP requests, for example, run on worker threads, but their completion callbacks are replayed on the game's own thread once per frame — so from the game's point of view, the 2012 iOS networking model still holds.

sequenceDiagram
    participant G as Game (guest thread)
    participant R as Runtime
    participant W as Worker thread
    participant H as Host (NSURLSession / fetch)

    G->>R: NSURLConnection start
    R->>W: hand off request
    W->>H: real network call
    H-->>W: response bytes
    Note over R,G: next frame
    R->>G: replay didReceiveData / didFinish on the game thread

What this layer produces

With the runtime linked in, the translated game finally has an iPhone-shaped world to live in: it boots, renders, reads its assets, plays sound, and opens network connections. The last missing piece is something on the other end of those connections — a server. That's chapter 4.