mirror of
https://github.com/izzy2lost/Ghostship.git
synced 2026-10-26 11:45:44 -07:00
- Updated various level headers (vcutm.h, wdw.h, wf.h, wmotr.h) to ensure macro object strings are aligned using ALIGN_ASSET(2). - Modified texture headers (effect.h, skyboxes/bbh.h, bidw.h, bitfs.h, bits.h, ccm.h, cloud_floor.h, clouds.h, ssl.h, water.h, wdw.h, title_screen_bg.h) to apply ALIGN_ASSET(2) to string declarations for consistency and improved memory alignment. - Refactored Game.cpp to clarify asset unpacking process on Android, removing unnecessary AndroidUtils.h and AndroidUtils.cpp files. - Enhanced GameExtractor to support explicit asset and game path parameters for better compatibility with Android's asset management. - Updated Rando module to resolve randomizer folder path on first use to avoid issues with global constructors during Android initialization. - Improved error handling in asset loading within AssetArrayFactory to log missing assets more effectively.
190 lines
7.0 KiB
Kotlin
190 lines
7.0 KiB
Kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
import java.security.MessageDigest
|
|
import java.util.Properties
|
|
|
|
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
}
|
|
|
|
val repositoryRoot: File = rootProject.projectDir.parentFile
|
|
|
|
// One source of truth for the version: the CMake project declaration.
|
|
val projectVersion: String =
|
|
Regex("""project\s*\([^)]*VERSION\s+([0-9.]+)""")
|
|
.find(File(repositoryRoot, "CMakeLists.txt").readText())
|
|
?.groupValues?.get(1)
|
|
?: "0.0.0"
|
|
|
|
// Release signing: android/key.properties locally, the environment on CI. The
|
|
// file holds passwords, so it is gitignored and never read into the build
|
|
// output.
|
|
val keystoreProperties = Properties().apply {
|
|
val file = rootProject.file("key.properties")
|
|
if (file.isFile) file.inputStream().use { load(it) }
|
|
}
|
|
|
|
// With neither configured — a fork's CI, or a fresh clone — the release build
|
|
// falls back to the debug key so it still produces an installable APK rather
|
|
// than failing on a keystore nobody has.
|
|
val hasReleaseKeystore =
|
|
keystoreProperties.getProperty("storeFile") != null || System.getenv("KEYSTORE_FILE") != null
|
|
|
|
// Torch runs on the device to turn the user's ROM into sm64.o2r, and it reads
|
|
// its extraction recipes off the filesystem. They ride into the APK in one zip
|
|
// alongside the engine archive; the launcher unpacks it into the app's external
|
|
// files directory on first run (see GameAssets.kt).
|
|
val gamedataDir = layout.buildDirectory.dir("generated/gamedata")
|
|
|
|
val packGameData = tasks.register<Zip>("packGameData") {
|
|
description = "Bundles the Torch recipes and the engine archive into the APK."
|
|
val engineArchive = File(repositoryRoot, "ghostship.o2r")
|
|
doFirst {
|
|
if (!engineArchive.exists()) {
|
|
throw GradleException(
|
|
"ghostship.o2r not found at $engineArchive — build the GeneratePortO2R target " +
|
|
"on a desktop platform (or download the CI artifact) before building the APK."
|
|
)
|
|
}
|
|
}
|
|
from(File(repositoryRoot, "assets")) { into("assets") }
|
|
from(File(repositoryRoot, "config.yml"))
|
|
from(engineArchive)
|
|
archiveFileName.set("gamedata.zip")
|
|
destinationDirectory.set(gamedataDir)
|
|
// A reproducible zip means a stable digest, which means an app update only
|
|
// re-unpacks when the contents actually changed.
|
|
isPreserveFileTimestamps = false
|
|
isReproducibleFileOrder = true
|
|
}
|
|
|
|
val stampGameData = tasks.register("stampGameData") {
|
|
description = "Writes the digest the launcher compares against to decide whether to unpack."
|
|
dependsOn(packGameData)
|
|
val stampFile = gamedataDir.get().file("gamedata.version").asFile
|
|
outputs.file(stampFile)
|
|
doLast {
|
|
val zipFile = gamedataDir.get().file("gamedata.zip").asFile
|
|
val digest = MessageDigest.getInstance("MD5").digest(zipFile.readBytes())
|
|
stampFile.writeText(digest.joinToString("") { "%02x".format(it) })
|
|
}
|
|
}
|
|
|
|
// Derived from the task rather than named as a plain path, so every consumer
|
|
// picks up the dependency. Naming the directory directly only looks fine until
|
|
// something other than the asset merge reads it — release builds run lint-vital
|
|
// over the source sets, and that fails on the missing dependency.
|
|
val stagedGameData: Provider<File> = stampGameData.map { gamedataDir.get().asFile }
|
|
|
|
android {
|
|
namespace = "com.ghostship.android"
|
|
compileSdk = 36
|
|
ndkVersion = "30.0.15729638"
|
|
|
|
defaultConfig {
|
|
applicationId = "com.ghostship.android"
|
|
minSdk = 24
|
|
targetSdk = 36
|
|
versionCode = 2
|
|
versionName = projectVersion
|
|
|
|
ndk {
|
|
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
|
}
|
|
|
|
externalNativeBuild {
|
|
cmake {
|
|
arguments += listOf(
|
|
"-DANDROID_STL=c++_shared",
|
|
"-DUSE_OPENGLES=ON",
|
|
"-DSDL_SHARED=ON",
|
|
"-DSDL_STATIC=OFF",
|
|
"-DHAVE_LD_VERSION_SCRIPT=OFF",
|
|
// Forced for every variant, debug included. The root
|
|
// CMakeLists only tunes the Release flags (-O1 with
|
|
// -fno-strict-aliasing), and the game's C code relies on
|
|
// that: at -O2 and above, strict aliasing miscompiles it
|
|
// into graphical glitches. Left alone, the Android plugin
|
|
// builds the release variant as RelWithDebInfo (-O2) and
|
|
// walks straight into them.
|
|
"-DCMAKE_BUILD_TYPE=Release"
|
|
)
|
|
targets += "Ghostship"
|
|
}
|
|
}
|
|
}
|
|
|
|
signingConfigs {
|
|
if (hasReleaseKeystore) {
|
|
create("release") {
|
|
val configuredStore = keystoreProperties.getProperty("storeFile")
|
|
if (configuredStore != null) {
|
|
// A relative path in key.properties reads against the file's
|
|
// own directory, which is what someone editing it would
|
|
// expect.
|
|
storeFile = rootProject.file(configuredStore)
|
|
storePassword = keystoreProperties.getProperty("storePassword")
|
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
|
} else {
|
|
storeFile = file(System.getenv("KEYSTORE_FILE"))
|
|
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
|
keyAlias = System.getenv("KEY_ALIAS")
|
|
keyPassword = System.getenv("KEY_PASSWORD")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
debug {
|
|
// No isJniDebuggable: the native side is built Release regardless
|
|
// (see the CMAKE_BUILD_TYPE argument above), so there would be
|
|
// nothing useful to attach to.
|
|
applicationIdSuffix = ".debug"
|
|
}
|
|
release {
|
|
// The APK is almost entirely native code and assets, so shrinking
|
|
// the tiny Kotlin layer buys nothing and only risks stripping the
|
|
// classes the JNI bridge looks up by name.
|
|
isMinifyEnabled = false
|
|
signingConfig = signingConfigs.getByName(if (hasReleaseKeystore) "release" else "debug")
|
|
ndk {
|
|
debugSymbolLevel = "SYMBOL_TABLE"
|
|
}
|
|
}
|
|
}
|
|
|
|
externalNativeBuild {
|
|
cmake {
|
|
path = File(repositoryRoot, "CMakeLists.txt")
|
|
// libultraship needs CMake >= 3.24; the NDK-bundled 3.22.1 is too old.
|
|
version = "3.30.3+"
|
|
}
|
|
}
|
|
|
|
sourceSets.named("main") {
|
|
assets.srcDir(stagedGameData)
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
lint {
|
|
abortOnError = false
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_17)
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation("androidx.core:core-ktx:1.13.1")
|
|
implementation("androidx.activity:activity-ktx:1.9.3")
|
|
}
|