Release 1.2.3 (#67)

* Fix Android XR metadata placement

* Update README

* Fix broken max aspect metadata

* fix: wrong placement of uses-library

* refactor: bitmap caching and coroutine scopes

* chore: update ARSCLib to 1.4.0

* chore: bump version to 1.2.3
This commit is contained in:
Mathew Tkachuk authored and GitHub committed 2026-07-25 18:40:18 +03:00
1 parent 474f7bf12a
commit c790854772
10 files changed
+45 -42

No files matched your search

+1 -1
View File
@@ -40,7 +40,7 @@ If you have any issues regarding game compatibility, please, enable verbose logg
# About legality # About legality
Think of this app like an emulator: you need to legally own the game files to use it. Think of this app like an emulator: you need to legally obtain the game files to use it.
Obviously, to get a Quest game running on a different headset, the app has to strip away some entitlement checks. Obviously, to get a Quest game running on a different headset, the app has to strip away some entitlement checks.
However, the game still won't launch on a Quest unless you’ve actually bought it. However, the game still won't launch on a Quest unless you’ve actually bought it.
@@ -15,7 +15,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import moe.crx.overport.app.decodeBitmap import moe.crx.overport.app.decodeBitmap
import moe.crx.overport.app.util.ModifierUtil.rounded import moe.crx.overport.app.util.ModifierUtil.rounded
@@ -25,26 +24,31 @@ import overportapp.composeapp.generated.resources.Res
import overportapp.composeapp.generated.resources.unknown_package import overportapp.composeapp.generated.resources.unknown_package
import java.util.* import java.util.*
val iconCache = mutableMapOf<String, ImageBitmap?>()
val coverCache = mutableMapOf<String, ImageBitmap?>()
@Composable @Composable
fun ApplicationInfo( fun ApplicationInfo(
applicationName: String? = "Application", applicationName: String,
applicationPackage: String? = "com.company.application", applicationPackage: String,
applicationVersion: String? = "1.0.0", applicationVersion: String,
applicationIcon: ImageBitmap? = null, applicationIcon: ImageBitmap? = null,
forceLocalInfo: Boolean = false, forceLocalInfo: Boolean = false,
useCover: Boolean = false, useCover: Boolean = false,
) { ) {
var iconIsLoading by rememberSaveable(applicationPackage) { mutableStateOf(true) } var iconIsLoading by remember(applicationPackage) { mutableStateOf(true) }
var onlineIcon by rememberSaveable(applicationPackage) { mutableStateOf<ImageBitmap?>(null) } var onlineIcon by remember(applicationPackage) { mutableStateOf(iconCache[applicationPackage]) }
var onlineCover by rememberSaveable(applicationPackage) { mutableStateOf<ImageBitmap?>(null) } var onlineCover by remember(applicationPackage) { mutableStateOf(coverCache[applicationPackage]) }
var onlineLabel by rememberSaveable(applicationPackage) { mutableStateOf<String?>(null) } var onlineLabel by rememberSaveable(applicationPackage) { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(applicationPackage) { LaunchedEffect(applicationPackage) {
scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { if (iconCache.containsKey(applicationPackage)) {
val info = applicationPackage?.let { getApplicationInfo(it) } return@withContext
}
val info = getApplicationInfo(applicationPackage)
onlineLabel = info?.displayName onlineLabel = info?.displayName
val icon = info?.run { val icon = info?.run {
@@ -57,13 +61,18 @@ fun ApplicationInfo(
associated["APP_IMG_COVER_SQUARE"] ?: associated["APP_IMG_ICON"] associated["APP_IMG_COVER_SQUARE"] ?: associated["APP_IMG_ICON"]
} }
onlineIcon = icon?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap() val iconBitmap = icon?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap()
onlineCover = cover?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap() val coverBitmap = cover?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap()
}
iconCache[applicationPackage] = iconBitmap
coverCache[applicationPackage] = coverBitmap
onlineIcon = iconBitmap
onlineCover = coverBitmap
iconIsLoading = false iconIsLoading = false
} }
} }
}
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
@@ -21,9 +21,9 @@ import overportapp.composeapp.generated.resources.*
@Composable @Composable
@Preview @Preview
fun ApplicationInfoContent( fun ApplicationInfoContent(
applicationName: String? = "Application", applicationName: String,
applicationPackage: String? = "", applicationPackage: String,
applicationVersion: String? = "1.0.0", applicationVersion: String,
applicationIcon: ImageBitmap? = null, applicationIcon: ImageBitmap? = null,
onCancel: () -> Unit = {}, onCancel: () -> Unit = {},
onConfirm: (Map<String, List<String>>) -> Unit = {} onConfirm: (Map<String, List<String>>) -> Unit = {}
@@ -35,15 +35,12 @@ fun PatcherScreen(
val isPatcherVisible = !viewModel.working && viewModel.isApkLoaded() val isPatcherVisible = !viewModel.working && viewModel.isApkLoaded()
LaunchedEffect(openFlow) { LaunchedEffect(openFlow) {
scope.launch {
openFlow.collect { openFlow.collect {
inputFile = it inputFile = it
} }
} }
}
LaunchedEffect(saveFlow) { LaunchedEffect(saveFlow) {
scope.launch {
saveFlow.collect { stream -> saveFlow.collect { stream ->
val outputStream = stream ?: return@collect val outputStream = stream ?: return@collect
try { try {
@@ -57,8 +54,7 @@ fun PatcherScreen(
errorMessage = getString(Res.string.unknown_error) to ex.stackTraceToString() errorMessage = getString(Res.string.unknown_error) to ex.stackTraceToString()
} finally { } finally {
viewModel.cancel() viewModel.cancel()
outputStream.close() //outputStream.close()
}
} }
} }
} }
@@ -71,9 +67,9 @@ fun PatcherScreen(
FadeVisibility(isPatcherVisible) { FadeVisibility(isPatcherVisible) {
ApplicationInfoContent( ApplicationInfoContent(
viewModel.currentAppName(), viewModel.currentAppName() ?: "",
viewModel.currentAppPackage(), viewModel.currentAppPackage() ?: "",
viewModel.currentAppVersion(), viewModel.currentAppVersion() ?: "",
viewModel.currentAppIcon(), viewModel.currentAppIcon(),
onCancel = { onCancel = {
scope.launch { scope.launch {
@@ -27,12 +27,10 @@ fun VersionManagerSheet(viewModel: MainViewModel, onSelected: (String) -> Unit,
var available by remember { mutableStateOf<List<OverportRelease>?>(null) } var available by remember { mutableStateOf<List<OverportRelease>?>(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
available = viewModel.versionManager.available() available = viewModel.versionManager.available()
} }
} }
}
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { onCancel() }, onDismissRequest = { onCancel() },
+1 -1
View File
@@ -7,4 +7,4 @@ org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.useAndroidX=true android.useAndroidX=true
#Application #Application
appVersion=1.2.2 appVersion=1.2.3
+1 -1
View File
@@ -8,7 +8,7 @@ plugins {
} }
group = "moe.crx" group = "moe.crx"
version = "1.2.2" version = "1.2.3"
dependencies { dependencies {
implementation(libs.android.tools.build) implementation(libs.android.tools.build)
@@ -31,7 +31,7 @@ fun createMetadataFloat(name: String, value: Float): JSONObject {
).put( ).put(
JSONObject().put("name", "value").put("id", 16842788) JSONObject().put("name", "value").put("id", 16842788)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android") .put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "FLOAT").put("data", value) .put("value_type", "FLOAT").put("data", value.toRawBits())
) )
) )
} }
@@ -91,6 +91,14 @@ fun createProperty(key: String, value: String): JSONObject {
val PATCH_VR_METADATA = Patch("patch_vr_metadata") { val PATCH_VR_METADATA = Patch("patch_vr_metadata") {
selectManifestJson { selectManifestJson {
takeNodesEach({ named("manifest") }) { takeNodesEach({ named("manifest") }) {
takeNodes {
this?.put(createUsesFeature("android.software.xr.api.openxr", true))
?.put(createUsesFeature("android.software.xr.api.spatial", true))
?.put(createUsesFeature("android.software.xr.input.controller", false))
?.put(createUsesPermission("org.khronos.openxr.permission.OPENXR"))
?.put(createUsesPermission("org.khronos.openxr.permission.OPENXR_SYSTEM"))
?.put(createUsesPermission("com.huawei.android.permission.VR"))
}
takeNodesEach({ named("application") }) { takeNodesEach({ named("application") }) {
takeNodesEach({ named("activity") }) { takeNodesEach({ named("activity") }) {
takeNodes { takeNodes {
@@ -107,15 +115,6 @@ val PATCH_VR_METADATA = Patch("patch_vr_metadata") {
) )
} }
} }
takeNodes {
this?.put(createUsesFeature("android.software.xr.api.openxr", true))
this?.put(createUsesFeature("android.software.xr.api.spatial", true))
this?.put(createUsesLibrary("libopenxr.google.so", false))
this?.put(createUsesFeature("android.software.xr.input.controller", false))
?.put(createUsesPermission("org.khronos.openxr.permission.OPENXR"))
?.put(createUsesPermission("org.khronos.openxr.permission.OPENXR_SYSTEM"))
?.put(createUsesPermission("com.huawei.android.permission.VR"))
}
takeNodesEach({ named("meta-data") }) { takeNodesEach({ named("meta-data") }) {
if (nameAttribute() == "com.oculus.supportedDevices") null else this if (nameAttribute() == "com.oculus.supportedDevices") null else this
} }
@@ -131,6 +130,7 @@ val PATCH_VR_METADATA = Patch("patch_vr_metadata") {
?.put(createMetadata("com.htc.vr.content.NumController", "1,2")) ?.put(createMetadata("com.htc.vr.content.NumController", "1,2"))
?.put(createMetadata("com.htc.vr.content.NumDoFController", "3,6DoF")) ?.put(createMetadata("com.htc.vr.content.NumDoFController", "3,6DoF"))
?.put(createMetadata("com.htc.vr.content.NumDoFHmd", "3,6DoF")) ?.put(createMetadata("com.htc.vr.content.NumDoFHmd", "3,6DoF"))
?.put(createUsesLibrary("libopenxr.google.so", false))
} }
} }
} }
@@ -10,7 +10,7 @@ class VersionManager(val workingDirectory: File) {
companion object { companion object {
val json = Json { ignoreUnknownKeys = true } val json = Json { ignoreUnknownKeys = true }
const val VERSION = "1.2.2" const val VERSION = "1.2.3"
private fun isVersionLower(left: String, right: String): Boolean { private fun isVersionLower(left: String, right: String): Boolean {
val (lY, lM, lD) = left.substringBeforeLast('-').split('.').map { it.toInt() } val (lY, lM, lD) = left.substringBeforeLast('-').split('.').map { it.toInt() }