forked from geode-sdk/android-launcher
Bundle Geode with Play Store build (#88)
* add gradle task to download geode binaries * disable automatic update on play build * get download url from release api * fix incorrect priorities between external library and prebundled * bundle geode resources this also fixes the release api fetch as i never tested it until now, oops * add message for loading from files dir * raise minimum gd version with bundled geode * clear old resources prior to copying
This commit is contained in:
1 parent
9e86bbe355
commit
3194dff7ff
14 files changed
+455
-83
No files matched your search
@@ -1,3 +1,9 @@
|
||||
import groovy.json.JsonSlurper
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
extraWarnings.set(true)
|
||||
@@ -10,6 +16,232 @@ plugins {
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
}
|
||||
|
||||
val currentGeodeVersion = "5.10.1"
|
||||
|
||||
abstract class DownloadVersionMetadataTask : DefaultTask() {
|
||||
@get:Input
|
||||
abstract val geodeVersion: Property<String>
|
||||
|
||||
@get:OutputFile
|
||||
abstract val targetFile: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
fun execute() {
|
||||
val downloadGeodeVersion = geodeVersion.get()
|
||||
val destinationFile = targetFile.get().asFile
|
||||
|
||||
val releaseUrl = "https://api.geode-sdk.org/v1/loader/versions/$downloadGeodeVersion"
|
||||
URI.create(releaseUrl).toURL().openStream().use { response ->
|
||||
destinationFile.outputStream().use { destination ->
|
||||
response.copyTo(destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DownloadGeodeFileTask : DefaultTask() {
|
||||
@get:Input
|
||||
abstract val platform: Property<String>
|
||||
|
||||
@get:InputFile
|
||||
abstract val metadata: RegularFileProperty
|
||||
|
||||
private fun downloadAndVerifyFile(filename: String, url: String, hash: String?): File {
|
||||
logger.info("Downloading $filename from $url")
|
||||
|
||||
val tempFile = File(temporaryDir, filename)
|
||||
|
||||
URI.create(url).toURL().openStream().use { response ->
|
||||
tempFile.outputStream().use { output ->
|
||||
response.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
if (hash != null) {
|
||||
val downloadedHash = MessageDigest.getInstance("SHA-256")
|
||||
.digest(tempFile.readBytes())
|
||||
.toHexString()
|
||||
|
||||
if (downloadedHash != hash) {
|
||||
throw GradleException("Hash check failed for file $filename, expected $hash but found $downloadedHash.")
|
||||
}
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
fun downloadZip(fallbackUrl: String?): File {
|
||||
val downloadPlatform = platform.get()
|
||||
|
||||
val metadataFile = metadata.get().asFile
|
||||
val json = JsonSlurper().parse(metadataFile) as Map<*, *>
|
||||
|
||||
val payload = json["payload"] as? Map<*, *>
|
||||
|
||||
val downloads = payload?.get("downloads") as? Map<*, *>
|
||||
val platformInfo = downloads?.get(downloadPlatform) as? Map<*, *>
|
||||
|
||||
val downloadUrl = platformInfo?.get("url") as? String ?: fallbackUrl
|
||||
if (downloadUrl == null) {
|
||||
throw GradleException("Failed to fetch download url for platform $platform.")
|
||||
}
|
||||
|
||||
val expectedHash = platformInfo?.get("hash") as? String
|
||||
|
||||
return downloadAndVerifyFile("$downloadPlatform.zip", downloadUrl, expectedHash)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DownloadAndUnzipGeodeTask : DownloadGeodeFileTask() {
|
||||
@get:Input
|
||||
abstract val geodeVersion: Property<String>
|
||||
|
||||
@get:OutputFile
|
||||
abstract val targetFile: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
fun execute() {
|
||||
val downloadGeodeVersion = geodeVersion.get()
|
||||
val downloadPlatform = platform.get()
|
||||
val destinationFile = targetFile.get().asFile
|
||||
|
||||
val tempOutput = this.downloadZip("https://github.com/geode-sdk/geode/releases/download/v$downloadGeodeVersion/geode-v$downloadGeodeVersion-$downloadPlatform.zip")
|
||||
|
||||
val expectedFile = "Geode.$downloadPlatform.so"
|
||||
|
||||
ZipInputStream(tempOutput.inputStream()).use { zip ->
|
||||
var entry = zip.nextEntry
|
||||
while (entry != null) {
|
||||
if (entry.name == expectedFile) {
|
||||
destinationFile.outputStream().use {
|
||||
zip.copyTo(it)
|
||||
}
|
||||
return@execute
|
||||
}
|
||||
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
|
||||
throw GradleException("Could not find Geode release.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DownloadAndUnzipGeodeResourcesTask : DownloadGeodeFileTask() {
|
||||
init {
|
||||
platform.set("resources")
|
||||
}
|
||||
|
||||
@get:Input
|
||||
abstract val geodeVersion: Property<String>
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val outputDir: DirectoryProperty
|
||||
|
||||
@TaskAction
|
||||
fun execute() {
|
||||
val downloadGeodeVersion = geodeVersion.get()
|
||||
|
||||
val tempOutput = this.downloadZip("https://github.com/geode-sdk/geode/releases/download/v$downloadGeodeVersion/resources.zip")
|
||||
|
||||
val baseOutput = outputDir.get().asFile
|
||||
if (baseOutput.exists()) baseOutput.deleteRecursively()
|
||||
|
||||
val output = File(baseOutput, "geode.loader")
|
||||
output.mkdirs()
|
||||
|
||||
val canonicalOutput = output.canonicalPath
|
||||
|
||||
ZipInputStream(tempOutput.inputStream()).use { zipStream ->
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
val destination = File(output, entry.name)
|
||||
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
|
||||
throw GradleException("attempted copy to outside of output directory: ${entry.name}")
|
||||
}
|
||||
|
||||
if (destination.isDirectory) {
|
||||
destination.mkdirs()
|
||||
} else {
|
||||
destination.parentFile?.mkdirs()
|
||||
|
||||
destination.outputStream().use { destinationStream ->
|
||||
zipStream.copyTo(destinationStream)
|
||||
}
|
||||
}
|
||||
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fetchGeodeMetadataTask = tasks.register<DownloadVersionMetadataTask>("fetchGeodeMetadata") {
|
||||
description = "Fetches and saves a Geode version's metadata"
|
||||
inputs.property("geodeVersion", currentGeodeVersion)
|
||||
|
||||
geodeVersion = currentGeodeVersion
|
||||
targetFile = temporaryDir.resolve("metadata.json")
|
||||
}
|
||||
|
||||
val downloadGeode32Task = tasks.register<DownloadAndUnzipGeodeTask>("downloadGeode32Binary") {
|
||||
description = "Downloads the 32bit Geode release archive"
|
||||
inputs.property("geodeVersion", currentGeodeVersion)
|
||||
|
||||
geodeVersion = currentGeodeVersion
|
||||
platform = "android32"
|
||||
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
|
||||
targetFile = temporaryDir.resolve("libgeode.so")
|
||||
}
|
||||
|
||||
val downloadGeode64Task = tasks.register<DownloadAndUnzipGeodeTask>("downloadGeode64Binary") {
|
||||
description = "Downloads the 64bit Geode release archive"
|
||||
inputs.property("geodeVersion", currentGeodeVersion)
|
||||
|
||||
geodeVersion = currentGeodeVersion
|
||||
platform = "android64"
|
||||
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
|
||||
targetFile = temporaryDir.resolve("libgeode.so")
|
||||
}
|
||||
|
||||
val downloadGeodeResourcesTask = tasks.register<DownloadAndUnzipGeodeResourcesTask>("downloadGeodeResources") {
|
||||
description = "Downloads Geode resources"
|
||||
inputs.property("geodeVersion", currentGeodeVersion)
|
||||
|
||||
geodeVersion = currentGeodeVersion
|
||||
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
|
||||
outputDir = layout.buildDirectory.dir("geode-resources")
|
||||
}
|
||||
|
||||
abstract class MergeGeodeFilesTask : DefaultTask() {
|
||||
@get:InputFile
|
||||
abstract val binary32: RegularFileProperty
|
||||
|
||||
@get:InputFile
|
||||
abstract val binary64: RegularFileProperty
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val outputDir: DirectoryProperty
|
||||
|
||||
@TaskAction
|
||||
fun execute() {
|
||||
val out = outputDir.get().asFile
|
||||
out.deleteRecursively()
|
||||
out.mkdirs()
|
||||
|
||||
binary32.get().asFile.copyTo(outputDir.file("armeabi-v7a/libgeode.so").get().asFile)
|
||||
binary64.get().asFile.copyTo(outputDir.file("arm64-v8a/libgeode.so").get().asFile)
|
||||
}
|
||||
}
|
||||
|
||||
val fullDownloadTask = tasks.register<MergeGeodeFilesTask>("downloadGeodeFull") {
|
||||
description = "Downloads both Geode releases"
|
||||
|
||||
binary32 = downloadGeode32Task.flatMap { it.targetFile }
|
||||
binary64 = downloadGeode64Task.flatMap { it.targetFile }
|
||||
outputDir = layout.buildDirectory.dir("geode-prebuilt")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 37
|
||||
|
||||
@@ -54,12 +286,14 @@ android {
|
||||
dimension = "distribution"
|
||||
isDefault = true
|
||||
buildConfigField("boolean", "GOOGLE_PLAY_BUILD", "false")
|
||||
buildConfigField("String", "PREBUNDLED_GEODE", "null")
|
||||
}
|
||||
|
||||
create("googlePlay") {
|
||||
dimension = "distribution"
|
||||
applicationId = "com.geode.launcher.play"
|
||||
buildConfigField("boolean", "GOOGLE_PLAY_BUILD", "true")
|
||||
buildConfigField("String", "PREBUNDLED_GEODE", "\"$currentGeodeVersion\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +334,19 @@ android {
|
||||
ndkVersion = "29.0.14206865"
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants(selector().withFlavor("distribution" to "googlePlay")) { variant ->
|
||||
variant.sources.jniLibs?.addGeneratedSourceDirectory(
|
||||
fullDownloadTask,
|
||||
MergeGeodeFilesTask::outputDir
|
||||
)
|
||||
variant.sources.assets?.addGeneratedSourceDirectory(
|
||||
downloadGeodeResourcesTask,
|
||||
DownloadAndUnzipGeodeResourcesTask::outputDir
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation (platform("androidx.compose:compose-bom:2026.08.00"))
|
||||
implementation ("androidx.core:core-ktx:1.19.0")
|
||||
|
||||
@@ -379,56 +379,73 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
return "$sourceDir!/lib/$architecture/"
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun loadGeodeLibrary() {
|
||||
// Load Geode if exists
|
||||
// bundling the object with the application allows for nicer backtraces
|
||||
try {
|
||||
// put libgeode.so in jniLibs/armeabi-v7a to get this
|
||||
System.loadLibrary("geode")
|
||||
return
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
// but users may prefer it stored with data
|
||||
val geodeFilename = LaunchUtils.geodeFilename
|
||||
val geodePath = File(filesDir.path, "launcher/$geodeFilename")
|
||||
if (geodePath.exists()) {
|
||||
System.load(geodePath.path)
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (BuildConfig.PREBUNDLED_GEODE != null) {
|
||||
// attempt to load an externally provided Geode first
|
||||
if (!loadExternalGeode()) {
|
||||
copyGeodeResources()
|
||||
System.loadLibrary("geode")
|
||||
}
|
||||
} else {
|
||||
// Load Geode if exists
|
||||
// bundling the object with the application allows for nicer backtraces
|
||||
try {
|
||||
// put libgeode.so in jniLibs/armeabi-v7a to get this
|
||||
System.loadLibrary("geode")
|
||||
return
|
||||
}
|
||||
|
||||
// you know zmx i have 0 clue what this does so im
|
||||
// just gonna like copy the binary from external
|
||||
// also i get 20 million permission denied errors
|
||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
|
||||
|
||||
val copiedPath = File(filesDir.path, "copied")
|
||||
if (copiedPath.exists()) {
|
||||
copiedPath.deleteRecursively()
|
||||
}
|
||||
copiedPath.mkdir()
|
||||
|
||||
val copiedGeodePath = File(copiedPath.path, "Geode.so")
|
||||
|
||||
if (externalGeodePath.exists()) {
|
||||
DownloadUtils.copyFile(
|
||||
FileInputStream(externalGeodePath),
|
||||
FileOutputStream(copiedGeodePath)
|
||||
)
|
||||
|
||||
if (copiedGeodePath.exists()) {
|
||||
// android 17 fix
|
||||
copiedGeodePath.setWritable(false)
|
||||
|
||||
println("Loading Geode from ${externalGeodePath.name}")
|
||||
System.load(copiedGeodePath.path)
|
||||
return
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
if (!loadExternalGeode()) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun loadExternalGeode(): Boolean {
|
||||
// but users may prefer it stored with data
|
||||
val geodeFilename = LaunchUtils.geodeFilename
|
||||
val geodePath = File(filesDir.path, "launcher/$geodeFilename")
|
||||
if (geodePath.exists()) {
|
||||
println("Loading Geode from ${geodePath.path}")
|
||||
System.load(geodePath.path)
|
||||
return true
|
||||
}
|
||||
|
||||
// you know zmx i have 0 clue what this does so im
|
||||
// just gonna like copy the binary from external
|
||||
// also i get 20 million permission denied errors
|
||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)
|
||||
?: return false
|
||||
|
||||
val copiedPath = File(filesDir.path, "copied")
|
||||
if (copiedPath.exists()) {
|
||||
copiedPath.deleteRecursively()
|
||||
}
|
||||
copiedPath.mkdir()
|
||||
|
||||
val copiedGeodePath = File(copiedPath.path, "Geode.so")
|
||||
|
||||
if (externalGeodePath.exists()) {
|
||||
DownloadUtils.copyFile(
|
||||
FileInputStream(externalGeodePath),
|
||||
FileOutputStream(copiedGeodePath)
|
||||
)
|
||||
|
||||
if (copiedGeodePath.exists()) {
|
||||
// android 17 fix
|
||||
copiedGeodePath.setWritable(false)
|
||||
|
||||
println("Loading Geode from ${externalGeodePath.name}")
|
||||
System.load(copiedGeodePath.path)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun createView(): FrameLayout {
|
||||
val frameLayoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
@@ -666,6 +683,56 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyGeodeResources() {
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (BuildConfig.PREBUNDLED_GEODE == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val resourcesDirectory = LaunchUtils.getGeodeResourcesDirectory(this)
|
||||
|
||||
val pathBase = "geode.loader"
|
||||
val versionFilename = "version"
|
||||
|
||||
val bundledVersion = try {
|
||||
assets.open("$pathBase/$versionFilename").bufferedReader().use {
|
||||
it.readText()
|
||||
}.trimEnd()
|
||||
} catch (_: IOException) {
|
||||
return
|
||||
}
|
||||
|
||||
val versionFile = File(resourcesDirectory, versionFilename)
|
||||
val resourcesVersion = if (versionFile.exists()) {
|
||||
versionFile.readText().trimEnd()
|
||||
} else null
|
||||
|
||||
if (bundledVersion == resourcesVersion) {
|
||||
println("copyGeodeResources: found $resourcesVersion, expected $bundledVersion. No work needs to be done!")
|
||||
return
|
||||
}
|
||||
|
||||
if (resourcesDirectory.exists()) {
|
||||
resourcesDirectory.deleteRecursively()
|
||||
}
|
||||
resourcesDirectory.mkdirs()
|
||||
|
||||
println("copyGeodeResources: found $resourcesVersion, expected $bundledVersion. Copying assets")
|
||||
|
||||
val fileListing = try {
|
||||
assets.list(pathBase)
|
||||
} catch (_: IOException) {
|
||||
return
|
||||
}
|
||||
|
||||
fileListing?.forEach { fileName ->
|
||||
val resourceOutput = File(resourcesDirectory, fileName)
|
||||
|
||||
val resource = assets.open("$pathBase/$fileName")
|
||||
DownloadUtils.copyFile(resource, resourceOutput.outputStream())
|
||||
}
|
||||
}
|
||||
|
||||
class EGLConfigChooser : GLSurfaceView.EGLConfigChooser {
|
||||
// this comes from EGL14, but is unavailable on EGL10
|
||||
// also EGL14 is incompatible with EGL10. so whatever
|
||||
|
||||
@@ -111,6 +111,16 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
clearDownloadedApks(this)
|
||||
|
||||
// remove old Geode for the like 4 people that downloaded the Play Store build
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (BuildConfig.PREBUNDLED_GEODE != null) {
|
||||
val preferences = PreferenceUtils.get(this)
|
||||
if (!preferences.getBoolean(PreferenceUtils.Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED)) {
|
||||
LaunchUtils.getInstalledGeodePath(this)?.delete()
|
||||
preferences.setBoolean(PreferenceUtils.Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED, true)
|
||||
}
|
||||
}
|
||||
|
||||
val returnMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE)
|
||||
val returnExtendedMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE)
|
||||
|
||||
@@ -207,7 +217,7 @@ fun mapCancelReasonToInfo(cancelReason: LaunchViewModel.LaunchCancelReason): Lau
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.GAME_OUTDATED -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_error),
|
||||
details = stringResource(R.string.launcher_cancelled_outdated)
|
||||
details = stringResource(R.string.launcher_cancelled_outdated, GamePackageUtils.getSupportedUnifiedVersion())
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.AUTOMATIC,
|
||||
LaunchViewModel.LaunchCancelReason.MANUAL-> LaunchStatusInfo(
|
||||
@@ -828,10 +838,11 @@ fun AltMainScreen(
|
||||
)
|
||||
|
||||
// only show launcher update in a case where a user won't see it ingame
|
||||
if (launchUIState is LaunchViewModel.LaunchUIState.Cancelled) {
|
||||
val currentUIState = launchUIState
|
||||
if (currentUIState is LaunchViewModel.LaunchUIState.Cancelled) {
|
||||
val gameVersion = remember { GamePackageUtils.getGameVersionCodeOrNull(context.packageManager) }
|
||||
|
||||
if (gameVersion != null && gameVersion < Constants.SUPPORTED_VERSION_CODE_MIN_WARNING) {
|
||||
if (gameVersion != null && gameVersion < Constants.SUPPORTED_VERSION_CODE_MIN_WARNING && currentUIState.reason != LaunchViewModel.LaunchCancelReason.GAME_OUTDATED) {
|
||||
UnsupportedVersionWarning()
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,11 @@ fun onShowLogs(context: Context) {
|
||||
fun ErrorInfoDescription(description: String, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val currentRelease = remember {
|
||||
PreferenceUtils.get(context).getString(PreferenceUtils.Key.CURRENT_VERSION_TAG)
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (BuildConfig.PREBUNDLED_GEODE != null)
|
||||
"${BuildConfig.PREBUNDLED_GEODE}*"
|
||||
else
|
||||
PreferenceUtils.get(context).getString(PreferenceUtils.Key.CURRENT_VERSION_TAG)
|
||||
}
|
||||
|
||||
val gameVersion = remember {
|
||||
|
||||
@@ -187,6 +187,8 @@ fun LongPressButton(onClick: () -> Unit, onLongPress: () -> Unit, modifier: Modi
|
||||
|
||||
@Composable
|
||||
fun UnsupportedVersionWarning(modifier: Modifier = Modifier) {
|
||||
val gameVersion = GamePackageUtils.getSupportedUnifiedVersion()
|
||||
|
||||
InlineDialog(
|
||||
headline = {
|
||||
Icon(
|
||||
@@ -202,7 +204,7 @@ fun UnsupportedVersionWarning(modifier: Modifier = Modifier) {
|
||||
},
|
||||
body = {
|
||||
Text(
|
||||
stringResource(R.string.launcher_game_update_required),
|
||||
stringResource(R.string.launcher_game_update_required, gameVersion),
|
||||
)
|
||||
},
|
||||
modifier = modifier.padding(8.dp)
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.geode.launcher.BuildConfig
|
||||
import com.geode.launcher.updater.ReleaseManager
|
||||
import com.geode.launcher.utils.Constants
|
||||
import com.geode.launcher.utils.GamePackageUtils
|
||||
@@ -182,7 +183,9 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
return
|
||||
}
|
||||
|
||||
if (GamePackageUtils.getGameVersionCode(packageManager) < Constants.SUPPORTED_VERSION_CODE_MIN) {
|
||||
// play store build only comes with the one build of geode
|
||||
val minVersionCode = if (BuildConfig.GOOGLE_PLAY_BUILD) Constants.SUPPORTED_VERSION_CODE else Constants.SUPPORTED_VERSION_CODE_MIN
|
||||
if (GamePackageUtils.getGameVersionCode(packageManager) < minVersionCode) {
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
||||
return
|
||||
}
|
||||
@@ -248,25 +251,31 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
initReadyTimer()
|
||||
}
|
||||
|
||||
val releaseManager = ReleaseManager.get(application)
|
||||
if (!BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
val releaseManager = ReleaseManager.get(application)
|
||||
|
||||
val hasGeode = LaunchUtils.isGeodeInstalled(application)
|
||||
val shouldUpdate = PreferenceUtils.get(application).getBoolean(PreferenceUtils.Key.UPDATE_AUTOMATICALLY)
|
||||
val hasGeode = LaunchUtils.isGeodeInstalled(application)
|
||||
val shouldUpdate = PreferenceUtils.get(application).getBoolean(PreferenceUtils.Key.UPDATE_AUTOMATICALLY)
|
||||
|
||||
if ((shouldUpdate && !hasCheckedForUpdates) || !hasGeode) {
|
||||
releaseManager.checkForUpdates(false)
|
||||
} else {
|
||||
releaseManager.afterUseCachedUpdate()
|
||||
}
|
||||
if ((shouldUpdate && !hasCheckedForUpdates) || !hasGeode) {
|
||||
releaseManager.checkForUpdates(false)
|
||||
} else {
|
||||
releaseManager.afterUseCachedUpdate()
|
||||
}
|
||||
|
||||
if (isRestart) {
|
||||
releaseManager.dismissedLauncherUpdate = true
|
||||
if (isRestart) {
|
||||
releaseManager.dismissedLauncherUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
preReadyCheck()
|
||||
}
|
||||
|
||||
fun retryUpdate() {
|
||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
return
|
||||
}
|
||||
|
||||
ReleaseManager.get(application).checkForUpdates(true)
|
||||
if (ReleaseManager.get(application).isInUpdate) {
|
||||
restoreUpdateState = _uiState.value
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geode.launcher.BuildConfig
|
||||
import com.geode.launcher.R
|
||||
import com.geode.launcher.preferences.components.OptionsButton
|
||||
import com.geode.launcher.preferences.components.OptionsGroup
|
||||
@@ -204,28 +205,30 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
)
|
||||
}
|
||||
|
||||
OptionsGroup(title = stringResource(R.string.preference_category_updater)) {
|
||||
val context = LocalContext.current
|
||||
if (!BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
OptionsGroup(title = stringResource(R.string.preference_category_updater)) {
|
||||
val context = LocalContext.current
|
||||
|
||||
SettingsSelectCard(
|
||||
title = stringResource(R.string.preference_release_channel_tag_name),
|
||||
dialogTitle = stringResource(R.string.preference_release_channel_select),
|
||||
preferenceKey = PreferenceUtils.Key.RELEASE_CHANNEL_TAG,
|
||||
options = linkedMapOf(
|
||||
0 to stringResource(R.string.preference_release_channel_stable),
|
||||
1 to stringResource(R.string.preference_release_channel_beta),
|
||||
2 to stringResource(R.string.preference_release_channel_nightly),
|
||||
),
|
||||
extraSelectBehavior = {
|
||||
PreferenceUtils.get(context)
|
||||
.setLong(PreferenceUtils.Key.LAST_UPDATE_CHECK_TIME, 0L)
|
||||
}
|
||||
)
|
||||
SettingsSelectCard(
|
||||
title = stringResource(R.string.preference_release_channel_tag_name),
|
||||
dialogTitle = stringResource(R.string.preference_release_channel_select),
|
||||
preferenceKey = PreferenceUtils.Key.RELEASE_CHANNEL_TAG,
|
||||
options = linkedMapOf(
|
||||
0 to stringResource(R.string.preference_release_channel_stable),
|
||||
1 to stringResource(R.string.preference_release_channel_beta),
|
||||
2 to stringResource(R.string.preference_release_channel_nightly),
|
||||
),
|
||||
extraSelectBehavior = {
|
||||
PreferenceUtils.get(context)
|
||||
.setLong(PreferenceUtils.Key.LAST_UPDATE_CHECK_TIME, 0L)
|
||||
}
|
||||
)
|
||||
|
||||
SettingsCard(
|
||||
title = stringResource(R.string.preference_disable_update_cache),
|
||||
preferenceKey = PreferenceUtils.Key.DISABLE_UPDATE_CACHE
|
||||
)
|
||||
SettingsCard(
|
||||
title = stringResource(R.string.preference_disable_update_cache),
|
||||
preferenceKey = PreferenceUtils.Key.DISABLE_UPDATE_CACHE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OptionsGroup(title = stringResource(R.string.preference_category_testing)) {
|
||||
|
||||
@@ -70,7 +70,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geode.launcher.BuildConfig
|
||||
import com.geode.launcher.MainActivity
|
||||
import com.geode.launcher.R
|
||||
import com.geode.launcher.UserDirectoryProvider
|
||||
import com.geode.launcher.preferences.components.BackupButton
|
||||
@@ -510,6 +509,10 @@ fun GameplaySettingsGroup() {
|
||||
|
||||
@Composable
|
||||
fun UpdateSettingsGroup(releaseViewModel: ReleaseViewModel, snackbarHostState: SnackbarHostState, onUpdate: () -> Unit) {
|
||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
return
|
||||
}
|
||||
|
||||
val updateStatus by releaseViewModel.uiState.collectAsState()
|
||||
|
||||
OptionsGroup(stringResource(R.string.preference_category_updater)) {
|
||||
@@ -661,11 +664,14 @@ fun AboutSettingsGroup(developerModeEnabled: Boolean) {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
OptionsLabel(
|
||||
stringResource(R.string.preference_loader_version_name),
|
||||
stringResource(
|
||||
R.string.preference_loader_version_description,
|
||||
currentRelease ?: "unknown"
|
||||
if (BuildConfig.PREBUNDLED_GEODE != null)
|
||||
"${BuildConfig.PREBUNDLED_GEODE}*"
|
||||
else (currentRelease ?: "unknown")
|
||||
),
|
||||
displayInline = true
|
||||
)
|
||||
|
||||
@@ -409,6 +409,10 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
|
||||
fun shouldUseCache(): Boolean {
|
||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
return true
|
||||
}
|
||||
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
if (sharedPreferences.getBoolean(PreferenceUtils.Key.DISABLE_UPDATE_CACHE)) {
|
||||
return false
|
||||
@@ -427,6 +431,11 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
|
||||
private suspend fun checkForNewRelease(isManual: Boolean = false) {
|
||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
_uiState.value = ReleaseManagerState.Finished()
|
||||
return
|
||||
}
|
||||
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
|
||||
if (!isManual && shouldUseCache()) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.geode.launcher.BuildConfig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -89,6 +90,10 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
||||
}
|
||||
|
||||
fun runReleaseCheck(isManual: Boolean = false) {
|
||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
||||
return
|
||||
}
|
||||
|
||||
hasPerformedCheck = true
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
@@ -63,6 +63,10 @@ object GamePackageUtils {
|
||||
return gameVersionMap[versionCode] ?: getGameVersionString(packageManager)
|
||||
}
|
||||
|
||||
fun getSupportedUnifiedVersion(): String {
|
||||
return gameVersionMap[Constants.SUPPORTED_VERSION_CODE] ?: Constants.SUPPORTED_VERSION_STRING
|
||||
}
|
||||
|
||||
fun detectAbiMismatch(context: Context, packageInfo: PackageInfo, loadException: Error): Boolean {
|
||||
val abi = LaunchUtils.applicationArchitecture
|
||||
val isExtracted =
|
||||
|
||||
@@ -42,6 +42,9 @@ object LaunchUtils {
|
||||
}
|
||||
|
||||
fun isGeodeInstalled(context: Context): Boolean {
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (BuildConfig.PREBUNDLED_GEODE != null) return true
|
||||
|
||||
return getInstalledGeodePath(context) != null
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
LAST_UPDATE_CHECK_TIME,
|
||||
LAST_LAUNCHER_UPDATE,
|
||||
DISABLE_UPDATE_CACHE,
|
||||
PREBUNDLED_GEODE_MIGRATION_PERFORMED,
|
||||
}
|
||||
|
||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||
@@ -199,6 +200,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
Key.LAST_UPDATE_CHECK_TIME -> "PreferenceLastUpdateCheckTime"
|
||||
Key.LAST_LAUNCHER_UPDATE -> "PreferenceLastLauncherUpdate"
|
||||
Key.DISABLE_UPDATE_CACHE -> "PreferenceDisableUpdateCache"
|
||||
Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED -> "PreferencePrebundledGeodeMigrationPerformed"
|
||||
// Key.RELEASE_CHANNEL -> "PreferenceReleaseChannel"
|
||||
// Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
|
||||
// Key.FORCE_HRR -> "PreferenceForceHighRefreshRate"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</string>
|
||||
|
||||
<string name="launcher_game_update_required_short">A Geometry Dash update is required.</string>
|
||||
<string name="launcher_game_update_required">The currently installed version of Geometry Dash is no longer supported. Please update Geometry Dash to continue receiving Geode updates.</string>
|
||||
<string name="launcher_game_update_required">The currently installed version of Geometry Dash is no longer supported. Please update Geometry Dash to version %1$s to continue receiving Geode updates.</string>
|
||||
|
||||
<string name="launcher_update_available">A new launcher update is available!</string>
|
||||
<string name="launcher_update_dismiss">Dismiss</string>
|
||||
@@ -82,7 +82,7 @@
|
||||
<string name="launcher_cancelled_crash">Launch paused due to a crash.</string>
|
||||
<string name="launcher_cancelled_crash_instant">Geode crashed.</string>
|
||||
<string name="launcher_cancelled_error">Geode cannot be used.</string>
|
||||
<string name="launcher_cancelled_outdated">A Geometry Dash update is required.</string>
|
||||
<string name="launcher_cancelled_outdated">Geometry Dash version %1$s is required.</string>
|
||||
<string name="launcher_cancelled_restart">Restart</string>
|
||||
<string name="launcher_cancelled_resume">Resume</string>
|
||||
<string name="launcher_cancelled_retry">Retry</string>
|
||||
|
||||
Reference in new issue
Block a user