Release 1.2.2 (#28)

* Fix version detection edge case

* Fix VrApi detection

* Delete empty icon file

* Remove leftover code

* Animate icon and label on patch switching

* Fix forced replace icon/label patch

* Rename patch

* Create Unity stub if UE4 smali is present only

* Remove unused VrApi after patching

* Add line breaks for appends

* Add OculusXRPlugin binary patch

and improve binary patching

* Mark frida cleanup patch as not recommended

* Change frida cleanup behavior

* Filter version with existing directories

* Show cover if selected

* Remove VrApi library patch

* Android XR metadata

* Huawei VR metadata

* More metadata

* Uses-library for Android XR

* Bump version

* Rename patch
This commit is contained in:
Mathew Tkachuk authored and GitHub committed 2025-12-21 22:18:18 +03:00
1 parent c50c431a7e
commit 474f7bf12a
20 files changed
+228 -74

No files matched your search

@@ -39,15 +39,15 @@
<string name="patch_copy_libraries">Copy overport libraries</string> <string name="patch_copy_libraries">Copy overport libraries</string>
<string name="patch_copy_ovrplugin_vrapi">Copy OVRPlugin if VrApi is present</string> <string name="patch_copy_ovrplugin_vrapi">Copy OVRPlugin if VrApi is present</string>
<string name="patch_fix_min_android_sdk">Fix minimal Android SDK</string> <string name="patch_fix_min_android_sdk">Fix minimal Android SDK</string>
<string name="patch_generate_config">Generate ovrport config</string> <string name="patch_generate_config">Generate overport config</string>
<string name="patch_remove_localized_names">Remove localized app names</string> <string name="patch_remove_localized_names">Remove localized app names</string>
<string name="patch_clean_up_frida">Clean up Frida leftovers</string> <string name="patch_clean_up_frida">Clean up Frida leftovers in smali</string>
<string name="patch_oculus_unity">Patch Oculus detection for Unity</string> <string name="patch_oculus_unity">Patch Oculus detection for Unity</string>
<string name="patch_oculus_unreal">Patch Oculus detection for Unreal</string> <string name="patch_oculus_unreal">Patch Oculus detection for Unreal</string>
<string name="patch_vr_metadata">Pico/YVR/Quest metadata</string> <string name="patch_vr_metadata">Pico/YVR/Quest metadata</string>
<string name="patch_launcher_entry">Fix launcher icon entry</string> <string name="patch_launcher_entry">Fix launcher icon entry</string>
<string name="patch_remove_uses_library">Remove uses-library</string> <string name="patch_remove_uses_library">Remove uses-library</string>
<string name="patch_fix_unreal_crash">Fix Unreal Engine 4 crash</string> <string name="patch_fix_unreal_crash">Fix UE4 crash with Unity stub</string>
<string name="patch_meta_xr_audio">Patch Meta XR Audio</string> <string name="patch_meta_xr_audio">Patch Meta XR Audio</string>
<string name="patch_mark_as_debuggable">Mark application as debuggable</string> <string name="patch_mark_as_debuggable">Mark application as debuggable</string>
<string name="patch_mark_allow_backup">Mark application to allow backup</string> <string name="patch_mark_allow_backup">Mark application to allow backup</string>
@@ -56,4 +56,5 @@
<string name="patch_force_passthrough">Force enable passthrough</string> <string name="patch_force_passthrough">Force enable passthrough</string>
<string name="patch_disable_space_warp">Disable application space warp if used</string> <string name="patch_disable_space_warp">Disable application space warp if used</string>
<string name="patch_disable_controller_offset">Disable controller tracking offset</string> <string name="patch_disable_controller_offset">Disable controller tracking offset</string>
<string name="patch_remove_vrapi">Remove VrApi library</string>
</resources> </resources>
@@ -46,7 +46,6 @@ fun AppContent(
} }
var versionToUpdate by remember { mutableStateOf<GithubRelease?>(null) } var versionToUpdate by remember { mutableStateOf<GithubRelease?>(null) }
var showAboutDialog by remember { mutableStateOf(false) }
val urlHandler = LocalUriHandler.current val urlHandler = LocalUriHandler.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -95,18 +94,5 @@ fun AppContent(
navigationItems = listOf(patcherNavItem), navigationItems = listOf(patcherNavItem),
itemsEnabled = !viewModel.working && !viewModel.isApkLoaded() itemsEnabled = !viewModel.working && !viewModel.isApkLoaded()
) )
if (showAboutDialog) {
AlertDialog(
onDismissRequest = { showAboutDialog = false },
title = { Text("overport") },
text = { Text("about overport") }, // TODO
confirmButton = {
TextButton(onClick = { showAboutDialog = false }) {
Text("Close")
}
},
)
}
} }
} }
@@ -31,9 +31,12 @@ fun ApplicationInfo(
applicationPackage: String? = "com.company.application", applicationPackage: String? = "com.company.application",
applicationVersion: String? = "1.0.0", applicationVersion: String? = "1.0.0",
applicationIcon: ImageBitmap? = null, applicationIcon: ImageBitmap? = null,
forceLocalInfo: Boolean = false,
useCover: Boolean = false,
) { ) {
var iconIsLoading by rememberSaveable(applicationPackage) { mutableStateOf(true) } var iconIsLoading by rememberSaveable(applicationPackage) { mutableStateOf(true) }
var onlineIcon by rememberSaveable(applicationPackage) { mutableStateOf<ImageBitmap?>(null) } var onlineIcon by rememberSaveable(applicationPackage) { mutableStateOf<ImageBitmap?>(null) }
var onlineCover by rememberSaveable(applicationPackage) { mutableStateOf<ImageBitmap?>(null) }
var onlineLabel by rememberSaveable(applicationPackage) { mutableStateOf<String?>(null) } var onlineLabel by rememberSaveable(applicationPackage) { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -44,12 +47,18 @@ fun ApplicationInfo(
val info = applicationPackage?.let { getApplicationInfo(it) } val info = applicationPackage?.let { getApplicationInfo(it) }
onlineLabel = info?.displayName onlineLabel = info?.displayName
val image = info?.run { val icon = info?.run {
val associated = images.associateBy { it.imageType } val associated = images.associateBy { it.imageType }
associated["APP_IMG_ICON"] ?: associated["APP_IMG_COVER_SQUARE"] associated["APP_IMG_ICON"] ?: associated["APP_IMG_COVER_SQUARE"]
} }
onlineIcon = image?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap() val cover = info?.run {
val associated = images.associateBy { it.imageType }
associated["APP_IMG_COVER_SQUARE"] ?: associated["APP_IMG_ICON"]
}
onlineIcon = icon?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap()
onlineCover = cover?.uri?.let { Base64.getDecoder().decode(it) }?.decodeBitmap()
} }
iconIsLoading = false iconIsLoading = false
} }
@@ -61,14 +70,9 @@ fun ApplicationInfo(
) { ) {
Box { Box {
FadeVisibility(!iconIsLoading) { FadeVisibility(!iconIsLoading) {
onlineIcon?.let { AnimatedContent((onlineCover.takeIf { useCover } ?: onlineIcon)
Image( .takeIf { !forceLocalInfo } ?: applicationIcon) {
it, if (it == null) {
contentDescription = null,
modifier = Modifier.size(48.dp).rounded(8.dp)
)
} ?: run {
if (applicationIcon == null) {
Icon( Icon(
Icons.Default.Widgets, Icons.Default.Widgets,
contentDescription = null, contentDescription = null,
@@ -76,7 +80,7 @@ fun ApplicationInfo(
) )
} else { } else {
Image( Image(
applicationIcon, it,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(48.dp).rounded(8.dp) modifier = Modifier.size(48.dp).rounded(8.dp)
) )
@@ -91,7 +95,7 @@ fun ApplicationInfo(
modifier = Modifier.height(48.dp), modifier = Modifier.height(48.dp),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
AnimatedContent(onlineLabel ?: applicationName) { AnimatedContent(onlineLabel.takeIf { !forceLocalInfo } ?: applicationName) {
Text("$it ($applicationVersion)") Text("$it ($applicationVersion)")
} }
Text( Text(
@@ -20,7 +20,7 @@ import overportapp.composeapp.generated.resources.*
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@Preview @Preview
fun ApplicationInfoCard( fun ApplicationInfoContent(
applicationName: String? = "Application", applicationName: String? = "Application",
applicationPackage: String? = "", applicationPackage: String? = "",
applicationVersion: String? = "1.0.0", applicationVersion: String? = "1.0.0",
@@ -28,23 +28,13 @@ fun ApplicationInfoCard(
onCancel: () -> Unit = {}, onCancel: () -> Unit = {},
onConfirm: (Map<String, List<String>>) -> Unit = {} onConfirm: (Map<String, List<String>>) -> Unit = {}
) { ) {
Column( val enabledPatches = remember(applicationPackage) {
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
FlowRow(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
ApplicationInfo(applicationName, applicationPackage, applicationVersion, applicationIcon)
}
val enabledPatches = remember {
mutableStateMapOf( mutableStateMapOf(
*PatchStore.recommended().map { it.name to listOf<String>() }.toTypedArray() *PatchStore.recommended().map { it.name to listOf<String>() }.toTypedArray()
) )
} }
var patchIconArgument by remember {
var patchIconArgument by remember(applicationPackage) {
mutableStateOf("icon") mutableStateOf("icon")
} }
@@ -58,6 +48,24 @@ fun ApplicationInfoCard(
} }
} }
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
FlowRow(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
ApplicationInfo(
applicationName,
applicationPackage,
applicationVersion,
applicationIcon,
!enabledPatches.containsKey(PATCH_REPLACE_ICON_LABEL.name),
patchIconArgument != "icon",
)
}
Box(modifier = Modifier.weight(1f)) { Box(modifier = Modifier.weight(1f)) {
LazyColumn { LazyColumn {
items(PatchStore.all()) { items(PatchStore.all()) {
@@ -129,7 +137,9 @@ fun ApplicationInfoCard(
Button( Button(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
onClick = { onClick = {
if (enabledPatches.containsKey(PATCH_REPLACE_ICON_LABEL.name)) {
enabledPatches[PATCH_REPLACE_ICON_LABEL.name] = listOf(patchIconArgument) enabledPatches[PATCH_REPLACE_ICON_LABEL.name] = listOf(patchIconArgument)
}
onConfirm(enabledPatches) onConfirm(enabledPatches)
} }
) { ) {
@@ -70,7 +70,7 @@ fun PatcherScreen(
} }
FadeVisibility(isPatcherVisible) { FadeVisibility(isPatcherVisible) {
ApplicationInfoCard( ApplicationInfoContent(
viewModel.currentAppName(), viewModel.currentAppName(),
viewModel.currentAppPackage(), viewModel.currentAppPackage(),
viewModel.currentAppVersion(), viewModel.currentAppVersion(),
@@ -24,6 +24,7 @@ object PatchLocalizer {
PATCH_META_XR_AUDIO to Res.string.patch_meta_xr_audio, PATCH_META_XR_AUDIO to Res.string.patch_meta_xr_audio,
PATCH_MARK_AS_DEBUGGABLE to Res.string.patch_mark_as_debuggable, PATCH_MARK_AS_DEBUGGABLE to Res.string.patch_mark_as_debuggable,
PATCH_MARK_ALLOW_BACKUP to Res.string.patch_mark_allow_backup, PATCH_MARK_ALLOW_BACKUP to Res.string.patch_mark_allow_backup,
PATCH_REMOVE_VRAPI to Res.string.patch_remove_vrapi,
PATCH_REMOVE_UNREAL_FORCE_QUIT to Res.string.patch_remove_unreal_force_quit, PATCH_REMOVE_UNREAL_FORCE_QUIT to Res.string.patch_remove_unreal_force_quit,
PATCH_FORCE_PASSTHROUGH to Res.string.patch_force_passthrough, PATCH_FORCE_PASSTHROUGH to Res.string.patch_force_passthrough,
PATCH_DISABLE_SPACE_WARP to Res.string.patch_disable_space_warp, PATCH_DISABLE_SPACE_WARP to Res.string.patch_disable_space_warp,
+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.1 appVersion=1.2.2
+1 -1
View File
@@ -8,7 +8,7 @@ plugins {
} }
group = "moe.crx" group = "moe.crx"
version = "1.2.1" version = "1.2.2"
dependencies { dependencies {
implementation(libs.android.tools.build) implementation(libs.android.tools.build)
@@ -6,16 +6,6 @@ import moe.crx.overport.utils.named
import moe.crx.overport.utils.takeNodesEach import moe.crx.overport.utils.takeNodesEach
val PATCH_CLEAN_UP_FRIDA = Patch("patch_clean_up_frida") { val PATCH_CLEAN_UP_FRIDA = Patch("patch_clean_up_frida") {
selectLibrary("libfrda.so") {
file.delete()
}
selectLibrary("libfrda.config.so") {
file.delete()
}
selectLibrary("libscript.so") {
file.delete()
}
selectManifestJson { selectManifestJson {
takeNodesEach({ named("manifest") }) { takeNodesEach({ named("manifest") }) {
takeNodesEach({ named("application") }) { takeNodesEach({ named("application") }) {
@@ -9,8 +9,8 @@ val PATCH_COPY_OVRPLUGIN_VRAPI = Patch("patch_copy_ovrplugin_vrapi") {
val arch = getLibraries().resolve("lib/${archDirectory.name}") val arch = getLibraries().resolve("lib/${archDirectory.name}")
if (arch.exists()) { if (arch.exists()) {
val hasVrApi = arch.listFiles().find { it.name.lowercase() == "libvrapi.so" } != null val vrApi = archDirectory.listFiles().find { it.name.lowercase() == "libvrapi.so" }
if (hasVrApi) { if (vrApi != null) {
arch.listFiles().filter { it.name == "libOVRPlugin.so" }.forEach { library -> arch.listFiles().filter { it.name == "libOVRPlugin.so" }.forEach { library ->
library.copyTo(File(archDirectory, library.name), true) library.copyTo(File(archDirectory, library.name), true)
} }
@@ -3,18 +3,18 @@ package moe.crx.overport.patches
import moe.crx.overport.patching.Patch import moe.crx.overport.patching.Patch
val PATCH_FIX_UNREAL_CRASH = Patch("patch_fix_unreal_crash") { val PATCH_FIX_UNREAL_CRASH = Patch("patch_fix_unreal_crash") {
createSmali("com/unity3d/player/UnityPlayer") {
if (readText().isBlank()) {
append(".class public Lcom/unity3d/player/UnityPlayer;")
append(".super Ljava/lang/Object;")
append(".field public static currentActivity:Landroid/app/Activity;")
}
}
selectSmali("com/epicgames/ue4/GameActivity") { selectSmali("com/epicgames/ue4/GameActivity") {
replace( replace(
"sput-object (\\w+), Lcom\\/epicgames\\/ue4\\/GameActivity;->\\w+:Lcom\\/epicgames\\/ue4\\/GameActivity;", "sput-object (\\w+), Lcom\\/epicgames\\/ue4\\/GameActivity;->\\w+:Lcom\\/epicgames\\/ue4\\/GameActivity;",
"$0\nsput-object $1, Lcom/unity3d/player/UnityPlayer;->currentActivity:Landroid/app/Activity;" "$0\nsput-object $1, Lcom/unity3d/player/UnityPlayer;->currentActivity:Landroid/app/Activity;"
) )
createSmali("com/unity3d/player/UnityPlayer") {
if (readText().isBlank()) {
appendLine(".class public Lcom/unity3d/player/UnityPlayer;")
appendLine(".super Ljava/lang/Object;")
appendLine(".field public static currentActivity:Landroid/app/Activity;")
}
}
} }
} }
@@ -45,6 +45,7 @@ val PATCH_LAUNCHER_ENTRY = Patch("patch_launcher_entry") {
.put(createCategory("android.intent.category.LAUNCHER")) .put(createCategory("android.intent.category.LAUNCHER"))
.put(createCategory("com.oculus.intent.category.VR")) .put(createCategory("com.oculus.intent.category.VR"))
.put(createCategory("com.yvr.intent.category.VR")) .put(createCategory("com.yvr.intent.category.VR"))
.put(createCategory("org.khronos.openxr.intent.category.IMMERSIVE_HMD"))
} }
} }
} }
@@ -8,4 +8,15 @@ val PATCH_OCULUS_UNITY = Patch("patch_oculus_unity") {
"(getIsOnOculusHardware\\(\\)Z.*?)return (\\w+)", "$1\nconst/4 $2, 0x1\nreturn $2" "(getIsOnOculusHardware\\(\\)Z.*?)return (\\w+)", "$1\nconst/4 $2, 0x1\nreturn $2"
) )
} }
selectLibrary("libOculusXRPlugin.so") {
replaceHex(
"1F 1C 00 72 ?? ?? ?? ?? E9 07 9F 1A C0 00 80 52 20 00 A0 72 ?? ?? ?? ??",
"1F 1C 00 72 ?? ?? ?? ?? 29 00 80 52 C0 00 80 52 20 00 A0 72 ?? ?? ?? ??"
)
replaceHex(
"00 28 18 BF 01 20 ?? ?? 08 70 06 20 C0 F2 01 00",
"01 20 00 BF 00 BF ?? ?? 08 70 06 20 C0 F2 01 00"
)
}
} }
@@ -1,6 +1,7 @@
package moe.crx.overport.patches package moe.crx.overport.patches
import moe.crx.overport.patching.Patch import moe.crx.overport.patching.Patch
import moe.crx.overport.utils.nameAttribute
import moe.crx.overport.utils.named import moe.crx.overport.utils.named
import moe.crx.overport.utils.takeNodesEach import moe.crx.overport.utils.takeNodesEach
@@ -8,8 +9,12 @@ val PATCH_REMOVE_USES_LIBRARY = Patch("patch_remove_uses_library") {
selectManifestJson { selectManifestJson {
takeNodesEach({ named("manifest") }) { takeNodesEach({ named("manifest") }) {
takeNodesEach({ named("application") }) { takeNodesEach({ named("application") }) {
takeNodesEach({ named("uses-library") }) { null } takeNodesEach({ named("uses-library") }) {
takeNodesEach({ named("uses-native-library") }) { null } if (nameAttribute() != "libopenxr.google.so") null else this
}
takeNodesEach({ named("uses-native-library") }) {
if (nameAttribute() != "libopenxr.google.so") null else this
}
} }
} }
} }
@@ -0,0 +1,16 @@
package moe.crx.overport.patches
import moe.crx.overport.patching.Patch
val PATCH_REMOVE_VRAPI = Patch("patch_remove_vrapi", false) {
selectWorkspace {
file.resolve("root/lib").listFiles()?.forEach { archDirectory ->
val arch = getLibraries().resolve("lib/${archDirectory.name}")
if (arch.exists()) {
val vrApi = archDirectory.listFiles().find { it.name.lowercase() == "libvrapi.so" }
vrApi?.delete()
}
}
}
}
@@ -55,6 +55,12 @@ val PATCH_REPLACE_ICON_LABEL = Patch("patch_replace_icon_label") { arguments ->
} }
} }
if (!iconDownloaded) {
selectFile("root/res/drawable/custom_icon_ovrp.png") {
delete()
}
}
if (iconDownloaded) { if (iconDownloaded) {
selectPackageBlock { selectPackageBlock {
getOrCreate("", "drawable", "custom_icon_ovrp").apply { getOrCreate("", "drawable", "custom_icon_ovrp").apply {
@@ -22,10 +22,100 @@ fun createMetadata(name: String, value: String): JSONObject {
) )
} }
fun createMetadataFloat(name: String, value: Float): JSONObject {
return JSONObject().put("node_type", "element").put("name", "meta-data").put(
"attributes", JSONArray().put(
JSONObject().put("name", "name").put("id", 16842755)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "STRING").put("data", name)
).put(
JSONObject().put("name", "value").put("id", 16842788)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "FLOAT").put("data", value)
)
)
}
fun createUsesFeature(name: String, required: Boolean): JSONObject {
return JSONObject().put("node_type", "element").put("name", "uses-feature").put(
"attributes", JSONArray().put(
JSONObject().put("name", "name").put("id", 16842755)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "STRING").put("data", name)
).put(
JSONObject().put("name", "required").put("id", 16843406)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "BOOLEAN").put("data", required)
)
)
}
fun createUsesLibrary(name: String, required: Boolean): JSONObject {
return JSONObject().put("node_type", "element").put("name", "uses-library").put(
"attributes", JSONArray().put(
JSONObject().put("name", "name").put("id", 16842755)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "STRING").put("data", name)
).put(
JSONObject().put("name", "required").put("id", 16843406)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "BOOLEAN").put("data", required)
)
)
}
fun createUsesPermission(name: String): JSONObject {
return JSONObject().put("node_type", "element").put("name", "uses-permission").put(
"attributes", JSONArray().put(
JSONObject().put("name", "name").put("id", 16842755)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "STRING").put("data", name)
)
)
}
fun createProperty(key: String, value: String): JSONObject {
return JSONObject().put("node_type", "element").put("name", "property").put(
"attributes", JSONArray().put(
JSONObject().put("name", "name").put("id", 16842755)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "STRING").put("data", key)
).put(
JSONObject().put("name", "value").put("id", 16842788)
.put("uri", "http://schemas.android.com/apk/res/android").put("prefix", "android")
.put("value_type", "STRING").put("data", value)
)
)
}
val PATCH_VR_METADATA = Patch("patch_vr_metadata") { val PATCH_VR_METADATA = Patch("patch_vr_metadata") {
selectManifestJson { selectManifestJson {
takeNodesEach({ named("manifest") }) { takeNodesEach({ named("manifest") }) {
takeNodesEach({ named("application") }) { takeNodesEach({ named("application") }) {
takeNodesEach({ named("activity") }) {
takeNodes {
this?.put(
createProperty(
"android.window.PROPERTY_XR_ACTIVITY_START_MODE",
"XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED"
)
)?.put(
createProperty(
"android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED",
"XR_BOUNDARY_TYPE_LARGE"
)
)
}
}
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
} }
@@ -33,6 +123,14 @@ val PATCH_VR_METADATA = Patch("patch_vr_metadata") {
this?.put(createMetadata("pvr.app.type", "vr")) this?.put(createMetadata("pvr.app.type", "vr"))
?.put(createMetadata("com.yvr.intent.category.VR", "vr_only")) ?.put(createMetadata("com.yvr.intent.category.VR", "vr_only"))
?.put(createMetadata("com.oculus.supportedDevices", "all")) ?.put(createMetadata("com.oculus.supportedDevices", "all"))
?.put(createMetadata("com.huawei.android.vr.application.mode", "vr_only"))
?.put(createMetadata("com.huawei.android.vr.application.type", "game"))
?.put(createMetadata("com.huawei.vr.application.freeDegree", "3dof|6dof"))
?.put(createMetadataFloat("android.max_aspect", 2.1f))
?.put(createMetadata("minWaveSDKVersion", "1"))
?.put(createMetadata("com.htc.vr.content.NumController", "1,2"))
?.put(createMetadata("com.htc.vr.content.NumDoFController", "3,6DoF"))
?.put(createMetadata("com.htc.vr.content.NumDoFHmd", "3,6DoF"))
} }
} }
} }
@@ -8,6 +8,10 @@ class FileTransformer(val file: File) {
file.writeText(Regex(input, RegexOption.DOT_MATCHES_ALL).replace(file.readText(), output)) file.writeText(Regex(input, RegexOption.DOT_MATCHES_ALL).replace(file.readText(), output))
} }
fun delete(): Boolean {
return file.deleteRecursively()
}
fun readText(): String { fun readText(): String {
return file.readText() return file.readText()
} }
@@ -16,6 +20,11 @@ class FileTransformer(val file: File) {
file.appendText(line) file.appendText(line)
} }
fun appendLine(line: String) {
append(line)
append("\n")
}
fun readJson(): JSONObject? { fun readJson(): JSONObject? {
return runCatching { JSONObject(file.readText()) }.getOrNull() return runCatching { JSONObject(file.readText()) }.getOrNull()
} }
@@ -35,6 +44,10 @@ class FileTransformer(val file: File) {
fun replaceHex(input: String, output: String) { fun replaceHex(input: String, output: String) {
val bytes = file.readBytes() val bytes = file.readBytes()
val inputBytes = input.split(' ') val inputBytes = input.split(' ')
val outputBytes = output.split(' ')
var startIndex = -1
check(inputBytes.size == outputBytes.size)
for (i in 0..<bytes.size - inputBytes.size + 1) { for (i in 0..<bytes.size - inputBytes.size + 1) {
var matches = true var matches = true
@@ -52,12 +65,22 @@ class FileTransformer(val file: File) {
} }
if (matches) { if (matches) {
val replaced = bytes.slice(0..<i) + startIndex = i
output.split(' ').map { it.toUByte(16).toByte() } + }
bytes.slice(i + inputBytes.size..<bytes.size) }
file.writeBytes(replaced.toByteArray())
if (startIndex == -1) {
return return
} }
for (i in 0..<inputBytes.size) {
if (outputBytes[i] != "??") {
bytes[i + startIndex] = outputBytes[i].toUByte(16).toByte()
} else if (inputBytes[i] != "??") {
bytes[i + startIndex] = inputBytes[i].toUByte(16).toByte()
} }
} }
file.writeBytes(bytes)
}
} }
@@ -42,6 +42,7 @@ object PatchStore {
register(PATCH_META_XR_AUDIO) register(PATCH_META_XR_AUDIO)
register(PATCH_MARK_AS_DEBUGGABLE) register(PATCH_MARK_AS_DEBUGGABLE)
register(PATCH_MARK_ALLOW_BACKUP) register(PATCH_MARK_ALLOW_BACKUP)
register(PATCH_REMOVE_VRAPI)
register(PATCH_REMOVE_UNREAL_FORCE_QUIT) register(PATCH_REMOVE_UNREAL_FORCE_QUIT)
register(PATCH_FORCE_PASSTHROUGH) register(PATCH_FORCE_PASSTHROUGH)
register(PATCH_DISABLE_SPACE_WARP) register(PATCH_DISABLE_SPACE_WARP)
@@ -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.1" const val VERSION = "1.2.2"
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() }
@@ -80,6 +80,7 @@ class VersionManager(val workingDirectory: File) {
if (installedEntry != null) { if (installedEntry != null) {
val librariesDir = workingDirectory.resolve("libraries/${installedEntry.version}") val librariesDir = workingDirectory.resolve("libraries/${installedEntry.version}")
if (librariesDir.isDirectory) { if (librariesDir.isDirectory) {
appendInstalled(installedEntry)
return installedEntry.version return installedEntry.version
} }
} }
@@ -132,7 +133,7 @@ class VersionManager(val workingDirectory: File) {
o2.version o2.version
) )
) 1 else -1 ) 1 else -1
}) }).filter { workingDirectory.resolve("libraries/${it.version}").isDirectory }
} }
return listOf() return listOf()