Files
spoopyghosty0--frameport/tests/test_patches.py
T
Codex 25db57f468 Fix #387: pool GLES panel swapchains before the runtime count limit
Add opt-in swapchain_pool with private GLES images and up to two packed
runtime atlases. Start at eight live app runtime swapchains to reserve
headroom before the session cap. Preserve image ownership, GL state,
source fences, crops and the existing cylinder/equirect layer ordering.

Enable for 4XVR, add count-limit triage without unknown suggestions for
older apps, and document the pending headset menu check. Leave default
recipes and adapter revisions unchanged. Rebuild arm64/arm32 artifacts.

Validation: 132 targeted tests passed (2 skipped), including the native
runtime/GL harness; Ruff and generated docs pass. Full host suite:
2095 passed, 137 skipped, 12 failures and 16 setup errors, all denied
socket creation in this sandbox. No headset validation or push.
2026-10-19 00:04:16 -04:00

1079 lines
56 KiB
Python

import zipfile
import pytest
from frameport.analysis.stubgen import build_stub_library
from frameport.apk.workspace import ApkWorkspace
from frameport.core.events import Reporter
from frameport.core.models import Analysis, Recipe
from frameport.patches import base
from frameport.patches.settings import adapter_settings
def _analysis(**kw):
d = dict(package="com.example.questgame", version="1.0", label="Quest Game", abis=["arm64-v8a"], engine="Unity",
xr="OpenXR", graphics="Vulkan (declared in manifest)", direct_vrapi=False, libs=[], launcher_activity=None,
has_info_category=True, meta_permissions=[], uses_glad_gl=False, unity_msaa_levels=0,
oculus_os_classes=False, is_overport_output=True, debuggable=True, extra={"size": 1})
d.update(kw)
return Analysis(**d)
def _apk(tmp_path, manifest):
p = tmp_path / "in.apk"
loader_imports = build_stub_library(["ovr_Present"], soname="libovrplatformloader.so")
game = build_stub_library(["game_main"], soname="libgame.so")
with zipfile.ZipFile(p, "w") as z:
z.writestr("AndroidManifest.xml", manifest)
z.writestr("classes.dex", b"dex\n035\0")
z.writestr("lib/arm64-v8a/libopenxr_loader_generic.so", b"\x7fELF-original-loader")
z.writestr("lib/arm64-v8a/libovrplatformloader.so", loader_imports)
z.writestr("lib/arm64-v8a/libgame.so", game)
return p
def test_registry_has_everything():
ids = {p.id for p in base.all_patches()}
for pid in ("patch_copy_libraries", "patch_remove_unreal_force_quit", "frame.adapter", "frame.launcher",
"frame.ovrstubs", "frame.vrapi_bridge", "frame.gl_shim", "adapter.scene_emul", "device.files"):
assert pid in ids
def test_adapter_and_launcher(tmp_path, quest_manifest):
apk = _apk(tmp_path, quest_manifest)
recipe = {"frame.adapter": {}, "frame.launcher": {}, "adapter.scene_emul": {"value": 1}}
with ApkWorkspace(apk) as ws:
for pid in ("frame.adapter", "frame.launcher"):
ctx = base.ApkContext(ws, _analysis(), {}, Reporter(), recipe)
assert base.get(pid).apply(ctx)
assert all(ok for _, ok, _ in base.get("frame.adapter").validate(ctx))
assert ws.has(ws.lib("libVkLayer_fdm_darken.so")) # dormant until the launcher test opt-in
out = ws.write(tmp_path / "out.apk")
with zipfile.ZipFile(out) as z:
names = z.namelist()
assert z.read("lib/arm64-v8a/libopenxr_loader_original.so") == b"\x7fELF-original-loader"
assert z.read("lib/arm64-v8a/libopenxr_loader_generic.so").startswith(b"\x7fELF")
assert (z.read("lib/arm64-v8a/libframe_settings.so")
== b"scale=1.0\nfoveation_fix=1\ncontroller_fix=1\nquest_buttons=0\nscene_emul=1\n")
assert names.count("lib/arm64-v8a/libopenxr_loader_generic.so") == 1
def test_adapter_refreshes_existing_wrapper(tmp_path, quest_manifest):
"""Reinstalling an already wrapped APK replaces its adapter with the shipped repair."""
from frameport.patches.frame import artifact
apk = _apk(tmp_path, quest_manifest)
with ApkWorkspace(apk) as ws:
ws.put(ws.lib("libopenxr_loader_original.so"), b"original-loader")
ws.put(ws.lib("libopenxr_loader_generic.so"), b"old-adapter")
patch = base.get("frame.adapter")
ctx = base.ApkContext(ws, _analysis(), {}, Reporter(), {"frame.adapter": {}})
assert patch.apply(ctx)
from frameport.analysis import elf
from frameport.patches.frame.foveation import LAYER
assert ws.read_lib("libopenxr_loader_generic.so") == elf.add_needed(
artifact(ws.abi, "libopenxr_loader_generic.so"), LAYER)
assert ws.read_lib("libopenxr_loader_original.so") == b"original-loader"
assert not patch.apply(ctx)
def test_native_video_follows_the_recipe_not_the_package(tmp_path, quest_manifest):
"""Batman's cutscene setup = frame.hw_video_decode + adapter.surface_native (its recipe), for any package. The
decoder itself is installed once per Frame by the agent, never embedded in a game's APK."""
from pathlib import Path
plugin = (Path(__file__).with_name("fixtures") / "libfakeovrplugin_arm64.so").read_bytes()
video = {"frame.adapter": {}, "frame.hw_video_decode": {}, "adapter.surface_native": {"value": 1}}
for package, recipe, enabled in (("com.camouflaj.manta", video, True), ("com.example.questgame", video, True),
("com.camouflaj.manta", {"frame.adapter": {}}, False)):
apk = _apk(tmp_path, quest_manifest)
with zipfile.ZipFile(apk, "a") as z:
z.writestr("lib/arm64-v8a/libOVRPlugin.so", plugin)
with ApkWorkspace(apk) as ws:
ctx = base.ApkContext(ws, _analysis(package=package), {}, Reporter(), recipe)
assert base.get("frame.adapter").apply(ctx)
assert (b"surface_native=1\n" in ws.read_lib("libframe_settings.so")) is enabled
assert ws.has(ws.lib("libframe_xrshim.so")) is enabled
assert not any(n.startswith("assets/frameport/hevc/") for n in ws.names())
if enabled:
assert b"libframe_xrshim.so\0" in ws.read_lib("libOVRPlugin.so")
assert not base.get("frame.adapter").apply(ctx)
def test_adapter_removes_unneeded_codec_without_touching_video_assets(tmp_path, quest_manifest):
"""Updating earlier test builds leaves no codec wrapper active in unrelated games."""
apk = _apk(tmp_path, quest_manifest)
with zipfile.ZipFile(apk, "a") as z:
for name in ("manifest.json", "libstagefrighthw.so", "media_codecs_frameport.xml", "podman.py",
"COPYING.FFmpeg"):
z.writestr("assets/frameport/hevc/" + name, b"old test codec")
z.writestr("assets/movie.mp4", b"original movie")
with ApkWorkspace(apk) as ws:
ctx = base.ApkContext(ws, _analysis(), {}, Reporter(), {"frame.adapter": {}})
assert base.get("frame.adapter").apply(ctx)
assert not any(n.startswith("assets/frameport/hevc/") for n in ws.names())
assert not base.get("frame.adapter").apply(ctx)
out = ws.write(tmp_path / "scoped.apk")
with zipfile.ZipFile(out) as z:
assert z.read("assets/movie.mp4") == b"original movie"
assert not any(n.startswith("assets/frameport/hevc/") for n in z.namelist())
def test_arm32_does_not_get_the_arm64_codec_or_shim(tmp_path):
apk = tmp_path / "arm32.apk"
with zipfile.ZipFile(apk, "w") as z:
z.writestr("lib/armeabi-v7a/libopenxr_loader_generic.so", b"original loader")
recipe = {"frame.adapter": {}, "frame.hw_video_decode": {}, "adapter.surface_native": {"value": 1}}
analysis = _analysis(package="com.camouflaj.manta", abis=["armeabi-v7a"])
assert not base.get("frame.hw_video_decode").applies(analysis)
with ApkWorkspace(apk) as ws:
ctx = base.ApkContext(ws, analysis, {}, Reporter(), recipe)
assert base.get("frame.adapter").apply(ctx)
assert not ws.has(ws.lib("libframe_xrshim.so"))
assert not any(n.startswith("assets/frameport/hevc/") for n in ws.names())
def test_controller_models_adds_xrshim(tmp_path, quest_manifest):
from pathlib import Path
from frameport.analysis import elf
# like Meta's OVRPlugin: DT_NEEDED libopenxr_loader.so + dlopen("libopenxr_loader.so") /
# dlsym(xrGetInstanceProcAddr)
plugin = (Path(__file__).with_name("fixtures") / "libfakeovrplugin_arm64.so").read_bytes()
for enabled in (True, False):
apk = _apk(tmp_path, quest_manifest)
with zipfile.ZipFile(apk, "a") as z:
z.writestr("lib/arm64-v8a/libOVRPlugin.so", plugin)
recipe = {"frame.adapter": {}, **({"adapter.controller_models": {"value": 1}} if enabled else {})}
with ApkWorkspace(apk) as ws:
assert base.get("frame.adapter").apply(base.ApkContext(ws, _analysis(), {}, Reporter(), recipe))
out = ws.write(tmp_path / f"out{enabled}.apk")
with zipfile.ZipFile(out) as z:
names = z.namelist()
patched = z.read("lib/arm64-v8a/libOVRPlugin.so")
if enabled:
assert "lib/arm64-v8a/libframe_xrshim.so" in names
assert len(patched) == len(plugin) and b"libframe_xrshim.so\0\0" in patched
assert patched.count(b"libopenxr_loader.so\0") == 1 # only the DT_NEEDED name (.dynstr) is left
assert elf.needed(patched) == elf.needed(plugin)
else: # off: builds stay byte-identical to before
assert "lib/arm64-v8a/libframe_xrshim.so" not in names and patched == plugin
def test_replace_rodata_string_whole_strings_only():
from pathlib import Path
from frameport.analysis import elf
plugin = (Path(__file__).with_name("fixtures") / "libfakeovrplugin_arm64.so").read_bytes()
out, n = elf.replace_rodata_string(plugin, "libopenxr_loader.so", "libframe_xrshim.so")
assert n == 1 and elf.replace_rodata_string(out, "libopenxr_loader.so", "x")[1] == 0
assert elf.replace_rodata_string(plugin, "openxr_loader.so", "y")[1] == 0 # a tail of a longer string
def test_settings_order_and_types():
s = adapter_settings({"adapter.controller_fix": {"value": 0}, "adapter.scene_height": {"value": "3"}})
assert list(s) == ["scale", "foveation_fix", "controller_fix", "scene_height"]
assert s["controller_fix"] == 0 and s["scene_height"] == 3.0
def test_controller_models_setting():
patch = base.get("adapter.controller_models")
plain = _analysis()
assert patch.detect(plain) is None and not patch.applies(plain)
meta_sdk = _analysis(libs=["libOVRPlugin.so"])
assert patch.applies(meta_sdk) and patch.detect(meta_sdk) is None # SDK present, runtime models not declared
for a in (_analysis(meta_permissions=["com.oculus.permission.RENDER_MODEL"]),
_analysis(extra={"features": {"com.oculus.feature.RENDER_MODEL": False}})):
s = patch.detect(a)
assert s.recommended and s.params == {"value": 1} and patch.applies(a)
assert adapter_settings({"adapter.controller_models": {"value": 1}})["controller_models"] == 1
assert "controller_models" not in adapter_settings({}) # off unless selected: existing builds are unchanged
def test_cube_quads_applies_like_cube_standin():
quads = base.get("adapter.cube_quads")
standin = base.get("adapter.cube_standin")
assert quads.default == 1
for analysis in (_analysis(), _analysis(graphics="GLES or unknown (no Vulkan declaration)"),
_analysis(abis=["armeabi-v7a"])):
assert quads.applies(analysis) == standin.applies(analysis)
assert adapter_settings({"adapter.cube_quads": {"value": "0"}})["cube_quads"] == 0
def test_equirect_emul_setting():
patch = base.get("adapter.equirect_emul")
gles = dict(graphics="GLES or unknown (no Vulkan declaration)")
vulkan_360 = _analysis(extra={"xr_layer_exts": ["XR_KHR_composition_layer_equirect2"]})
assert not patch.applies(vulkan_360) and patch.detect(vulkan_360) is None # never offered for Vulkan games
plain = _analysis(**gles, extra={"xr_layer_exts": ["XR_KHR_composition_layer_cylinder"]})
assert patch.applies(plain) and patch.detect(plain) is None # GLES but no 360 layers requested
player = _analysis(**gles, extra={"xr_layer_exts": ["XR_KHR_composition_layer_cylinder",
"XR_KHR_composition_layer_equirect2"]})
s = patch.detect(player)
assert s.recommended and s.params == {"value": 1}
for key in ("equirect_face", "equirect_res", "equirect_flip", "equirect_fps", "equirect_stereo"):
assert base.get(f"adapter.{key}").applies(player) and not base.get(f"adapter.{key}").applies(vulkan_360)
def test_per_game_session_settings_are_off_unless_selected():
keys = ("equirect_emul", "layer_debug", "stable_local", "focus_hold", "aim_pitch", "aim_yaw", "aim_forward",
"refresh_rate", "equirect_face", "equirect_flip", "equirect_fps", "equirect_stereo")
assert not set(keys) & set(adapter_settings({})) # existing builds are unchanged
chosen = adapter_settings({"adapter.refresh_rate": {"value": 90}, "adapter.aim_pitch": {"value": "-12.5"},
"adapter.focus_hold": {"value": 1}})
assert chosen["refresh_rate"] == 90.0 and chosen["aim_pitch"] == -12.5 and chosen["focus_hold"] == 1
for key in keys:
patch = base.get(f"adapter.{key}")
assert patch.detect(_analysis()) is None
def test_vk_hide_fdm_is_a_per_game_unreal_setting():
"""Metro Awakening: Unreal's Vulkan isn't always detected, so it's offered for every Unreal game; off by default."""
from frameport.patches.settings import UI
patch = base.get("adapter.vk_hide_fdm")
assert patch.applies(_analysis(engine="Unreal", graphics="GLES or unknown (no Vulkan declaration)"))
assert not patch.applies(_analysis(engine="Unity"))
assert "vk_hide_fdm" not in adapter_settings({}) and UI["vk_hide_fdm"]["group"] == "troubleshooting"
def test_input_diag_is_an_off_by_default_diagnostic():
from frameport.patches.settings import UI
patch = base.get("adapter.input_diag")
assert patch.default == 0 and patch.applies(_analysis()) and patch.detect(_analysis()) is None
assert "input_diag" not in adapter_settings({}) # existing builds are unchanged
assert adapter_settings({"adapter.input_diag": {"value": 1}})["input_diag"] == 1
assert UI["input_diag"]["group"] == "troubleshooting" and UI["input_diag"]["level"] == "advanced"
def test_detect_direct_vrapi_suggests_bridge_and_shim():
a = _analysis(xr="VrApi", direct_vrapi=True, uses_glad_gl=True, graphics="GLES or unknown", package="x.y.unknown")
from frameport.recommend import engine
r = engine.suggest(a)
assert "frame.vrapi_bridge" in r.patches and "frame.gl_shim" in r.patches
assert r.source == "heuristics"
def test_catalog_recipe_climb2():
from frameport.recommend import engine
r = engine.suggest(_analysis(package="com.crytek.climb2", xr="VrApi", direct_vrapi=True, engine="Other"))
assert r.source.startswith("catalog")
assert "frame.vrapi_bridge" in r.patches
assert r.params("device.files")["files"]["user.cfg"].startswith("r_variable_rate_shading = 0")
assert r.status == "works"
def test_catalog_phantom_uses_alt():
from frameport.recommend import engine
r = engine.suggest(_analysis(package="com.nDreams.PhantomQuest", engine="Unreal", xr="VrApi"))
assert r.use_alt and r.alt_patches == ["patch_remove_unreal_force_quit"]
def test_32bit_through_the_bridge():
from frameport.recommend import engine
r = engine.suggest(_analysis(package="x.y.old", abis=["armeabi-v7a"]))
assert r.status != "unsupported" and "frame.a32_bridge" in r.patches and not r.overport
def test_recipe_roundtrip():
from frameport.core import library
r = Recipe("a.b", {"frame.adapter": {}}, ["patch_remove_unreal_force_quit"], True)
assert library.recipe_from_dict(library.recipe_to_dict(r)) == r
def _fixture(name):
from pathlib import Path
return (Path(__file__).with_name("fixtures") / name).read_bytes()
def test_swapchain_limit_raises_overport_guard():
from frameport.analysis import elf
from frameport.patches.frame.swapchain_limit import raise_swapchain_limit
lib = _fixture("libfakeoverport_arm64.so")
out, n = raise_swapchain_limit(lib)
assert n == 2 and len(out) == len(lib)
cmps = [o for _, m, o in elf.text_instructions(out) if m == "cmp" and "lsl #12" in o]
assert cmps and all(o.endswith("#4, lsl #12") for o in cmps) # 16384
assert raise_swapchain_limit(out) == (None, 0) # idempotent
assert elf.dyn_symbols(out, True) == elf.dyn_symbols(lib, True)
assert raise_swapchain_limit(_fixture("libfakeengine_arm64.so")) == (None, 0) # no xrCreateSwapchain
def test_ovr_microphone_guards_unopened_stream():
from frameport.analysis import elf
from frameport.patches.frame.ovr_microphone import guard_microphone
lib = _fixture("libfakemicrophone_arm64.so")
out = guard_microphone(lib)
assert out is not None and len(out) == len(lib)
ins = [(m, o) for a, m, o in elf.text_instructions(out)]
start = ins.index(("ldr", "x0, [x0, #0x18]"))
assert [m for m, _ in ins[start:start + 7]] == ["ldr", "cbz", "stp", "bl", "ldp", "sxtw", "ret"]
calls = lambda data: [o for _, m, o in elf.text_instructions(data) if m == "bl"] # noqa: E731
assert calls(out) == calls(lib) # still calls AAudioStream_getFramesPerBurst
assert guard_microphone(out) is None # idempotent
assert guard_microphone(_fixture("libfakeoverport_arm64.so")) is None # no such function
def test_ovr_microphone_suggested_only_for_microphone_games():
p = base.get("frame.ovr_microphone")
assert p.detect(_analysis(engine="Unreal", libs=["libUE4.so"], extra={"ovr_microphone": True})).recommended
assert p.detect(_analysis(engine="Unreal", libs=["libUE4.so"])) is None
assert not p.default_on
def test_swapchain_limit_suggested_for_video_players():
p = base.get("frame.swapchain_limit")
assert p.detect(_analysis(engine="Other", libs=["libavcodec4x.so", "libvr4p-oculus.so"])).recommended
assert p.default_on and p.detect(_analysis(engine="Unity", libs=["libunity.so"])).recommended
def test_vk_sanitize_routes_engine_vulkan_through_shim(tmp_path, quest_manifest):
from frameport.analysis import elf
engine = _fixture("libfakeengine_arm64.so")
apk = _apk(tmp_path, quest_manifest)
with zipfile.ZipFile(apk, "a") as z:
z.writestr("lib/arm64-v8a/libUE4.so", engine)
with ApkWorkspace(apk) as ws:
assert base.get("frame.vk_sanitize").apply(base.ApkContext(ws, _analysis(engine="Unreal"), {}, Reporter(), {}))
out = ws.write(tmp_path / "out.apk")
with zipfile.ZipFile(out) as z:
patched = z.read("lib/arm64-v8a/libUE4.so")
shim = z.read("lib/arm64-v8a/libfp_vk.so")
assert len(patched) == len(engine) and b"libfp_vk.so\0\0" in patched and b"libvulkan.so\0" not in patched
assert elf.soname(shim) == "libfp_vk.so" and "libvulkan.so" in elf.needed(shim)
assert {"vkGetInstanceProcAddr", "vkGetDeviceProcAddr", "vkCreateRenderPass2"} <= elf.dyn_symbols(shim, True)
# a game without the dlopen string is left alone
apk2 = _apk(tmp_path, quest_manifest)
with ApkWorkspace(apk2) as ws:
ctx = base.ApkContext(ws, _analysis(engine="Unreal"), {}, Reporter(), {})
assert not base.get("frame.vk_sanitize").apply(ctx)
def test_source_hints_are_generic_and_match_loosely():
from frameport.recommend.catalog import generic_source_hint, source_hint_matches
assert generic_source_hint("4XVR Video Player (Pro) v20022+2.0.22 -RLS") == "4XVR Video Player"
assert generic_source_hint("Marvels Deadpool VR (English Only) v8742+1.0.40.356975.Quest") == "Marvels Deadpool VR"
assert generic_source_hint("Batman- Arkham Shadow (Inc Lang Packs) v350961+1.4.1-350961") == "Batman- Arkham Shadow"
# other release, other name
assert source_hint_matches("Marvels Deadpool VR", "Marvel's Deadpool VR v9000+1.1 -XYZ")
assert source_hint_matches("The Climb 2 v974+2.2", "the climb 2 (quest) v1000")
assert not source_hint_matches("The Climb 2", "The Climb v100")
def test_ovrport_125_patches():
"""OVRPort 1.2.5's new patches: off by default, shown only where they can matter, mutual exclusions warned."""
from frameport.recommend import engine
quest = _analysis()
nexus = _analysis(package="com.Ubisoft.ACNexusVR", version="MAIN.450412.207706.final")
other_nexus = _analysis(package="com.Ubisoft.ACNexusVR", version="MAIN.999.1.final")
vrapi = _analysis(direct_vrapi=True, libs=["libvrapi.so"])
for pid in ("patch_vrapi_openxr", "patch_disable_meta_xr_audio_telemetry", "patch_ac_nexus_no_appsw_72",
"patch_ac_nexus_no_appsw_90"):
assert not base.get(pid).default_on
assert not base.get(pid).applies(quest)
assert base.get("patch_ac_nexus_no_appsw_90").applies(nexus) and base.get("patch_vrapi_openxr").applies(vrapi)
assert not base.get("patch_disable_meta_xr_audio_telemetry").applies(nexus) # emulators only
assert not base.get("patch_ac_nexus_no_appsw_90").applies(other_nexus) # OVRPort checks the exact build
from frameport.recommend import engine as eng
assert "patch_ac_nexus_no_appsw_90" in eng.suggest(nexus).patches # catalog default (owner, 2026-10-02)
assert "patch_ac_nexus_no_appsw_90" not in eng.suggest(other_nexus).patches # other builds would fail to patch
recipe = Recipe(package="com.Ubisoft.ACNexusVR",
patches=["patch_copy_libraries", "frame.adapter",
"patch_ac_nexus_no_appsw_72", "patch_ac_nexus_no_appsw_90"])
assert any("conflicts" in w for w in engine.warnings(recipe))
def test_overport_release_sources(monkeypatch):
from frameport.core import cache
from frameport.tools import toolchain
fork = {"tag_name": "v1.2.5", "assets": [{"name": "OVRPort-1.2.5-stable-cli.jar",
"browser_download_url": "https://example.invalid/OVRPort.jar"}]}
old = {"tag_name": "1.2.3", "assets": [{"name": "cli-jar.zip", "browser_download_url": "https://example.invalid/z"}]}
replies = {"overport-release-ovrport.json": fork, "overport-release.json": old}
monkeypatch.setattr(cache, "cached_json", lambda name, *a, **k: replies[name])
assert toolchain.latest_overport() == ("1.2.5", "https://example.invalid/OVRPort.jar")
replies["overport-release-ovrport.json"] = None # fork unreachable: the original project
assert toolchain.latest_overport() == ("1.2.3", "https://example.invalid/z")
def test_every_bundled_catalog_file_loads():
"""A YAML error silently drops a recipe from the catalog: catch it here."""
from pathlib import Path
from frameport.recommend import catalog
files = sorted((Path(__file__).resolve().parents[1] / "catalog/games").glob("*.yaml"))
loaded = {e.package for e in catalog.load(refresh=True).values() if e.origin == "bundled"}
assert {f.stem for f in files} <= loaded
def test_vr_kind_classifies_android_apps():
from frameport.analysis.detect import vr_kind
assert vr_kind({"libOVRPlugin.so", "libunity.so"}, []) == "quest"
assert vr_kind({"libopenxr_loader.so"}, ["com.oculus.supportedDevices"]) == "quest" # Meta's OpenXR build
assert vr_kind({"libopenxr_loader.so", "libunity.so"}, ["pvr.app.type"]) == "openxr" # Pico OpenXR build
assert vr_kind({"libPvr_UnitySDK.so"}, []) == "pico_sdk"
assert vr_kind({"libwvr_api.so"}, []) == "wave"
assert vr_kind(set(), ["android.software.xr.immersive"]) == "android_xr"
assert vr_kind({"libgame.so"}, ["android.intent.category.LAUNCHER"]) == "none"
def test_android_app_without_vr_gets_no_vr_translation():
from frameport.recommend import engine
flat = _analysis(package="org.example.flat", engine="Other", xr="?", libs=["libgame.so"], is_overport_output=False,
has_info_category=False,
extra={"size": 1, "vr_kind": "none"})
r = engine.suggest(flat)
assert r.as_is and not r.overport and list(r.patches) == ["device.hide_navbar"] and not engine.warnings(r)
from frameport.install.installer import install_context
assert install_context(r).env["LEPTON_GFXRECON_FP_PROPS"] == "0\nqemu.hw.mainkeys=1" # on by default
needs_launcher = _analysis(package="org.example.info", engine="Other", xr="?", libs=[], has_info_category=True,
is_overport_output=False, extra={"size": 1, "vr_kind": "none"})
r = engine.suggest(needs_launcher)
assert not r.as_is and not r.overport and sorted(r.patches) == ["device.hide_navbar", "frame.launcher"]
shown, _ = engine.visible_patches(needs_launcher, r) # VR patches can't take effect in a 2D app
assert "frame.launcher" in {p.id for p in shown} and all(not p.needs_vr for p in shown)
pico = _analysis(package="com.pico.game", libs=["libPvr_UnitySDK.so"], extra={"size": 1, "vr_kind": "pico_sdk"})
assert engine.suggest(pico).status == "unsupported"
x86 = _analysis(package="org.example.x86", abis=["x86_64"], extra={"size": 1, "vr_kind": "none"})
assert engine.suggest(x86).status == "unsupported"
quest = _analysis(libs=["libOVRPlugin.so"], extra={"size": 1}) # old library entries: no vr_kind = Quest
assert engine.suggest(quest).overport and "patch_copy_libraries" in engine.suggest(quest).patches
@pytest.mark.parametrize("info_activity,sdl_clipboard", [(False, False), (True, False), (False, True)])
def test_saved_flat_android_recipe_keeps_its_build_mode(monkeypatch, info_activity, sdl_clipboard):
"""Saving Plezy as known-good must not start an empty OVRPort conversion (#271)."""
from frameport.core import library
from frameport.recommend import catalog, engine
analysis = _analysis(package="org.example.savedflat", engine="Other", xr="?", libs=[],
has_info_category=info_activity, is_overport_output=False,
extra={"size": 1, "vr_kind": "none", "media_codec": True, "sdl_java": sdl_clipboard})
original = engine.suggest(analysis, use_catalog=False)
original.status, original.notes = "issues", "Some videos have color artifacts."
game = {"package": analysis.package, "title": "Flat app", "analysis": analysis.to_dict(),
"recipe": library.recipe_to_dict(original)}
catalog.save_user_entry(catalog.entry_from_library(game))
restored = engine.suggest(analysis)
assert restored.source == "catalog (user)"
assert not restored.overport and restored.as_is == original.as_is
assert restored.patches == original.patches
assert restored.status == "issues" and original.notes in restored.notes
assert "frame.hw_video_decode" in restored.patches and not engine.warnings(restored)
# Existing affected entries recover when the next app update re-derives their saved recipe.
broken = library.recipe_to_dict(original)
broken.update(as_is=False, overport=True, source="catalog (user)")
data = {"settings": {"recipes.app_version": "0.0.0"},
"games": {analysis.package: {**game, "recipe": broken}}}
monkeypatch.setattr(library, "REFRESH_ON_UPDATE", True)
assert library._follow_catalog(data)
repaired = library.recipe_from_dict(data["games"][analysis.package]["recipe"])
assert not repaired.overport and repaired.as_is == original.as_is
def test_saved_already_patched_android_recipe_keeps_install_as_is():
from frameport.core import library
from frameport.recommend import catalog, engine
analysis = _analysis(package="org.example.savedpatched", has_info_category=False,
extra={"size": 1, "frame_patched": True})
original = engine.suggest(analysis, use_catalog=False)
assert original.as_is
game = {"package": analysis.package, "title": "Patched app", "analysis": analysis.to_dict(),
"recipe": library.recipe_to_dict(original)}
catalog.save_user_entry(catalog.entry_from_library(game))
restored = engine.suggest(analysis)
assert restored.as_is
def test_patch_registry_is_complete_for_every_thread(monkeypatch):
"""A lookup from a second thread while the first is still importing the patch modules waits for them."""
import threading
import time
from frameport.patches import base
real = dict(base.REGISTRY)
registry: dict = {}
def slow_import(): # registers the patches one by one, like the real module imports
for pid, p in real.items():
registry[pid] = p
time.sleep(0.0005)
monkeypatch.setattr(base, "_loaded", False)
monkeypatch.setattr(base, "REGISTRY", registry)
monkeypatch.setattr(base, "_import_modules", slow_import)
found, errors = [], []
def lookup():
try:
found.append(base.get("device.hide_navbar").id)
except KeyError as exc:
errors.append(exc)
threads = [threading.Thread(target=lookup) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
assert not errors and found == ["device.hide_navbar"] * 4
def test_game_settings_dialog_shows_relevant_settings_and_saves_only_changes():
from dataclasses import asdict
from frameport.core import library
from frameport.core.models import Recipe
from frameport.patches.settings import HIDDEN, SETTINGS, UI
from frameport.ui.views import adapter_dialog as ad
# every number/switch has a label/control, except recipe-only ones
assert set(UI) == {k for k, kind, *_ in SETTINGS if kind != "str"} - HIDDEN
vulkan = _analysis()
gles = _analysis(graphics="GLES (declared in manifest)")
assert ad.relevant(vulkan, "flip_emul", 1) and not ad.relevant(gles, "flip_emul", 1) # Vulkan-only
# inverse alpha blending only matters where passthrough can be shown
mr = _analysis(extra={"size": 1, "features": {"com.oculus.feature.PASSTHROUGH": True}})
assert ad.relevant(mr, "alpha_blend_emul", 1) and not ad.relevant(vulkan, "alpha_blend_emul", 1)
assert ad.relevant(gles, "equirect_emul", 0) and not ad.relevant(vulkan, "equirect_emul", 0)
assert ad.relevant(vulkan, "equirect_emul", 1) # changed from its default: always shown
r = Recipe("com.x", patches={"adapter.scale": {"value": 1.5}, "frame.launcher": {}})
library.upsert_game("com.x", title="X", recipe=asdict(r), analysis=asdict(vulkan))
vals = ad.recipe_values(library.game("com.x"))
assert vals["scale"] == 1.5 and vals["controller_fix"] == 1 and vals["refresh_rate"] == 0.0
vals.update(scale=1.0, refresh_rate=90.0, aim_pitch=-5.0)
ad.save_to_recipe("com.x", vals)
saved = library.game("com.x")["recipe"]
adapter = {k: v for k, v in saved["patches"].items() if k.startswith("adapter.")}
assert adapter == {"adapter.refresh_rate": {"value": 90.0}, "adapter.aim_pitch": {"value": -5.0}} # no defaults
assert "frame.launcher" in saved["patches"] # other patches untouched
r = library.recipe_from_dict(saved)
r.patches["adapter.vk_shader_fix"] = {"value": "8:" + "0" * 64 + ":4:1"}
library.upsert_game("com.x", recipe=asdict(r))
ad.save_to_recipe("com.x", ad.recipe_values(library.game("com.x")))
assert library.game("com.x")["recipe"]["patches"]["adapter.vk_shader_fix"]["value"].startswith("8:") # kept
assert saved["source"] == "user" and saved["reasons"]["adapter.aim_pitch"] == "Set by you."
def test_saving_a_recipe_keeps_works_with_issues():
"""'Save as known-good' used to store every recipe as 'works', even one marked 'works with issues'."""
from dataclasses import asdict
from frameport.core.models import Recipe
from frameport.recommend.catalog import entry_from_library
def entry(status):
return {"package": "com.x", "title": "X", "analysis": {},
"recipe": asdict(Recipe("com.x", status=status, notes="Right eye distorts."))}
assert entry_from_library(entry("issues")).status == "issues"
assert entry_from_library(entry("unknown")).status == "works" # saved as known-good = it works
assert entry_from_library(entry("issues"), status="works").status == "works" # an explicit choice wins
def test_every_patch_has_a_plain_summary():
"""The game page shows patch.summary by default (plain words); Game settings (adapter.*) use their help line
from the Game settings dialog (patches/settings.UI), else patches/summaries.py."""
from frameport.patches import base
missing = [p.id for p in base.all_patches() if not p.summary]
assert not missing, f"add plain summaries to patches/summaries.py: {missing}"
def test_an_already_converted_apk_is_not_converted_again(tmp_path, monkeypatch):
"""A converted copy (e.g. from an earlier FramePort) gets only the Steam Frame patches: converting it again
replaced the platform loader and dropped its link to the Meta stand-ins (crash: cannot locate symbol ovr_...)."""
from frameport import build
from frameport.core.events import Reporter
from frameport.core.models import SourceGame
apk = tmp_path / "com.x.apk"
apk.write_bytes(b"converted")
(tmp_path / "com.x.alt-noforcequit.apk").write_bytes(b"converted alt")
used = []
monkeypatch.setattr(build.overport_tool, "patch", lambda *a, **k: (_ for _ in ()).throw(AssertionError("OVRPort")))
monkeypatch.setattr(build, "apply_frame_fixes",
lambda src, out, *a: (used.append(src.read_bytes()), out.write_bytes(b"u"), ([], [], {}))[2])
monkeypatch.setattr(build.sign, "sign", lambda src, dst, pkg: dst.write_bytes(b"signed"))
monkeypatch.setattr(build, "check_apk", lambda *a, **k: [])
analysis = _analysis(package="com.x", is_overport_output=True)
recipe = Recipe("com.x", patches={"patch_copy_libraries": {}}, alt_patches=["patch_remove_unreal_force_quit"],
use_alt=True)
src = SourceGame(name="X", apk=apk)
res = build.build(src, analysis, recipe, tmp_path / "out", Reporter())
assert used == [b"converted", b"converted alt"] # both builds without OVRPort: the alternate one is the saved copy
assert res.alt_apk.exists() and res.apk.exists()
(tmp_path / "com.x.alt-noforcequit.apk").unlink() # no saved alternate copy: OVRPort makes it (only that one)
calls = []
def fake_overport(src, work, name, ids, rep):
calls.append(ids)
(work / name).write_bytes(b"alt")
return work / name
monkeypatch.setattr(build.overport_tool, "patch", fake_overport)
used.clear()
build.build(src, analysis, recipe, tmp_path / "out", Reporter())
assert used == [b"converted", b"alt"] and len(calls) == 1 and "patch_remove_unreal_force_quit" in calls[0]
def test_stand_ins_count_only_when_the_loader_links_them(monkeypatch):
"""A libovrstubs.so that nothing loads doesn't provide anything (the check passed while the game crashed)."""
from frameport.analysis import detect
exports = {b"loader": {"ovr_Present"}, b"stubs": {"ovr_Room_GetNextRoomArrayPage"}, b"game": set()}
imports = {b"game": {"ovr_Present", "ovr_Room_GetNextRoomArrayPage"}}
linked = {b"loader": []}
monkeypatch.setattr(detect.elf, "is_elf", lambda d: True)
monkeypatch.setattr(detect.elf, "dyn_symbols", lambda d, defined: exports[d] if defined else imports.get(d, set()))
monkeypatch.setattr(detect.elf, "needed", lambda d: linked.get(d, []))
libs = {"libgame.so": b"game", "libovrplatformloader.so": b"loader", "libovrstubs.so": b"stubs"}
assert detect.missing_ovr_symbols(libs) == {"ovr_Room_GetNextRoomArrayPage"}
linked[b"loader"] = ["libovrstubs.so"]
assert detect.missing_ovr_symbols(libs) == set()
def test_start_activity_after_overport_conversion(tmp_path):
"""OVRPort gives every MAIN activity LAUNCHER + VR categories (WiiCompiled: LauncherActivity and QuestActivity),
so the VR activity must come from the original APK's analysis; only it may stay a launcher."""
from conftest import build_axml
from frameport.apk import axml
def activity(name):
out = [("start", "activity", [("name", "str", name)]), ("start", "intent-filter", []),
("start", "action", [("name", "str", axml.MAIN)]), ("end", "action")]
for c in (axml.LAUNCHER, axml.VR_CATEGORY):
out += [("start", "category", [("name", "str", c)]), ("end", "category")]
return out + [("end", "intent-filter"), ("end", "activity")]
manifest = build_axml([("start", "manifest", [("package", "str", "org.x.game")]),
*activity("org.x.game.launcher.LauncherActivity"), *activity("org.x.game.QuestActivity"),
("end", "manifest")])
assert axml.vr_activity(manifest) is None # can't be told apart any more
a = _analysis()
a.extra = {**(a.extra or {}), "vr_activity": "org.x.game.QuestActivity"}
patch = base.get("frame.start_activity")
with ApkWorkspace(_apk(tmp_path, manifest)) as ws:
ctx = base.ApkContext(ws, a, {}, Reporter(), {"frame.start_activity": {}})
assert patch.apply(ctx)
assert patch.validate(ctx) == [("Only the VR activity is a launcher", True, "org.x.game.QuestActivity")]
def test_haptic_fix_suggested_for_ovrplugin_games():
base.load_all()
p = base.REGISTRY["adapter.haptic_fix"]
s = p.detect(_analysis(libs=["libunity.so", "libOVRPlugin.so"]))
assert s and s.recommended and s.params == {"value": 1}
assert p.detect(_analysis(libs=["libunity.so"])) is None
def test_foveation_choice_sets_the_launcher_env_and_wins_over_lepton_env():
from frameport.install.installer import install_context
assert base.get("device.foveation").CHOICES[0][0] == "" # Valve's default needs no patch
r = Recipe("com.x.game", patches={"device.foveation": {"mode": "off"},
"device.lepton_env": {"env": {"VK_INSTANCE_LAYERS": "VK_LAYER_x"}}})
assert install_context(r).env["VK_INSTANCE_LAYERS"] == "" # applied last, whatever the recipe's order
r = Recipe("com.x.game", patches={"device.foveation": {"mode": "fixed"}})
assert install_context(r).env == {"ENABLE_VULKAN_FDM_INJECTION_LAYER": "1",
"FDM_DEBUG": "enable,disable_offsets"}
def test_explicit_eye_tracked_foveation_enables_injection_without_debug_spam():
from frameport.install.installer import install_context
r = Recipe("com.x.game", patches={"device.foveation": {"mode": "tracked"},
"device.lepton_env": {"env": {"FDM_DEBUG": "disable_offsets", "FOVE_LEVEL": "2"}}})
env = install_context(r).env
assert env["ENABLE_VULKAN_FDM_INJECTION_LAYER"] == "1"
assert env["FDM_DEBUG"] == "enable" # no frozen gaze or per-frame logging
assert "FOVE_LEVEL" not in env # Valve's layer ignores numbers; the size comes from the level choice (#394)
assert "VK_INSTANCE_LAYERS" not in env # keep Steam's other layers
r.patches["device.foveation"]["level"] = "med"
assert install_context(r).env["FOVE_LEVEL"] == "med"
assert install_context(Recipe("com.x.game", patches={"device.foveation": {"mode": ""}})).env == {}
def test_catalog_foveation_reaches_the_recipe(monkeypatch):
from frameport.recommend import catalog, engine
entry = catalog.CatalogEntry(package="com.drbeef.rtcwquest", title="RTCWQuest", status="works", foveation="off")
assert catalog.CatalogEntry.from_dict(yaml_load(catalog.to_yaml(entry)), "bundled").foveation == "off"
monkeypatch.setattr(catalog, "lookup", lambda package: entry)
r = engine.suggest(_analysis(package="com.drbeef.rtcwquest", engine="Other", libs=["libopenxr_loader.so"],
extra={"size": 1}))
assert r.params("device.foveation") == {"mode": "off"}
entry.foveation = "tracked"
entry.foveation_radius = 0.3
entry.foveation_test = True
loaded = catalog.CatalogEntry.from_dict(yaml_load(catalog.to_yaml(entry)), "bundled")
assert (loaded.foveation_radius, loaded.foveation_test) == (0.3, True)
r = engine.suggest(_analysis(package=entry.package, engine="Other", libs=["libopenxr_loader.so"],
extra={"size": 1}))
assert r.params("device.foveation") == {"mode": "tracked", "radius": 0.3, "test": True}
def yaml_load(text):
import yaml
return yaml.safe_load(text)
def test_vrapi_stub_keeps_every_function_of_metas_loader(tmp_path, quest_manifest):
from frameport.analysis import elf
apk = _apk(tmp_path, quest_manifest)
metas = build_stub_library(["vrapi_Initialize", "vrapi_SetPropertyInt", "vrapi_GetHmdInfo"], soname="libvrapi.so")
metas = metas.replace(b"\x00\x00\x80\xd2", b"\x20\x00\x80\xd2") # different code, same exports (mov x0, #1)
with ApkWorkspace(apk) as ws:
ws.put(ws.lib("libvrapi.so"), metas)
ws.put(ws.lib("libOVRPlugin.so"), build_stub_library(["ovrp_GetVersion"], soname="libOVRPlugin.so"))
patch = base.get("frame.vrapi_stub")
a = _analysis(libs=["libvrapi.so", "libOVRPlugin.so"], direct_vrapi=False)
assert patch.applies(a) and patch.detect(a) is None # suggested by triage only
assert not patch.applies(_analysis(libs=["libvrapi.so"], direct_vrapi=True)) # that's the bridge's case
ctx = base.ApkContext(ws, a, {}, Reporter(), {"frame.vrapi_stub": {}})
assert patch.apply(ctx)
stub = ws.read_lib("libvrapi.so")
assert elf.dyn_symbols(stub, True) == {"vrapi_Initialize", "vrapi_SetPropertyInt", "vrapi_GetHmdInfo"}
assert elf.soname(stub) == "libvrapi.so" and not patch.apply(ctx) # applying twice changes nothing
assert "frame.vrapi_bridge" in base.get("frame.vrapi_stub").conflicts
def test_vrapi_stub_covers_jurassic_world_aftermath():
"""With the real APK's libraries (FRAMEPORT_GAMES): every vrapi_* OVRPlugin imports is in the stub."""
import os
from pathlib import Path
from frameport.analysis import elf
root = os.environ.get("FRAMEPORT_GAMES")
if not root:
import pytest
pytest.skip("FRAMEPORT_GAMES not set")
apks = list(Path(root).glob("Jurassic World Aftermath*/com.coatsink.alone.apk"))
if not apks:
import pytest
pytest.skip("Jurassic World Aftermath not in FRAMEPORT_GAMES")
with zipfile.ZipFile(apks[0]) as z:
vrapi, plugin = (z.read(f"lib/arm64-v8a/{n}") for n in ("libvrapi.so", "libOVRPlugin.so"))
exports = sorted(s for s in elf.dyn_symbols(vrapi, True) if s.startswith(("vrapi_", "ovr")))
need = {s for s in elf.dyn_symbols(plugin, False) if s.startswith("vrapi_")}
assert need and need <= elf.dyn_symbols(build_stub_library(exports, soname="libvrapi.so"), True)
def test_static_check_finds_vrapi_functions_the_bridge_lacks(tmp_path, monkeypatch, quest_manifest):
from frameport.apk import sign
from frameport.validate import static
monkeypatch.setattr(sign, "verify", lambda apk: (True, ""))
monkeypatch.setattr(sign, "alignment_problems", lambda apk: [])
apk = tmp_path / "game.apk"
game = build_stub_library(["game_main"], soname="libtargemapp.so")
with zipfile.ZipFile(apk, "w") as z:
z.writestr("AndroidManifest.xml", quest_manifest)
z.writestr("lib/arm64-v8a/libvrapi.so", build_stub_library(["vrapi_Initialize"], soname="libvrapi.so"))
z.writestr("lib/arm64-v8a/libtargemapp.so", game)
monkeypatch.setattr(static.elf, "dyn_symbols", lambda data, defined: (
{"vrapi_Initialize"} if defined else {"vrapi_Initialize", "vrapi_PollEvent"}) if data == game or defined
else set())
check = next(c for c in static.check_apk(apk, expect_adapter=False) if c["name"] == "VrApi functions resolvable")
assert check["ok"] is False and check["detail"] == "vrapi_PollEvent" # BlazeRush (GitHub #57)
def test_bridge_exports_what_blazerush_imports():
from frameport.analysis import elf
from frameport.patches.frame import artifact
have = elf.dyn_symbols(artifact("arm64-v8a", "libvrapi.so"), True)
assert {"vrapi_PollEvent", "vrapi_GetSystemPropertyFloatArray", "vrapi_RecenterPose",
"vrapi_SetDisplayRefreshRate"} <= have
def test_tbxr_vendor_adds_the_valve_loader(tmp_path, quest_manifest):
from frameport.analysis import elf
apk = _apk(tmp_path, quest_manifest)
patch = base.get("frame.tbxr_vendor")
a = _analysis(libs=["libxash.so", "libopenxr_loader_meta.so"], extra={"size": 1, "tbxr_libs": ["libxash.so"]})
assert patch.applies(a) and patch.detect(a) and not patch.applies(_analysis())
with ApkWorkspace(apk) as ws:
assert patch.apply(base.ApkContext(ws, a, {}, Reporter(), {patch.id: {}})) # (libxash.so absent here)
loader = ws.read_lib("libopenxr_loader_valve.so")
assert elf.soname(loader) == "libopenxr_loader_valve.so" # loadLibrary("openxr_loader_valve") finds it
assert not patch.apply(base.ApkContext(ws, a, {}, Reporter(), {patch.id: {}}))
def test_tbxr_vendor_on_lambda1vr():
"""With the real APK (FRAMEPORT_GAMES): the six "meta" checks of libxash.so now match "valve"."""
import os
from pathlib import Path
import pytest
from frameport.analysis import elf
root = os.environ.get("FRAMEPORT_GAMES")
apks = list(Path(root).glob("Half Life 1 VR*/com.drbeef.lambda1vr.apk")) if root else []
if not apks:
pytest.skip("Lambda1VR not in FRAMEPORT_GAMES")
with zipfile.ZipFile(apks[0]) as z:
xash = z.read("lib/arm64-v8a/libxash.so")
assert b"\0OPENXR_HMD\0" in xash and b"\0meta\0" in xash
fixed, count = elf.replace_rodata_string(xash, "meta", "alve")
assert count == 1 and b"\0alve\0" in fixed and len(fixed) == len(xash)
@pytest.mark.parametrize("gl4es", [True, False])
def test_tbxr_shim_leaves_desktop_draws_to_gl4es(tmp_path, quest_manifest, gl4es):
"""#191: the same shim must preserve GL4ES dispatch without removing other games' GLES hooks."""
from frameport.analysis import elf
from frameport.patches.frame import artifact
from frameport.patches.frame.tbxr_vendor import GL4ES_DRAW_EXPORTS
original = artifact("arm64-v8a", "libglshim.so")
patch = base.get("frame.tbxr_vendor")
a = _analysis(libs=["libxash.so"], extra={"tbxr_libs": ["libxash.so"]})
with ApkWorkspace(_apk(tmp_path, quest_manifest)) as ws:
ws.put(ws.lib("libglshim.so"), original)
if gl4es:
ws.put(ws.lib("libgl4es.so"), build_stub_library(["glDrawElements"], soname="libgl4es.so"))
ctx = base.ApkContext(ws, a, {}, Reporter(), {patch.id: {}})
if gl4es:
assert patch.validate(ctx)[0][1] is False
assert patch.apply(ctx)
shim = ws.read_lib("libglshim.so")
_, visible = elf.hide_exports(shim, GL4ES_DRAW_EXPORTS)
assert bool(visible) is not gl4es
if gl4es:
assert patch.validate(ctx)[0][1] is True
# Functions remain callable inside the shim; its GL queries and framebuffer fix stay intact.
assert elf.find_symbols(shim, GL4ES_DRAW_EXPORTS) == elf.find_symbols(original, GL4ES_DRAW_EXPORTS)
assert elf.find_symbols(shim, ["eglGetProcAddress", "glGetString", "glGetIntegerv"]) == \
elf.find_symbols(original, ["eglGetProcAddress", "glGetString", "glGetIntegerv"])
else:
assert shim == original
assert not patch.apply(ctx)
def test_unity_user_presence_points_the_xr_plugin_at_the_shim(tmp_path, quest_manifest):
"""BONELAB: OVRPlugin reported the worn headset as not worn and the Marrow rig froze (no tracking, no controls)."""
from frameport.analysis import elf
from frameport.patches.frame import unity_user_presence as P
fake = _fixture("libfakeovrplugin_arm64.so")
xr_plugin, n = elf.replace_rodata_string(fake, "xrGetInstanceProcAddr", P.CALL) # a stand-in with the lookup name
assert n == 1
apk = _apk(tmp_path, quest_manifest)
with zipfile.ZipFile(apk, "a") as z:
z.writestr("lib/arm64-v8a/libOculusXRPlugin.so", xr_plugin)
z.writestr("lib/arm64-v8a/libOVRPlugin.so", fake)
p = base.get("frame.unity_user_presence")
a = _analysis(engine="Unity", libs=["libunity.so", "libOVRPlugin.so", "libOculusXRPlugin.so"])
assert p.applies(a) and p.detect(a) is None # per game, from the catalog
assert not p.applies(_analysis(engine="Unity", libs=["libunity.so", "libOVRPlugin.so"]))
with ApkWorkspace(apk) as ws:
ctx = base.ApkContext(ws, a, {}, Reporter(), {})
assert p.apply(ctx)
assert p.validate(ctx) == [("User presence shim loaded", True, P.SHIM)]
out = ws.write(tmp_path / "out.apk")
with zipfile.ZipFile(out) as z:
patched = z.read("lib/arm64-v8a/libOculusXRPlugin.so")
assert len(patched) == len(xr_plugin) and P.SHIM_CALL.encode() + b"\0" in patched
assert P.SHIM in elf.needed(z.read("lib/arm64-v8a/libOVRPlugin.so"))
shim = z.read(f"lib/arm64-v8a/{P.SHIM}")
assert P.SHIM_CALL in elf.dyn_symbols(shim, True) # the rebuilt artifact exports the wrapper
def test_microphone_fix_is_not_suggested_for_unitys_platform_wrapper(quest_manifest, tmp_path):
"""Unity's C# platform wrapper (libil2cpp.so) names every Meta function, used or not: no reason to patch."""
from frameport.analysis.detect import analyze
def apk(lib):
p = tmp_path / f"{lib}.apk"
with zipfile.ZipFile(p, "w") as z:
z.writestr("AndroidManifest.xml", quest_manifest)
z.writestr(f"lib/arm64-v8a/{lib}", b"\x7fELF\0ovr_Microphone_GetOutputBufferMaxSize\0")
return p
assert not analyze(apk("libil2cpp.so")).extra["ovr_microphone"]
assert analyze(apk("libUE4.so")).extra["ovr_microphone"] # Unreal's voice code calls it (TWD Ch. 2)
def test_to_string_stubs_return_an_empty_string():
"""ovrPeerConnectionState_ToString & co. (BlazeRush): OVRPort's loader lacks them; NULL could crash the caller."""
import struct
from frameport.analysis import detect, elf
lib = build_stub_library(["ovr_Foo", "ovrPeerConnectionState_ToString"], soname="libovrstubs.so")
syms = elf._elf(lib).get_section_by_name(".dynsym")
at = syms.get_symbol_by_name("ovrPeerConnectionState_ToString")[0]["st_value"]
word = struct.unpack_from("<I", lib, at)[0]
imm = ((word >> 29) & 3) | ((word >> 5) & 0x7FFFF) << 2
imm -= (1 << 21) if imm & (1 << 20) else 0
assert word & 0x9F00001F == 0x10000000 and lib[at + imm] == 0 # adr x0, <a zero byte>
foo = syms.get_symbol_by_name("ovr_Foo")[0]["st_value"]
assert struct.unpack_from("<I", lib, foo)[0] == 0xD2800000 # everything else still returns 0
assert detect.OVR_TO_STRING.match("ovrVoipMuteState_ToString")
assert not detect.OVR_TO_STRING.match("ovrAvatar_Create")
def test_avatar_stub_keeps_the_loaders_functions(tmp_path, quest_manifest):
from frameport.analysis import elf
apk = _apk(tmp_path, quest_manifest)
with ApkWorkspace(apk) as ws:
real = build_stub_library(["ovrAvatar_InitializeAndroid", "ovrAvatarMessage_Pop"],
soname="libovravatarloader.so")
ws.put(ws.lib("libovravatarloader.so"), real.replace(b"\x00\x00\x80\xd2", b"\x20\x00\x80\xd2"))
p = base.get("frame.avatar_stub")
a = _analysis(libs=["libovravatarloader.so"])
assert p.applies(a) and p.detect(a) is None
assert p.apply(base.ApkContext(ws, a, {}, Reporter(), {p.id: {}}))
stub = ws.read_lib("libovravatarloader.so")
assert elf.dyn_symbols(stub, True) == {"ovrAvatar_InitializeAndroid", "ovrAvatarMessage_Pop"}
def test_unreal_gl_shim_offered_for_unreal_gles_only():
"""Star Wars Pinball VR (GitHub #83): Zink crashes Unreal's MSRTT path; the shim is an option, not a suggestion."""
shim = base.get("frame.unreal_gl_shim")
gles = _analysis(engine="Unreal", graphics="GLES or unknown (no Vulkan declaration)")
assert shim.applies(gles) and shim.detect(gles) is None
assert not shim.applies(_analysis(engine="Unreal")) # Vulkan
assert not shim.applies(_analysis(engine="Unity", graphics="GLES (Unity boot.config)"))
assert not shim.applies(gles.__class__(**{**gles.__dict__, "abis": ["armeabi-v7a"]}))
def test_gl_shim_keeps_unreal_multiview_msrtt():
"""Unreal 4.25 enables multiview only with GL_OVR_multiview_multisampled_render_to_texture listed: the shim keeps it
for Unreal and draws its function single-sampled (the prebuilt library must be rebuilt from the current source)."""
from frameport.patches.frame import artifact
shim = artifact("arm64-v8a", "libglshim.so")
assert b"multiview multisampled render-to-texture (%d samples) drawn single-sampled" in shim
assert b"glFramebufferTextureMultiviewOVR\0" in shim
def test_slz_vulkan_hooks_switches_off_both_registrations(tmp_path, quest_manifest):
"""BONELAB 1.2974: SLZ's graphics plugin crashed Unity's Vulkan start-up; only its two registrations go."""
import struct
from frameport.patches.frame import slz_vulkan_hooks as S
lib = _fixture("libfakeslz_arm64.so")
sites = S.find_registrations(lib)
assert len(sites) == 2
apk = _apk(tmp_path, quest_manifest)
with zipfile.ZipFile(apk, "a") as z:
z.writestr(f"lib/arm64-v8a/{S.LIB}", lib)
p = base.get("frame.slz_vulkan_hooks")
a = _analysis(engine="Unity", libs=["libunity.so", S.LIB])
assert p.applies(a) and p.detect(a).recommended
assert not p.applies(_analysis(engine="Unity", libs=["libunity.so"]))
with ApkWorkspace(apk) as ws:
ctx = base.ApkContext(ws, a, {}, Reporter(), {})
assert p.apply(ctx)
assert p.validate(ctx) == [("SLZ Vulkan hooks off", True, "")]
assert not p.apply(ctx) # nothing left to switch off
out = ws.write(tmp_path / "out.apk")
with zipfile.ZipFile(out) as z:
patched = z.read(f"lib/arm64-v8a/{S.LIB}")
changed = [i for i in range(0, len(lib), 4) if lib[i:i + 4] != patched[i:i + 4]]
assert changed == sites and all(struct.unpack_from("<I", patched, o)[0] == S.NOP for o in sites)
def test_bonelab_recipe_serves_both_builds():
"""One catalog entry for BONELAB 1.2068 (no SLZ plugin: the Vulkan-hook patch changes nothing) and 1.2974."""
from pathlib import Path
import yaml
entry = yaml.safe_load((Path(__file__).resolve().parents[1] / "catalog/games/com.StressLevelZero.BONELAB.yaml")
.read_text(encoding="utf-8"))
assert {"frame.unity_user_presence", "frame.slz_vulkan_hooks"} <= set(entry["frame"])
hooks, presence = base.get("frame.slz_vulkan_hooks"), base.get("frame.unity_user_presence")
libs = ["libunity.so", "libOVRPlugin.so", "libOculusXRPlugin.so"]
old, new = _analysis(engine="Unity", libs=libs), _analysis(engine="Unity", libs=libs + ["libSLZQuestNative.so"])
assert presence.applies(old) and presence.applies(new)
assert not hooks.applies(old) and hooks.applies(new)
def test_ovr_stable_user_pins_the_made_up_user_id():
"""GitHub #182: OVRPort makes up a new user id at every start (6e18 + random()); games lose saves filed under it."""
from frameport.analysis import elf
from frameport.patches.frame.ovr_stable_user import BASE_ID, find_helper, pin_user_id, stable_id
lib = _fixture("libfakestableuser_arm64.so")
assert find_helper(lib) is not None
uid = stable_id("com.example.game", b"keystore")
assert BASE_ID <= uid < BASE_ID + 2**32
assert uid == stable_id("com.example.game", b"keystore") != stable_id("com.example.other", b"keystore")
assert uid != stable_id("com.example.game", b"another user's keystore")
out = pin_user_id(lib, uid)
assert out is not None and len(out) == len(lib)
ins = [(m, o) for _, m, o in elf.text_instructions(out)]
start = next(i for i, (m, o) in enumerate(ins) if m == "mov" and o.startswith("x0,") and ins[i + 1][0] == "movk")
assert [m for m, _ in ins[start:start + 5]] == ["mov", "movk", "movk", "movk", "ret"]
value = 0
for i, (_, o) in enumerate(ins[start:start + 4]):
imm = int(o.split("#")[1].split(",")[0], 0)
value |= imm << (16 * i)
assert value == uid
assert not any(m == "bl" for m, _ in ins[start:start + 5]) # random() isn't called any more
assert pin_user_id(out, uid) is None # idempotent
assert pin_user_id(_fixture("libfakemicrophone_arm64.so"), uid) is None # not OVRPort's helper
def test_ovr_stable_user_suggested_for_platform_games():
p = base.get("frame.ovr_stable_user")
assert p.detect(_analysis(engine="Unity", libs=["libovrplatformloader.so", "libunity.so"])).recommended
assert p.detect(_analysis(engine="Unity", libs=["libunity.so"])) is None
def test_hide_fb_foveation_setting():
from frameport.patches.frame.adapter import needs_xrshim
from frameport.patches.settings import UI, XRSHIM_SETTINGS
patch = base.get("adapter.hide_fb_foveation")
assert patch.applies(_analysis())
assert not patch.applies(_analysis(graphics="OpenGL ES"))
assert patch.detect(_analysis()) is None
assert "hide_fb_foveation" not in adapter_settings({})
recipe = {patch.id: {"value": 1}}
assert adapter_settings(recipe)["hide_fb_foveation"] == 1
assert "hide_fb_foveation" in XRSHIM_SETTINGS
assert needs_xrshim(recipe)
assert UI["hide_fb_foveation"]["level"] == "advanced"
def test_swapchain_pool_is_opt_in_and_gles_only():
patch = base.get("adapter.swapchain_pool")
assert patch.default == 0 and patch.revision == 1
assert "swapchain_pool" not in adapter_settings({})
assert adapter_settings({"adapter.swapchain_pool": {"value": "1"}})["swapchain_pool"] == 1
gles = _analysis(graphics="GLES or unknown (no Vulkan declaration)")
assert patch.applies(gles) and patch.detect(gles) is None
assert not patch.applies(_analysis(graphics="Vulkan"))
assert "Vulkan" in patch.description