Skip to content

5 · Running it everywhere

The payoff: because the game is translated to portable C++ and the iOS environment is supplied by a swappable runtime backend, the same project builds and runs on platforms the 2012 binary could never have touched.

One source, many targets

Nothing about the game is rewritten per platform. The recompiler emits architecture-neutral C++; the runtime hides every host difference behind a backend chosen at link time. So porting to a new platform means writing one backend, not touching the game.

flowchart TB
    CPP["Translated game (portable C++)<br/>+ runtime core"]
    CPP --> M["macOS<br/><i>Cocoa · OpenGL · OpenAL</i>"]
    CPP --> I["iOS / iPadOS<br/><i>UIKit · EAGL · OpenAL</i>"]
    CPP --> A["Android<br/><i>NativeActivity · EGL · OpenSL</i>"]
    CPP --> W["Web<br/><i>WebAssembly · WebGL2 · Web Audio</i>"]

What it took per platform

The point of the architecture is that this table is short — each platform is a backend, not a rewrite:

Platform CPU Graphics Audio Networking Owns main thread
macOS arm64 / x86-64 OpenGL OpenAL NSURLSession the app
iOS / iPadOS arm64 OpenGL ES (EAGL) OpenAL / AVAudioPlayer NSURLSession UIKit
Android arm64 / armv7 OpenGL ES (EGL) OpenSL ES HttpURLConnection (JNI) the activity
Web WebAssembly WebGL2 Web Audio fetch the browser

The web port, up close

The web build is the clearest proof the architecture works — and, perhaps surprisingly, it was one of the easier targets. From the runtime's point of view, a browser is just another host with the same shape as Android or iOS: the translated game and the runtime core don't change at all, and all we have to supply is a new platform backend. Three things needed attention:

  • Graphics — almost free. The game renders with OpenGL ES 2.0, and WebGL2 is OpenGL ES compatible. The graphics backend forwards the game's GL calls straight to the browser's WebGL context, so there was very little to rewrite.
  • Threads — reworked. The game is multi-threaded, but the browser doesn't do threads the way iOS does. We re-implemented the runtime's thread management on top of the browser's worker model (WebAssembly with shared memory), so every guest thread still gets a real thread of execution.
  • Networking — TCP over WebSocket. The game speaks raw TCP, but a web page can't open a TCP socket. So the runtime's network layer tunnels the game's socket over a WebSocket: the exact same encrypted byte stream, just wrapped in WebSocket frames. Neither the game nor the server logic notices — only the transport underneath changes.
flowchart LR
    G["Game's socket calls<br/>(raw TCP byte stream)"] --> NS["Runtime network layer"]
    NS -->|native build| TCP["real TCP socket"] --> S1["C# server"]
    NS -->|web build| WS["WebSocket frames"] --> S2["C# server<br/>(WebSocket endpoint)"]

The result: the original 2012 game, compiled to WebAssembly, rendering with WebGL2, logging in to the live C# server over a WebSocket and loading a real village — in an ordinary browser tab, with no plugin and no emulator.

Try it yourself — the web build is live at play.retro.atrasisclash.net.

The full picture

Putting all four layers together, here is the complete revived system:

flowchart TB
    subgraph CLIENT["Client (any platform)"]
        direction TB
        GAME["Translated game<br/>(armv7re → C++)"]
        RUN["Runtime<br/>(iOS environment + platform backend)"]
        GAME --- RUN
    end

    subgraph SERVER["Server"]
        direction TB
        PROXY["Proxy / gateway<br/>(RC4, routing)"]
        LOGIC["Game logic<br/>(cpp2cs-generated C#)"]
        SVCS["Home · Alliance · Matchmaking · Chat"]
        PROXY --- LOGIC
        LOGIC --- SVCS
    end

    CLIENT <-->|"Clash of Clans 1.70 protocol<br/>RC4 over TCP / WebSocket"| SERVER
  • Web build In a browser tab — WebAssembly + WebGL2.

  • iOS build On a modern iPhone — the 32-bit game, now native arm64.

  • Android build On Android — EGL + OpenSL ES.

  • macOS build On the macOS desktop.

Recap

Four problems, four layers:

  1. A stripped binary became rebuildable source (reversing).
  2. ARMv7 machine code became portable C++ (the recompiler).
  3. iOS became a swappable runtime (the runtime).
  4. A backend it could no longer reach became a safe C# server (the server).

The result is a 2012 game that no original device can run, brought back as a native app on today's phones, desktops, and the open web — talking to a server that didn't exist a year ago but speaks a protocol from over a decade ago, perfectly.