diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3fdde6fb..e2a2aa42 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 + + @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 + + @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 + + @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 + + @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("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("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("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("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("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") diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 15b507c4..8c13687e 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -379,54 +379,71 @@ 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) - return + @Suppress("SENSELESS_COMPARISON") + if (BuildConfig.PREBUNDLED_GEODE != null) { + // attempt to load an externally provided Geode first + if (!loadExternalGeode()) { + copyGeodeResources() + System.loadLibrary("geode") } - - // 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() + } 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 + } catch (e: UnsatisfiedLinkError) { + if (!loadExternalGeode()) { + throw e + } } - copiedPath.mkdir() - - val copiedGeodePath = File(copiedPath.path, "Geode.so") + } + } - if (externalGeodePath.exists()) { - DownloadUtils.copyFile( - FileInputStream(externalGeodePath), - FileOutputStream(copiedGeodePath) - ) + @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) + if (copiedGeodePath.exists()) { + // android 17 fix + copiedGeodePath.setWritable(false) - println("Loading Geode from ${externalGeodePath.name}") - System.load(copiedGeodePath.path) - return - } + println("Loading Geode from ${externalGeodePath.name}") + System.load(copiedGeodePath.path) + return true } - - throw e } + + return false } private fun createView(): FrameLayout { @@ -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 diff --git a/app/src/main/java/com/geode/launcher/MainActivity.kt b/app/src/main/java/com/geode/launcher/MainActivity.kt index 0b084ab4..f0dfaa31 100644 --- a/app/src/main/java/com/geode/launcher/MainActivity.kt +++ b/app/src/main/java/com/geode/launcher/MainActivity.kt @@ -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() } diff --git a/app/src/main/java/com/geode/launcher/main/ErrorComponents.kt b/app/src/main/java/com/geode/launcher/main/ErrorComponents.kt index 2572fbac..19d899a3 100644 --- a/app/src/main/java/com/geode/launcher/main/ErrorComponents.kt +++ b/app/src/main/java/com/geode/launcher/main/ErrorComponents.kt @@ -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 { diff --git a/app/src/main/java/com/geode/launcher/main/LaunchComponents.kt b/app/src/main/java/com/geode/launcher/main/LaunchComponents.kt index c2b60784..97e90137 100644 --- a/app/src/main/java/com/geode/launcher/main/LaunchComponents.kt +++ b/app/src/main/java/com/geode/launcher/main/LaunchComponents.kt @@ -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) diff --git a/app/src/main/java/com/geode/launcher/main/LaunchViewModel.kt b/app/src/main/java/com/geode/launcher/main/LaunchViewModel.kt index 4b43531b..8afdfd78 100644 --- a/app/src/main/java/com/geode/launcher/main/LaunchViewModel.kt +++ b/app/src/main/java/com/geode/launcher/main/LaunchViewModel.kt @@ -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 diff --git a/app/src/main/java/com/geode/launcher/preferences/DeveloperSettingsActivity.kt b/app/src/main/java/com/geode/launcher/preferences/DeveloperSettingsActivity.kt index 65fb2cb7..b10130c9 100644 --- a/app/src/main/java/com/geode/launcher/preferences/DeveloperSettingsActivity.kt +++ b/app/src/main/java/com/geode/launcher/preferences/DeveloperSettingsActivity.kt @@ -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)) { diff --git a/app/src/main/java/com/geode/launcher/preferences/SettingsActivity.kt b/app/src/main/java/com/geode/launcher/preferences/SettingsActivity.kt index c9f59d4d..f1cde78b 100644 --- a/app/src/main/java/com/geode/launcher/preferences/SettingsActivity.kt +++ b/app/src/main/java/com/geode/launcher/preferences/SettingsActivity.kt @@ -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 ) diff --git a/app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt b/app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt index 63445cfb..3f2200cf 100644 --- a/app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt +++ b/app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt @@ -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()) { diff --git a/app/src/main/java/com/geode/launcher/updater/ReleaseViewModel.kt b/app/src/main/java/com/geode/launcher/updater/ReleaseViewModel.kt index 450d0b88..2a3b463b 100644 --- a/app/src/main/java/com/geode/launcher/updater/ReleaseViewModel.kt +++ b/app/src/main/java/com/geode/launcher/updater/ReleaseViewModel.kt @@ -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) { diff --git a/app/src/main/java/com/geode/launcher/utils/GamePackageUtils.kt b/app/src/main/java/com/geode/launcher/utils/GamePackageUtils.kt index a69929f3..eb6ca8ff 100644 --- a/app/src/main/java/com/geode/launcher/utils/GamePackageUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GamePackageUtils.kt @@ -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 = diff --git a/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt b/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt index 3671fff3..41fbcfa9 100644 --- a/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt @@ -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 } diff --git a/app/src/main/java/com/geode/launcher/utils/PreferenceUtils.kt b/app/src/main/java/com/geode/launcher/utils/PreferenceUtils.kt index 20f94b4c..b8b34f61 100644 --- a/app/src/main/java/com/geode/launcher/utils/PreferenceUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/PreferenceUtils.kt @@ -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" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ac3ea6e..7f9b4589 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,7 +35,7 @@ A Geometry Dash update is required. - The currently installed version of Geometry Dash is no longer supported. Please update Geometry Dash to continue receiving Geode updates. + The currently installed version of Geometry Dash is no longer supported. Please update Geometry Dash to version %1$s to continue receiving Geode updates. A new launcher update is available! Dismiss @@ -82,7 +82,7 @@ Launch paused due to a crash. Geode crashed. Geode cannot be used. - A Geometry Dash update is required. + Geometry Dash version %1$s is required. Restart Resume Retry