Compare commits

...
Author SHA1 Message Date
Chloe 8dd122631c run shared library initialization on separate context 2026-03-13 23:37:50 -07:00
3 changed files with 152 additions and 82 deletions

No files matched your search

@@ -24,8 +24,10 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import com.customRobTop.BaseRobTopActivity
import com.customRobTop.JniToCpp
import com.geode.launcher.main.FullScreenLoadingIndicator
import com.geode.launcher.main.LaunchNotification
import com.geode.launcher.main.determineDisplayedCards
import com.geode.launcher.utils.Constants
@@ -35,13 +37,14 @@ import com.geode.launcher.utils.GamePackageUtils
import com.geode.launcher.utils.GeodeUtils
import com.geode.launcher.utils.LaunchUtils
import com.geode.launcher.utils.PreferenceUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.cocos2dx.lib.Cocos2dxGLSurfaceView
import org.cocos2dx.lib.Cocos2dxHelper
import org.cocos2dx.lib.Cocos2dxRenderer
import org.fmod.FMOD
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
@@ -91,9 +94,46 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
return
}
try {
val rootLayout = setupContext()
val loadingView = ComposeView(this).apply {
setContent {
FullScreenLoadingIndicator()
}
}
if (rootLayout != null) {
rootLayout.addView(loadingView)
} else {
setContentView(loadingView)
}
lifecycleScope.launch {
val error = preloadGame(rootLayout)
if (error != null) {
returnToMain(error)
}
rootLayout?.removeView(loadingView)
}
}
suspend fun preloadGame(rootLayout: FrameLayout?): LaunchUtils.FullLauncherError? = try {
createVersionFile()
tryLoadGame()
val loadFailed = tryLoadGame()
val baseView = createView()
if (rootLayout != null) {
rootLayout.addView(baseView)
} else {
setContentView(baseView)
}
mGLSurfaceView?.manualBackEvents = true
setupNotificationView(baseView, loadFailed)
null
} catch (e: UnsatisfiedLinkError) {
Log.e("GeodeLauncher", "Library linkage failure", e)
@@ -110,78 +150,32 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
else -> LaunchUtils.LauncherError.LINKER_FAILED
}
returnToMain(errorMessage, e.message, e.stackTraceToString())
return
LaunchUtils.FullLauncherError(
errorMessage,
e.message ?: "Unknown exception",
e.stackTraceToString()
)
} catch (e: Exception) {
Log.e("GeodeLauncher", "Uncaught error during game load", e)
returnToMain(
LaunchUtils.FullLauncherError(
LaunchUtils.LauncherError.GENERIC,
e.message ?: "Unknown Exception",
e.stackTraceToString()
)
return
}
private fun setupContext(): FrameLayout? {
Cocos2dxHelper.init(this, this)
GeodeUtils.setContext(this)
GeodeUtils.setCapabilityListener(this)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
mGLSurfaceView?.sendKeyBack()
}
})
mGLSurfaceView?.manualBackEvents = true
}
private fun createVersionFile() {
val versionPath = File(filesDir, "game_version.txt")
val gameVersion = GamePackageUtils.getGameVersionCode(packageManager)
versionPath.writeText("$gameVersion")
}
private fun returnToMain(
error: LaunchUtils.LauncherError? = null,
returnMessage: String? = null,
returnExtendedMessage: String? = null
) {
val launchIntent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
if (error != null && !returnMessage.isNullOrEmpty()) {
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR, error)
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE, returnMessage)
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE, returnExtendedMessage)
}
}
startActivity(launchIntent)
}
private fun tryLoadGame() {
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
setupRedirection(gdPackageInfo)
Cocos2dxHelper.init(this, this)
GeodeUtils.setContext(this)
GeodeUtils.setCapabilityListener(this)
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
// this fix requires geode v3, which is 2.206+
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
LauncherFix.enableExceptionsRenaming()
}
LauncherFix.performPatches()
loadInternalMods()
val baseView = createView()
if (!GeodeUtils.handleSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
val frameLayoutParams = ViewGroup.LayoutParams(
@@ -204,15 +198,61 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
WindowInsets.CONSUMED
}
frameLayout.addView(baseView)
setContentView(frameLayout)
} else {
setContentView(baseView)
return frameLayout
}
return null
}
private suspend fun createVersionFile() = runCatching {
val gameVersion = GamePackageUtils.getGameVersionCode(packageManager)
val versionPath = File(filesDir, "game_version.txt")
withContext(Dispatchers.IO) {
if (versionPath.exists()) {
versionPath.delete()
}
versionPath.writeText("$gameVersion")
}
}
private fun returnToMain(message: LaunchUtils.FullLauncherError? = null, ) {
val launchIntent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
if (message != null) {
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR, message.error)
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE, message.returnMessage)
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE, message.extendedMessage)
}
}
startActivity(launchIntent)
}
private suspend fun tryLoadGame(): Boolean {
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
setupRedirection(gdPackageInfo)
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
// this fix requires geode v3, which is 2.206+
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
LauncherFix.enableExceptionsRenaming()
}
LauncherFix.performPatches()
loadInternalMods()
setupPostLibraryLoad(gdPackageInfo)
val geodeFailed = try {
return try {
loadGeodeLibrary()
false
} catch (e: UnsatisfiedLinkError) {
@@ -222,8 +262,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
handleGeodeException(e)
true
}
setupNotificationView(baseView, geodeFailed)
}
private fun setupNotificationView(baseView: FrameLayout, geodeFailed: Boolean) {
@@ -283,7 +321,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
private suspend fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
val nativeDir = getNativeLibraryDirectory(packageInfo.applicationInfo!!)
val libraryPath = if (nativeDir.endsWith('/')) "${nativeDir}lib$libraryName.so" else "$nativeDir/lib$libraryName.so"
@@ -302,7 +340,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun loadLibraryCopy(libraryName: String, libraryPath: String) {
private suspend fun loadLibraryCopy(libraryName: String, libraryPath: String) {
if (libraryPath.contains("!/")) {
// library in apk can't be loaded directly
loadLibraryFromAssetsCopy(libraryName)
@@ -312,11 +350,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val library = File(libraryPath)
val libraryCopy = File(cacheDir, "lib$libraryName.so")
libraryCopy.outputStream().use { libraryOutput ->
library.inputStream().use { inputStream ->
DownloadUtils.copyFile(inputStream, libraryOutput)
}
}
DownloadUtils.copyFile(library, libraryCopy)
System.load(libraryCopy.path)
@@ -324,7 +358,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun loadLibraryFromAssetsCopy(libraryName: String) {
private suspend fun loadLibraryFromAssetsCopy(libraryName: String) {
// loads a library loaded in assets
// this copies the library to a non-compressed directory
@@ -339,11 +373,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
// there doesn't seem to be a way to load a library from a file descriptor
val libraryCopy = File(cacheDir, "lib$libraryName.so")
withContext(Dispatchers.IO) {
libraryCopy.outputStream().use { libraryOutput ->
libraryFd.createInputStream().use { inputStream ->
DownloadUtils.copyFile(inputStream, libraryOutput)
}
}
}
System.load(libraryCopy.path)
@@ -376,7 +412,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun loadGeodeLibrary() {
private suspend fun loadGeodeLibrary() {
// Load Geode if exists
// bundling the object with the application allows for nicer backtraces
try {
@@ -397,22 +433,23 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
// also i get 20 million permission denied errors
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
val copiedPath = File(filesDir.path, "copied")
val copiedPath = File(filesDir, "copied")
if (copiedPath.exists()) {
copiedPath.deleteRecursively()
}
copiedPath.mkdir()
val copiedGeodePath = File(copiedPath.path, "Geode.so")
val copiedGeodePath = File(copiedPath, "Geode.so")
if (externalGeodePath.exists()) {
DownloadUtils.copyFile(
FileInputStream(externalGeodePath),
FileOutputStream(copiedGeodePath)
externalGeodePath,
copiedGeodePath
)
if (copiedGeodePath.exists()) {
println("Loading Geode from ${externalGeodePath.name}")
System.load(copiedGeodePath.path)
return
}
@@ -10,13 +10,16 @@ import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -120,9 +123,10 @@ fun LaunchNotification(cards: List<LaunchNotificationType>) {
val theme = Theme.fromInt(themeOption)
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
CompositionLocalProvider(LocalTheme provides theme) {
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) {
// surface is not in use, so this is unfortunately not provided
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Column(
@@ -280,3 +284,26 @@ fun ForwardCompatibilityContent(modifier: Modifier = Modifier) {
modifier = modifier.width(IntrinsicSize.Max)
)
}
@Composable
fun FullScreenLoadingIndicator() {
val theme = Theme.DARK
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
CompositionLocalProvider(LocalTheme provides theme) {
GeodeLauncherTheme(theme = theme, blackBackground = true, dynamicColor = !dynamicColorOption) {
Surface(modifier = Modifier.fillMaxSize()) {
var showProgress by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(1000L)
showProgress = true
}
AnimatedVisibility(showProgress) {
CircularProgressIndicator()
}
}
}
}
}
@@ -119,6 +119,12 @@ object LaunchUtils {
}
}
data class FullLauncherError(
val error: LauncherError,
val returnMessage: String,
val extendedMessage: String? = null,
) : Serializable
const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
const val LAUNCHER_KEY_RETURN_MESSAGE = "return_message"
const val LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE = "return_extended_message"