An unofficial Android (arm64) port of Spiral Knights.
SKapsule runs the real Spiral Knights desktop client on your phone or tablet. It ships a custom JRE and a set of native libraries, then boots the game's own Java VM on-device — the game code and assets are downloaded and patched from the official servers at runtime (via getdown), exactly like the desktop client. Nothing about the game itself is bundled in this repository.
Status: experimental / alpha. The game boots, logs in, and is playable, but there are known rough edges (see Known issues). Expect bugs.
Spiral Knights is © SEGA / Grey Havens, LLC. This is an unofficial, fan-made port and is not affiliated with, endorsed by, or supported by SEGA or Grey Havens.
- No Spiral Knights game code or art assets are included in this repository or in the APK. They are downloaded from the official servers at first launch, the same way the official desktop launcher works.
- You need your own Spiral Knights account (web or Steam) to play.
- This project exists to let people play a game they already own on hardware the official client doesn't target. Please support the game through official channels.
Gameplay is gamepad-first. Experimental touch controls are in place, but still need some fine-tuning and testing.
- Gamepad or Touch — primary input for gameplay (movement, combat, menus).
- Keyboard — used as needed for text entry (login, chat). An on-screen Keyboard button is provided for devices without a physical keyboard.
- Grab the latest signed
app-release.apkfrom the Releases page. - Copy it to your arm64 Android device (Android 8.0 / API 26 or newer) and install, allowing installation from unknown sources if prompted.
- Launch SKapsule. On first run it unpacks the bundled JRE and runtime, then downloads/patches the game from the official servers — this first launch takes a while and needs a network connection.
- Choose Play (Web) or Play (Steam) and sign in with your own account.
Only arm64-v8a devices are supported (no 32-bit builds).
The APK is a full from-source build: every native component is compiled from its
submodule, staged into the launcher, and then the Android app is assembled. CI does
exactly this — see .github/workflows/build-apk.yml
for the canonical, reproducible recipe.
| Tool | Version | Used for |
|---|---|---|
| Android SDK | compileSdk/targetSdk 35 | building the app |
| Android NDK | 30.0.14904198 |
native libs (Clang 20; NDK 27's Clang 18 miscompiles OpenAL's C++20 ranges) |
| CMake | 3.22.1 |
native build |
| JDK 8 | LWJGL's Java-8 multi-release layer | |
| JDK 25 | caciocavallo + frenchpress (--release 25) |
|
| ant, ninja, zip | submodule build scripts |
Set ANDROID_HOME/ANDROID_NDK_HOME appropriately.
git clone --recurse-submodules https://github.com/beebono/sk-arm64.git
cd sk-arm64These compile each submodule for arm64-v8a and drop outputs into out/:
./scripts/build-gl4es-android.sh
./scripts/build-openal-android.sh
./scripts/build-lwjgl3-android.sh # JAVA_HOME -> JDK 25 (matches dev box), JAVA8_HOME -> JDK 8
./scripts/build-cacio-android.sh # CACIO_JAVA_HOME -> JDK 25
./scripts/build-frenchpress-android.sh # FRENCHPRESS_JAVA_HOME -> JDK 25./scripts/stage-launcher-assets.shcd launcher
JAVA_HOME=/path/to/jdk-25 ./gradlew :app:assembleReleaseThe output lands in launcher/app/build/outputs/apk/release/. Without signing
configured (below) this is app-release-unsigned.apk.
Release signing reads, in order, a gitignored launcher/keystore.properties then
environment variables. With neither present, assembleRelease still succeeds and
emits an unsigned APK. Provide storeFile/storePassword/keyAlias/keyPassword
(props) or SK_KEYSTORE_FILE/SK_KEYSTORE_PASSWORD/SK_KEY_ALIAS/SK_KEY_PASSWORD
(env) to produce a signed app-release.apk. See keystore.properties.template.
In CI, the keystore is supplied via the KEYSTORE_BASE64, KEYSTORE_PASSWORD,
KEY_ALIAS, and KEY_PASSWORD repo secrets. A push of a v* tag with signing
secrets present publishes a GitHub Release with the signed APK attached.
LauncherActivity ─ unpacks JRE + runtime, picks Web/Steam, launches GameActivity
│
GameActivity (:game process) ─ starts the FCL JVM, sets up EGL/GLES + input
│
native (sklauncher.c) ─ boots the JVM, calls into the bootstrap
│
SkBootstrap ─ runs HeadlessGetdown (validates/patches the game), then invokes
com.threerings.projectx.client.ProjectXApp in-process
The game is a standard desktop Java client, so the port supplies everything that client expects on a platform Android lacks:
| Component | What it provides | Source |
|---|---|---|
| JRE 25 | the Java runtime the game runs on | bundled asset |
| gl4es | translates the game's OpenGL calls to OpenGL ES | gl4es submodule |
| openal-soft | audio | openal-soft submodule |
| LWJGL 3.4.1 | windowing / GL / input bindings (Android-native build) | lwjgl3 submodule |
| caciocavallo | headless AWT bridge (the game uses AWT/Swing for some UI) | caciocavallo17 submodule |
| frenchpress | Steam login (SteamKit-style auth) | frenchpress submodule |
| getdown | downloads, verifies, and patches the game | bundled getdown-pro.jar |
A small bootstrap Java module (SkBootstrap, HeadlessGetdown) and the launcher's
Kotlin installers wire these together.
launcher/ Android app (Kotlin) + bootstrap (Java) — the buildable project
app/ the Android application module
bootstrap/ SkBootstrap / HeadlessGetdown / GLFW shim, staged into the APK
scripts/ build-*-android.sh per native component + stage-launcher-assets.sh
gl4es/ submodule — GL → GLES translation
openal-soft/ submodule — audio
lwjgl3/ submodule — LWJGL Android build
caciocavallo17/ submodule — headless AWT
frenchpress/ submodule — Steam login
out/ native build outputs (generated, gitignored)
.github/workflows/ from-source CI + release automation
- Character-shadow shader artifact. Character shadows can render as a filled quad. The shader link bug is fixed, but a visual artifact persists from another cause. Workaround: set graphics quality to Low.
- Experimental maturity. Lifecycle/resume, input, and login paths work but haven't been hardened across the full range of devices and Android versions.
- APK size. The bundled runtime makes for a large (~90 MB) APK; it is untuned.
These projects were consulted during development. They are not part of the build (the
repo's refs/ working directory is gitignored), but they're credited here as the
shoulders this port stands on:
- getdown — Three Rings' application installer/patcher/launcher; SKapsule drives it headlessly to update the game.
- clyde — Three Rings' game tooling/runtime library used by the Spiral Knights client.
- Amethyst-Android / KnightLauncher-Android — related Android Java-game launchers that informed the JVM-on-Android approach (gl4es, LWJGL, cacio bridge).
- The Spiral Knights desktop Linux install layout — reference for the appdir/getdown layout SKapsule reproduces on-device.
The original code in this repository (the launcher, bootstrap, and build scripts) is licensed under the MIT License.
Bundled and submoduled third-party components retain their own licenses, including
MIT (gl4es), BSD (LWJGL, getdown, clyde), LGPL-2.x (OpenAL Soft, dynamically linked as
a separate .so), and GPL-2.0 with the Classpath Exception (caciocavallo). The LGPL's
dynamic-linking allowance and caciocavallo's Classpath Exception are what permit the
launcher's own code to be MIT-licensed while distributing those components alongside
it; each component's own LICENSE/COPYING file holds the authoritative terms.
Spiral Knights and all related game assets are the property of their respective owners and are not covered by this license.