Add VICE C64 emulator integration to Android companion app
Cross-compiles VICE 3.8 headless for ARM64 and x86_64 as part of the Android Studio build (Gradle buildVice task + build_vice.sh). The JNI bridge (vice_jni.c) renders frames into a shared framebuffer read by C64DisplayView, and forwards key events from C64KeyboardView to VICE's keyboard matrix. Without ROM files the app shows a placeholder blue screen; with ROMs and a .d64 disk image the full C64 emulator runs.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
}
|
||||
@@ -10,6 +12,8 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
ndkVersion = "30.0.14904198"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "de.ladkau.schwertundmagieonpebblecompanionapp"
|
||||
minSdk = 24
|
||||
@@ -18,6 +22,17 @@ android {
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "x86_64")
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_STL=c++_shared")
|
||||
// HAVE_VICE_SRC is detected automatically by CMakeLists.txt
|
||||
// based on whether vice-libs/<abi>/libvice.a exists.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -33,6 +48,86 @@ android {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/jni/CMakeLists.txt")
|
||||
version = "3.22.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VICE cross-compilation task
|
||||
//
|
||||
// Runs automatically before any CMake build step.
|
||||
// Skipped entirely if vice-libs/<abi>/libvice.a already exists.
|
||||
// The tarball at <project>/res/vice-3.8.tar.gz is unpacked by build_vice.sh.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
val viceLibArm = layout.projectDirectory.file("src/main/jni/vice-libs/arm64-v8a/libvice.a")
|
||||
val viceLibX86 = layout.projectDirectory.file("src/main/jni/vice-libs/x86_64/libvice.a")
|
||||
val viceTarball = layout.projectDirectory.file("../res/vice-3.8.tar.gz")
|
||||
|
||||
tasks.register("buildVice") {
|
||||
group = "build"
|
||||
description = "Cross-compile VICE 3.8 headless for Android (ARM64 + x86_64)"
|
||||
|
||||
inputs.file(viceTarball)
|
||||
outputs.file(viceLibArm)
|
||||
outputs.file(viceLibX86)
|
||||
|
||||
doLast {
|
||||
if (viceLibArm.asFile.exists() && viceLibX86.asFile.exists()) {
|
||||
logger.lifecycle("VICE already built — skipping")
|
||||
return@doLast
|
||||
}
|
||||
|
||||
val localProps = Properties().apply {
|
||||
rootProject.file("local.properties").takeIf { it.exists() }
|
||||
?.reader()?.use { load(it) }
|
||||
}
|
||||
val sdkDir = localProps.getProperty("sdk.dir")
|
||||
?: System.getenv("ANDROID_HOME")
|
||||
?: ""
|
||||
val ndkPath = localProps.getProperty("ndk.dir")
|
||||
?: System.getenv("ANDROID_NDK_HOME")
|
||||
?: System.getenv("ANDROID_NDK_ROOT")
|
||||
?: System.getenv("NDK")
|
||||
?: sdkDir.takeIf { it.isNotEmpty() }?.let { sdk ->
|
||||
// NDK side-by-side: pick the first installed version
|
||||
file("$sdk/ndk").takeIf { it.isDirectory }
|
||||
?.listFiles()?.sorted()?.lastOrNull()?.absolutePath
|
||||
?: "$sdk/ndk-bundle".takeIf { file("$sdk/ndk-bundle").isDirectory }
|
||||
}
|
||||
?: throw GradleException(
|
||||
"Android NDK not found.\n" +
|
||||
"Install via: Android Studio → SDK Manager → SDK Tools → NDK (Side by side)"
|
||||
)
|
||||
logger.lifecycle("Building VICE with NDK at $ndkPath …")
|
||||
|
||||
val proc = ProcessBuilder("bash", "build_vice.sh")
|
||||
.directory(layout.projectDirectory.dir("src/main/jni").asFile)
|
||||
.redirectErrorStream(true)
|
||||
.also { it.environment()["NDK"] = ndkPath }
|
||||
.start()
|
||||
proc.inputStream.bufferedReader().forEachLine { logger.lifecycle(it) }
|
||||
val exit = proc.waitFor()
|
||||
if (exit != 0) throw GradleException("build_vice.sh failed (exit $exit)")
|
||||
|
||||
// Touch CMakeLists.txt so CMake re-evaluates the EXISTS check on the
|
||||
// next build and picks up the newly created libvice.a.
|
||||
layout.projectDirectory.file("src/main/jni/CMakeLists.txt")
|
||||
.asFile.setLastModified(System.currentTimeMillis())
|
||||
logger.lifecycle("VICE build complete — CMakeLists.txt touched for re-evaluation")
|
||||
}
|
||||
}
|
||||
|
||||
// Run buildVice before any CMake configure or build step.
|
||||
tasks.whenTaskAdded {
|
||||
if (name.startsWith("configureCMake") || name.startsWith("buildCMake") ||
|
||||
name.startsWith("externalNativeBuild")) {
|
||||
dependsOn("buildVice")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
Reference in New Issue
Block a user