Files
spoopyghosty0--frameport/tests/test_rift.py
T

673 lines
32 KiB
Python

"""Oculus Rift (PC VR) support: PE detection, scanning, recipes, Revive unpacking, triage."""
import json
import struct
import zlib
from pathlib import Path
import pytest
from frameport.analysis import rift
from frameport.core import library
from frameport.recommend import engine
from frameport.sources import rift_dump
def make_pe(machine=0x8664, imports=(), extra=b"", subsystem=2, delay=()) -> bytes:
"""A minimal PE (one section holding the import directory + name strings) — enough for rift.read_pe."""
pe32plus = machine == 0x8664
opt_size = 240 if pe32plus else 224
sect_rva, sect_raw = 0x1000, 0x400
body = bytearray()
desc_size = 20 * (len(imports) + 1)
names = bytearray()
descs = bytearray()
for name in imports:
descs += struct.pack("<IIIII", 0, 0, 0, sect_rva + desc_size + len(names), 0)
names += name.encode() + b"\0"
descs += b"\0" * 20
delay_off = desc_size + len(names) + sum(len(n) + 1 for n in delay)
delay_descs = bytearray()
for name in delay: # ImgDelayDescr: attributes, then the DLL name RVA
delay_descs += struct.pack("<II", 1, sect_rva + desc_size + len(names)) + b"\0" * 24
names += name.encode() + b"\0"
if delay:
delay_descs += b"\0" * 32
body += descs + names + delay_descs + extra
hdr = bytearray(b"MZ" + b"\0" * 0x3A + struct.pack("<I", 0x80))
hdr += b"\0" * (0x80 - len(hdr))
hdr += b"PE\0\0" + struct.pack("<HHIIIHH", machine, 1, 0, 0, 0, opt_size, 0x22)
opt = bytearray(opt_size)
struct.pack_into("<H", opt, 0, 0x20B if pe32plus else 0x10B)
struct.pack_into("<H", opt, 68, subsystem) # 2 = Windows GUI (games), 3 = console
dd = 112 if pe32plus else 96
struct.pack_into("<II", opt, dd + 8, sect_rva if imports else 0, desc_size)
if delay:
struct.pack_into("<II", opt, dd + 13 * 8, sect_rva + delay_off, len(delay_descs))
hdr += opt
hdr += b".idata\0\0" + struct.pack("<IIII", len(body), sect_rva, len(body), sect_raw) + b"\0" * 16
hdr += b"\0" * (sect_raw - len(hdr))
return bytes(hdr + body)
def unity_game(root: Path, name="Space Game", imports=("d3d11.dll", "kernel32.dll"), platform=False, extra=b"") -> Path:
g = root / name
(g / f"{name}_Data" / "Plugins").mkdir(parents=True)
(g / f"{name}.exe").write_bytes(make_pe(imports=("kernel32.dll",)))
(g / "UnityPlayer.dll").write_bytes(make_pe(imports=imports, extra=extra))
(g / "UnityCrashHandler64.exe").write_bytes(make_pe())
(g / f"{name}_Data" / "Plugins" / "OVRPlugin.dll").write_bytes(make_pe(extra=b"ovr_Initialize"))
if platform:
(g / f"{name}_Data" / "Plugins" / "LibOVRPlatform64_1.dll").write_bytes(make_pe())
return g
def test_read_pe_machine_and_imports(tmp_path):
p = tmp_path / "a.exe"
p.write_bytes(make_pe(0x14C, ("d3d11.dll", "LibOVRPlatform32_1.dll")))
info, _ = rift.read_pe(p)
assert info.machine == "x86"
assert info.imports == ["d3d11.dll", "libovrplatform32_1.dll"]
def test_unity_rift_game(tmp_path):
g = unity_game(tmp_path)
a = rift.analyze(g)
assert a.package == "rift.space_game"
assert (a.engine, a.xr, a.graphics, a.abis) == ("Unity", "LibOVR", "D3D11", ["x86_64"])
assert a.extra["exe"] == "Space Game.exe" and a.extra["kind"] == "rift"
assert not a.extra["platform_sdk"] and a.extra["needs_revive"]
def test_platform_sdk_and_openxr_native(tmp_path):
a = rift.analyze(unity_game(tmp_path, "Owned", platform=True))
assert a.extra["platform_sdk"]
g = tmp_path / "XR"
g.mkdir()
(g / "XR.exe").write_bytes(make_pe(imports=("openxr_loader.dll", "d3d12.dll")))
b = rift.analyze(g)
assert b.xr == "OpenXR" and b.graphics == "D3D12" and b.extra["openxr_native"]
def test_unreal_layout_picks_shipping_exe(tmp_path):
g = tmp_path / "Climb Game"
(g / "Climb" / "Binaries" / "Win64").mkdir(parents=True)
(g / "Engine").mkdir()
(g / "Climb.exe").write_bytes(make_pe())
(g / "Climb" / "Binaries" / "Win64" / "Climb-Win64-Shipping.exe").write_bytes(
make_pe(imports=("d3d11.dll",), extra=b"LibOVRRT%hs_%d.dll" + b"\0" * 4096))
a = rift.analyze(g)
assert a.engine == "Unreal" and a.xr == "LibOVR"
assert a.extra["exe"] == "Climb/Binaries/Win64/Climb-Win64-Shipping.exe"
r = engine.suggest(a) # Oculus/LibOVR Unreal: Revive on (PC OpenVR backend), files unchanged, crash reporter off
assert r.as_is and set(r.patches) == {"pcvr.revive", "pcvr.revive_openvr", "pcvr.libovr_redirect",
"pcvr.steamvr_tuning", "pcvr.no_crash_reporter", "pcvr.oculus_unreal",
"pcvr.xr_timefix"}
def test_scan_finds_rift_games_not_parents_or_quest(tmp_path):
unity_game(tmp_path / "dumps")
q = tmp_path / "dumps" / "Quest Game"
q.mkdir()
(q / "game.apk").write_bytes(b"PK")
(q / "setup.exe").write_bytes(make_pe())
found = rift_dump.scan(tmp_path / "dumps")
assert [p.name for p in found] == ["Space Game"]
assert [p.name for p in rift_dump.scan(tmp_path)] == ["Space Game"] # dumps/Space Game: 2 levels down
assert rift_dump.scan(tmp_path, depth=1) == []
def test_recipe_only_pcvr_patches(tmp_path):
a = rift.analyze(unity_game(tmp_path))
r = engine.suggest(a)
assert r.as_is and "pcvr.revive" in r.patches # Oculus game: Revive gives it VR
assert all(pid.startswith("pcvr.") for pid in r.patches), r.patches
shown, hidden = engine.visible_patches(a, r)
assert shown and all(p.category == "pcvr" for p in shown + hidden)
assert engine.warnings(r) == []
def test_quest_games_never_see_pcvr_patches(quest_manifest, tmp_path):
import zipfile
from frameport.analysis.detect import analyze
apk = tmp_path / "q.apk"
with zipfile.ZipFile(apk, "w") as z:
z.writestr("AndroidManifest.xml", quest_manifest)
z.writestr("lib/arm64-v8a/libunity.so", b"\x7fELF")
a = analyze(apk, deep=False)
r = engine.suggest(a)
shown, hidden = engine.visible_patches(a, r)
assert not any(p.category == "pcvr" for p in shown + hidden)
assert not any(pid.startswith("pcvr.") for pid in r.patches)
def test_platform_sdk_note(tmp_path):
r = engine.suggest(rift.analyze(unity_game(tmp_path, "Owned", platform=True)))
assert "Oculus app" in r.notes
def test_pipeline_add_path_and_prepare(tmp_path, monkeypatch):
from frameport import pipeline
from frameport.core.events import Reporter
unity_game(tmp_path / "dumps")
added = pipeline.add_path(tmp_path / "dumps")
assert [g["package"] for g in added] == ["rift.space_game"]
g = library.game("rift.space_game")
assert g["kind"] == "rift" and g["exe"] == "Space Game.exe"
monkeypatch.setenv("FRAMEPORT_REVIVE_DIR", str(fake_revive(tmp_path / "revive")))
monkeypatch.setattr("frameport.artwork.fetch.cache.http_get", lambda *a, **k: (_ for _ in ()).throw(OSError()))
info = pipeline.build_game("rift.space_game", Reporter())
assert info["ok"] and info["sha256"] and info["kind"] == "rift"
def test_launch_env():
from frameport.core.models import Recipe
from frameport.patches.pcvr import launch_env
r = Recipe("rift.x", {"pcvr.proton_log": {}, "pcvr.proton_env": {"env": "DXVK_HUD=fps\n\nA = b"}})
assert launch_env(r) == {"PROTON_LOG": "1", "DXVK_HUD": "fps", "A": "b"}
# ------------------------------------------------------------------------------------------ Revive / NSIS
def fake_revive(d: Path) -> Path:
from frameport.tools import revive
d.mkdir(parents=True, exist_ok=True)
for n in revive.RUNTIME_FILES:
(d / n).write_bytes(b"x")
return d
def make_nsis(files: dict[str, bytes], outdir_var=21) -> bytes:
"""A tiny Unicode NSIS 3 installer: SetOutPath $INSTDIR[\\sub] + File entries, non-solid deflate."""
strings = bytearray(b"\0\0")
def add(s, var=None):
off = len(strings) // 2
if var is not None:
strings.extend(struct.pack("<HH", 3, (var & 0x7F) | ((var & 0x3F80) << 1) | 0x8080))
strings.extend(s.encode("utf-16-le") + b"\0\0")
return off
data = bytearray()
entries = []
def block(raw):
comp = zlib.compressobj(9, zlib.DEFLATED, -15)
c = comp.compress(raw) + comp.flush()
off = len(data)
data.extend(struct.pack("<I", len(c) | 0x80000000) + c)
return off
dirs = {}
for path, content in files.items():
folder, _, name = path.rpartition("/")
if folder not in dirs:
dirs[folder] = add("\\" + folder.replace("/", "\\") if folder else "", outdir_var)
entries.append((11, dirs[folder], 0, 0, 0, 0, 0))
entries.append((20, 0, add(name), block(content), 0, 0, 0))
entries.append((11, add("", 26), 0, 0, 0, 0, 0)) # $PLUGINSDIR: ignored
entries.append((20, 0, add("plugin.dll"), block(b"p"), 0, 0, 0))
ent = b"".join(struct.pack("<7I", *e) for e in entries)
head_len = 4 + 8 * 8
ent_off, str_off = head_len, head_len + len(ent)
lang_off = str_off + len(strings)
header = struct.pack("<I", 0) + struct.pack("<16I", 0, 0, 0, 0, ent_off, len(entries), str_off, 0, lang_off, 0,
lang_off, 0, 0, 0, 0, 0) + ent + bytes(strings)
comp = zlib.compressobj(9, zlib.DEFLATED, -15)
ch = comp.compress(header) + comp.flush()
body = struct.pack("<I", len(ch) | 0x80000000) + ch + data
first = struct.pack("<I", 0) + b"\xef\xbe\xad\xdeNullsoftInst" + struct.pack("<II", len(header), len(body) + 28)
return b"MZ" + b"\0" * 510 + first + body
def test_nsis_extract(tmp_path):
from frameport.tools import revive
files = {n: n.encode() for n in revive.RUNTIME_FILES}
files["Input/oculus_touch_default.json"] = b"{}"
files["Qt5Core.dll"] = b"qt"
inst = tmp_path / "ReviveInstaller.exe"
inst.write_bytes(make_nsis(files))
written = revive.extract(inst, tmp_path / "out")
assert set(written) == set(revive.RUNTIME_FILES) | {"Input/oculus_touch_default.json"}
assert (tmp_path / "out" / "ReviveInjector.exe").read_bytes() == b"ReviveInjector.exe"
assert not (tmp_path / "out" / "plugin.dll").exists()
assert [p.name for p in revive.runtime_files(tmp_path / "out")][:1] == ["oculus_touch_default.json"]
def test_nsis_missing_runtime_files(tmp_path):
from frameport.tools import revive
inst = tmp_path / "i.exe"
inst.write_bytes(make_nsis({"LICENSE": b"l"}))
with pytest.raises(revive.NsisError):
revive.extract(inst, tmp_path / "out")
@pytest.mark.skipif(not Path(__file__).with_name("fixtures").joinpath("ReviveInstaller.exe").exists(),
reason="real installer not present")
def test_real_revive_installer(tmp_path): # pragma: no cover - optional
from frameport.tools import revive
written = revive.extract(Path(__file__).with_name("fixtures") / "ReviveInstaller.exe", tmp_path)
assert "LibReviveXR64.dll" in written
# ------------------------------------------------------------------------------------------ triage
def test_pcvr_triage_uses_pcvr_signatures_only():
from frameport.validate.triage import triage
log = ("FramePort: launching rift.x with proton_11-arm64\nLaunched injector with: x\nSuccesfully injected!\n"
"ovrPlatformInitialize_NotEntitled\n")
r = triage(log, "EXITED", "rift.x")
assert r.milestone == "Revive injected into the game"
assert [f.id for f in r.findings] == ["oculus-entitlement"]
q = triage("Failed to create process\nFrameBridge: scale=1", "RUNNING", "com.x.y")
assert "revive-inject-failed" not in [f.id for f in q.findings]
def test_system_revive_preferred(tmp_path, monkeypatch):
"""A Revive the user installed (official installer) wins over FramePort's copy and is never replaced."""
from frameport.core import winhost
from frameport.tools import revive, toolchain
managed = fake_revive(tmp_path / "managed")
revive._state_file().write_text(json.dumps({"version": "3.2.0", "path": managed.as_posix()}))
system = fake_revive(tmp_path / "Program Files" / "Revive")
monkeypatch.delenv("FRAMEPORT_REVIVE_DIR", raising=False)
monkeypatch.setattr(winhost, "available", lambda: True)
monkeypatch.setattr(winhost, "is_windows", lambda: False)
monkeypatch.setattr(winhost, "to_local", lambda p: system if "Program Files" in p else tmp_path / "nope")
monkeypatch.setattr(winhost, "reg_query", lambda key, value: None)
monkeypatch.setattr(revive, "_system_cache", None)
monkeypatch.setattr(revive, "release_for", lambda p: "3.2.0")
assert revive.source() == "system" and revive.revive_dir() == system
assert revive.install() == system # no download when the user has Revive
assert revive.installed_version() == "3.2.0"
assert toolchain.revive_is_users()
# not in the default folder: found through the registry (HKLM\Software\Revive default value)
other = fake_revive(tmp_path / "D" / "Tools" / "Revive")
monkeypatch.setattr(winhost, "to_local", lambda p: other if p.startswith("D:") else tmp_path / "nope")
monkeypatch.setattr(winhost, "reg_query",
lambda key, value: "D:\\Tools\\Revive" if key.startswith("HKLM") else None)
monkeypatch.setattr(revive, "_system_cache", None)
assert revive.revive_dir() == other
# nothing installed on the system: FramePort's copy
monkeypatch.setattr(winhost, "reg_query", lambda key, value: None)
monkeypatch.setattr(revive, "_system_cache", None)
assert revive.source() == "managed" and revive.revive_dir() == managed
def test_release_for_matches_build_date(tmp_path, monkeypatch):
import os
from frameport.tools import revive
d = fake_revive(tmp_path / "rv")
os.utime(d / "LibReviveXR64.dll", (1679184000, 1679184000)) # 2023-03-19
rels = [{"tag_name": "3.2.0", "published_at": "2023-03-20T01:56:30Z"},
{"tag_name": "3.1.2", "published_at": "2022-10-01T00:00:00Z"},
{"tag_name": "3.3.0", "published_at": "2024-01-01T00:00:00Z"}]
monkeypatch.setattr(revive.cache, "cached_json", lambda *a, **k: rels)
assert revive.release_for(d) == "3.2.0"
# ------------------------------------------------------------------------------------------ nested collections
def collection_tree(root: Path) -> Path:
"""A collection folder: one folder per game, the game nested inside, installers around it."""
col = root / "PCVR"
# Unity game one level down, with an uninstaller and a top-level Setup.exe next to archive parts
a = col / "Space Game v1.6.0 -GRP"
(a / "Space Game" / "Space Game_Data").mkdir(parents=True)
(a / "Setup.exe").write_bytes(make_pe())
(a / "Space Game v1.6.0 -GRP.7z.001").write_bytes(b"7z")
(a / "Space Game" / "Space Game.exe").write_bytes(make_pe(extra=b"ovr_Initialize"))
(a / "Space Game" / "UnityPlayer.dll").write_bytes(make_pe(imports=("d3d11.dll",)))
(a / "Space Game" / "_UnInstall").mkdir()
(a / "Space Game" / "_UnInstall" / "unins000.exe").write_bytes(make_pe())
# Unreal: bootstrap exe + the shipping build that really runs (UTF-16 marker, as Unreal stores strings)
b = col / "Wrath Game v008 [XYZ Repacks]" / "Wrath" / "WindowsNoEditor"
(b / "WrathGame" / "Binaries" / "Win64").mkdir(parents=True)
(b / "WrathGame.exe").write_bytes(make_pe())
(b / "WrathGame" / "Binaries" / "Win64" / "WrathGame-Win64-Shipping.exe").write_bytes(
make_pe(imports=("d3d11.dll",), extra="LibOVRRT%hs_%d.dll".encode("utf-16-le")))
(b.parent / "Engine" / "Binaries" / "Win64").mkdir(parents=True)
(b.parent / "Engine" / "Binaries" / "Win64" / "CrashReportClient.exe").write_bytes(make_pe())
# two builds of the same game: ambiguous
c = col / "The Climber v1.5.0.16 -GRP" / "The Climber" / "bin"
for build in ("win_x64", "win_x64-steam"):
(c / build).mkdir(parents=True)
(c / build / "Climber.exe").write_bytes(make_pe(extra=b"LibOVRRT64_1.dll"))
# not games: a console tool and a Quest dump
tool = col / "Some Tool"
tool.mkdir()
(tool / "tool.exe").write_bytes(make_pe(subsystem=3))
q = col / "Quest Game -RLS"
q.mkdir()
(q / "game.apk").write_bytes(b"PK")
return col
def test_scan_collection_finds_nested_games(tmp_path):
col = collection_tree(tmp_path)
games = rift_dump.scan(col)
assert sorted(p.name for p in games) == ["Space Game v1.6.0 -GRP", "The Climber v1.5.0.16 -GRP",
"Wrath Game v008 [XYZ Repacks]"]
assert [p.name for p in rift_dump.scan(tmp_path)] == [p.name for p in games] # a level above works too
def test_ranking_and_ambiguity(tmp_path):
col = collection_tree(tmp_path)
a = rift.analyze(col / "Space Game v1.6.0 -GRP")
assert a.extra["exe"] == "Space Game/Space Game.exe" and a.extra["exe_confirmed"] and a.label == "Space Game"
w = rift.analyze(col / "Wrath Game v008 [XYZ Repacks]")
assert w.extra["exe"].endswith("WrathGame-Win64-Shipping.exe") and w.xr == "LibOVR" and w.engine == "Unreal"
assert "Unreal launcher (starts the real game build)" in w.extra["exe_candidates"][1]["reasons"]
c = rift.analyze(col / "The Climber v1.5.0.16 -GRP")
assert c.extra["exe_confirmed"] is False and c.extra["exe"] == "The Climber/bin/win_x64/Climber.exe"
assert "Steam build" in c.extra["exe_candidates"][1]["reasons"]
assert c.label == "The Climber" and c.package == "rift.the_climber"
@pytest.mark.parametrize("name,title", [
("Asgards Wrath v1.6.0 -GRP", "Asgards Wrath"),
("Arktika 1 (v1.0.0.7) -RLS", "Arktika 1"),
("Vader Immortal - Episode II v2.0.2+236948 Shipping", "Vader Immortal - Episode II"),
("Vader Immortal - Episode I v1.1.0+236956 Shipping -[ABC Repacks]", "Vader Immortal - Episode I"),
("Vader Immortal - Episode III v3.0.2+236944 [XYZ Repacks]", "Vader Immortal - Episode III"),
("Lies Beneath 4.15.20 -GRP", "Lies Beneath"),
("Defector v21.11.08.358012 -GRP", "Defector"),
("Lone Echo 2", "Lone Echo 2"),
("Rick and Morty - Virtual Rick-ality v2288226 -GRP", "Rick and Morty - Virtual Rick-ality"),
("Rick and Morty - Virtual Rick-ality", "Rick and Morty - Virtual Rick-ality"), # no tag: nothing cut
("Half-Life Alyx -GRP v12", "Half-Life Alyx"),
("Some Game (Oculus Release) -GRP", "Some Game"),
])
def test_clean_title(name, title):
assert rift.clean_title(name) == title
def test_set_exe_and_rescan_keep_choice(tmp_path):
from frameport import pipeline
col = collection_tree(tmp_path)
added = {g["title"]: g for g in pipeline.add_path(col)}
assert set(added) == {"Space Game", "Wrath Game", "The Climber"} # the fake Quest dump isn't a real APK
climber = added["The Climber"]
assert climber["exe_confirmed"] is False
pipeline.set_exe(climber["package"], "The Climber/bin/win_x64-steam/Climber.exe")
g = library.game(climber["package"])
assert g["exe_confirmed"] is True and g["exe"].endswith("win_x64-steam/Climber.exe")
library.upsert_game(g["package"], tags=["Mine"])
pipeline.add_path(col) # rescan: the choice and the tags stay
g = library.game(climber["package"])
assert g["exe"].endswith("win_x64-steam/Climber.exe") and g["tags"] == ["Mine"]
def test_unchanged_folder_not_reanalyzed(tmp_path, monkeypatch):
from frameport import pipeline
col = collection_tree(tmp_path)
pipeline.add_path(col)
calls = []
real = rift.analyze
monkeypatch.setattr(rift, "analyze", lambda *a, **k: calls.append(a) or real(*a, **k))
pipeline.add_path(col)
assert [c for c in calls if "Space Game" in str(c[0])] == [] # skipped via the fingerprint
def test_bundled_revive_dll_still_uses_framework_revive(tmp_path):
"""Repacks carry a LibRevive64.dll for their own launcher; it isn't loaded by itself, so Revive stays on."""
g = unity_game(tmp_path, "Repacked Game")
(g / "LibRevive64.dll").write_bytes(make_pe())
a = rift.analyze(g)
assert a.extra["revive_bundled"]
r = engine.suggest(a)
assert r.as_is and "pcvr.revive" in r.patches # don't reuse the repack's DLL; Revive provides the runtime
def test_quest_as_is_skips_patching(quest_manifest, tmp_path, monkeypatch):
import zipfile
from frameport import pipeline
from frameport.core.events import Reporter
apk = tmp_path / "patched.apk"
with zipfile.ZipFile(apk, "w") as z:
z.writestr("AndroidManifest.xml", quest_manifest)
z.writestr("lib/arm64-v8a/libframe_settings.so", b"\x7fELF")
monkeypatch.setattr("frameport.artwork.fetch.cache.http_get", lambda *a, **k: (_ for _ in ()).throw(OSError()))
from frameport.analysis.detect import analyze
a = analyze(apk, deep=False)
assert a.extra["frame_patched"] and engine.suggest(a).as_is
library.upsert_game(a.package, apk=str(apk), analysis=a.to_dict(),
recipe=library.recipe_to_dict(engine.suggest(a)))
called = []
monkeypatch.setattr(pipeline.builder, "build", lambda *x, **k: called.append(1))
info = pipeline.build_game(a.package, Reporter())
assert info["as_is"] and info["apk"] == str(apk) and not called # the original APK, overport never runs
def test_same_game_scanned_from_wrapper_or_inner_folder(tmp_path):
from frameport import pipeline
col = collection_tree(tmp_path)
pipeline.add_path(col)
pkg = "rift.space_game"
library.upsert_game(pkg, title="Space Game: Remastered", art_source="oculusdb")
pipeline.add_path(col / "Space Game v1.6.0 -GRP") # scanning the game's own folder finds the inner one
games = [g for g in library.games() if g["package"].startswith("rift.space")]
assert len(games) == 1 and games[0]["title"] == "Space Game: Remastered"
def test_upload_files_big_small_and_cancel(tmp_path):
from frameport.core.events import Cancelled, Reporter
from frameport.install import installer
class FakeFrame:
def __init__(self):
self.puts, self.tars, self.dirs = [], [], []
def mkdirs(self, dirs):
self.dirs += list(dirs)
def put(self, local, remote, progress=None, resume=True, mkdir=True):
progress(Path(local).stat().st_size // 2, Path(local).stat().st_size)
self.puts.append(remote)
def put_tar(self, files, root, progress=None):
for _local, rel in files:
progress(0, rel)
self.tars.append([rel for _, rel in files])
big = tmp_path / "big.bin"
big.write_bytes(b"x" * (installer.BIG_FILE + 1))
smalls = []
for i in range(3):
p = tmp_path / f"s{i}.txt"
p.write_bytes(b"y" * 10)
smalls.append(p)
items = [(big, "game/big.bin", big.stat().st_size)] + [(p, f"game/sub/{p.name}", 10) for p in smalls]
f = FakeFrame()
installer.upload_files(f, items, "/r/incoming", Reporter(), 10 ** 9)
assert f.puts == ["/r/incoming/game/big.bin"] and f.tars == [[f"game/sub/s{i}.txt" for i in range(3)]]
assert f.dirs == ["/r/incoming/game"]
rep = Reporter()
rep.cancelled.set()
with pytest.raises(Cancelled):
installer.upload_files(FakeFrame(), items, "/r/incoming", rep, 10 ** 9)
def test_platform_sdk_delay_loaded(tmp_path):
"""Oculus Store Unreal builds delay-load the Platform SDK (the exe or a modular build's OnlineSubsystemOculus
plugin): it has to count as the Platform SDK (the game crashes with 0xc06d007e without the Oculus app)."""
exe = tmp_path / "a.exe"
exe.write_bytes(make_pe(imports=("kernel32.dll",), delay=("LibOVRPlatform64_1.dll", "OVRPlugin.dll")))
info, _ = rift.read_pe(exe)
assert info.imports == ["kernel32.dll"] and info.delay_imports == ["libovrplatform64_1.dll", "ovrplugin.dll"]
g = tmp_path / "Game"
(g / "Game/Binaries/Win64").mkdir(parents=True)
(g / "Game/Binaries/Win64/Game-Win64-Shipping.exe").write_bytes(make_pe(imports=("kernel32.dll",)))
plug = g / "Engine/Plugins/Online/OnlineSubsystemOculus/Binaries/Win64"
plug.mkdir(parents=True)
(plug / "Game-OnlineSubsystemOculus-Win64-Shipping.dll").write_bytes(make_pe(delay=("LibOVRPlatform64_1.dll",)))
(g / "Engine/Plugins/Runtime/OculusVR/Binaries/Win64").mkdir(parents=True)
(g / "Engine/Plugins/Runtime/OculusVR/Binaries/Win64/Game-OculusHMD-Win64-Shipping.dll").write_bytes(make_pe())
assert rift.analyze(g).extra["platform_sdk"]
def test_openvr_native_routing(tmp_path):
"""A game that links OpenVR (openvr_api) and has no Oculus/LibOVR code is Frame-native (no Revive). A game that
also has LibOVR is treated as Oculus (needs Revive) — static analysis can't tell which runtime a dual build
picks."""
g = tmp_path / "SteamVR Game"
(g / f"{g.name}_Data").mkdir(parents=True)
(g / "SteamVR Game.exe").write_bytes(make_pe(imports=("openvr_api64.dll", "kernel32.dll")))
(g / "openvr_api64.dll").write_bytes(make_pe())
a = rift.analyze(g)
assert a.extra["openvr_native"] and a.extra["frame_native"] and not a.extra["needs_revive"]
r = engine.suggest(a)
assert "pcvr.revive" not in r.patches and "OpenVR" in a.xr
# add LibOVR markers -> a dual-API Unity build: runs natively, SteamVR selected by argument
(g / "OVRPlugin.dll").write_bytes(make_pe(extra=b"LibOVRRT%hs_%d.dll"))
a2 = rift.analyze(g)
assert a2.extra["launch"] == "native" and a2.extra["launch_args"] == "-vrmode OpenVR"
r2 = engine.suggest(a2)
assert "pcvr.revive" not in r2.patches and r2.patches["pcvr.launch_args"] == {"args": "-vrmode OpenVR"}
# without openvr_api: Oculus only -> FramePort's Revive
(g / "openvr_api64.dll").unlink()
(g / "SteamVR Game.exe").write_bytes(make_pe(imports=("kernel32.dll",)))
a3 = rift.analyze(g)
assert a3.extra["launch"] == "revive" and a3.extra["needs_revive"] and not a3.extra["frame_native"]
assert "pcvr.revive" in engine.suggest(a3).patches
def test_repack_launcher_mode(tmp_path):
"""A repack set up for SteamVR (bundled LibRevive64.dll + xinput loader next to the exe) runs directly: no second
Revive (that broke e.g. the Oculus Platform entitlement check), Wine loads the loader DLLs on the Frame."""
from frameport.core.models import Recipe
from frameport.patches import pcvr
g = tmp_path / "Wilsons Heart v1 -X"
(g / "Wilsons Heart").mkdir(parents=True)
(g / "Wilsons Heart/WHVR.exe").write_bytes(make_pe(imports=("libovrplatform64_1.dll", "kernel32.dll"),
extra=b"LibOVRRT%hs_%d.dll"))
for n in ("LibRevive64.dll", "openvr_api64.dll", "xinput1_3.dll", "xinput9_1_0.dll"):
(g / "Wilsons Heart" / n).write_bytes(make_pe())
a = rift.analyze(g)
assert a.extra["launch"] == "repack" and a.extra["loader_dlls"] == ["xinput1_3.dll", "xinput9_1_0.dll"]
r = engine.suggest(a)
assert "pcvr.revive" not in r.patches and "pcvr.revive_openvr" not in r.patches
assert r.patches["pcvr.repack_launcher"] == {"dlls": "xinput1_3.dll,xinput9_1_0.dll"}
assert "pcvr.libovr_redirect" in r.patches # the Frame points LibOVRRT at the bundled LibRevive
assert pcvr.launch_env(r)["WINEDLLOVERRIDES"] == "xinput1_3,xinput9_1_0=n,b"
assert engine.warnings(r) == []
r.patches["pcvr.revive"] = {}
assert any("conflicts" in w for w in engine.warnings(r))
# the user's extra overrides extend the loader's
r2 = Recipe("x", patches={"pcvr.repack_launcher": {"dlls": "xinput1_3.dll"},
"pcvr.proton_env": {"env": "WINEDLLOVERRIDES=d3d11=n\nDXVK_HUD=fps"}})
assert pcvr.launch_env(r2) == {"WINEDLLOVERRIDES": "xinput1_3=n,b;d3d11=n", "DXVK_HUD": "fps"}
def test_unreal_dual_api_uses_vd_bat_args(tmp_path):
"""Unreal builds shipping the OpenXR plugin run natively; the repack's VD.bat arguments are reused with the
Oculus HMD module swapped for OpenXR (no desktop shortcuts needed)."""
from frameport.patches import pcvr
g = tmp_path / "Behemoth"
exe_dir = g / "BHM/Binaries/Win64"
exe_dir.mkdir(parents=True)
(exe_dir / "BHM-Win64-Shipping.exe").write_bytes(make_pe(extra=b"OculusHMD"))
(g / "Engine/Binaries/ThirdParty/OpenXR/win64").mkdir(parents=True)
(g / "Engine/Binaries/ThirdParty/OpenXR/win64/openxr_loader.dll").write_bytes(make_pe())
(exe_dir / "VD.bat").write_text('"C:\\Program Files\\VD\\VirtualDesktop.Streamer.exe" "BHM-Win64-Shipping.exe" '
"-steam -hmd=OculusXRHMD\r\n")
a = rift.analyze(g)
assert a.extra["launch"] == "native" and a.extra["launch_args"] == "-steam -hmd=OpenXR"
r = engine.suggest(a)
assert "pcvr.revive" not in r.patches and "pcvr.oculus_unreal" not in r.patches
assert pcvr.game_args(r) == ["-steam", "-hmd=OpenXR", "-nocrashreports"]
(exe_dir / "VD.bat").unlink()
assert rift.analyze(g).extra["launch_args"] == "-hmd=OpenXR" # engine default without VD.bat
@pytest.mark.parametrize("folder,name", [
("Some Game v3055+2.5.0 -RLS v76", "Some Game v3055+2.5.0"),
("Some Game v974+2.2 -RLS R2", "Some Game v974+2.2"),
("Some Game - Episode One v12+1.0", "Some Game - Episode One v12+1.0"), # a subtitle isn't a release tag
])
def test_quest_folder_display_name(folder, name):
from frameport.sources.quest_dump import display_name
assert display_name(folder) == name
def _dual_unity(tmp_path):
"""SUPERHOT VR's older build (GitHub #105): Unity built-in VR with the Oculus and the OpenVR SDK + an Electron
launcher (SHVR.exe) next to it."""
g = tmp_path / "SUPERHOT VR"
(g / "SUPERHOTVR_Data/Plugins").mkdir(parents=True)
(g / "SUPERHOTVR.exe").write_bytes(make_pe(imports=("unityplayer.dll", "kernel32.dll")))
(g / "UnityPlayer.dll").write_bytes(make_pe())
(g / "SUPERHOTVR_Data/Plugins/openvr_api.dll").write_bytes(make_pe())
(g / "SUPERHOTVR_Data/Plugins/OVRPlugin.dll").write_bytes(make_pe(extra=b"LibOVRRT%hs_%d.dll"))
(g / "SHVR.exe").write_bytes(make_pe() + b"\0" * 1024)
(g / "resources").mkdir()
(g / "resources/app.asar").write_bytes(b"asar")
(g / "icudtl.dat").write_bytes(b"icu")
(g / "resources.pak").write_bytes(b"pak")
return g
def test_electron_launcher_ranked_below_the_game(tmp_path):
g = _dual_unity(tmp_path)
assert rift.is_electron(g / "SHVR.exe") and not rift.is_electron(g / "SUPERHOTVR.exe")
ranked = rift.rank_exes(g, [g / "SUPERHOTVR.exe", g / "SHVR.exe"])
assert ranked[0]["path"] == "SUPERHOTVR.exe"
shvr = ranked[1]
assert "Electron launcher (starts the game)" in shvr["reasons"]
assert "Unity game (next to its data)" not in shvr["reasons"]
assert not rift.is_ambiguous(ranked)
def test_catalog_from_another_build_keeps_launch_args(tmp_path, monkeypatch):
"""A catalog recipe verified with another build (other VR APIs) must not drop this build's own launch arguments:
SUPERHOT VR's OpenXR build is in the catalog; the older Oculus + OpenVR build needs -vrmode OpenVR (#105)."""
from frameport.recommend import catalog
a = rift.analyze(_dual_unity(tmp_path))
assert a.xr == "LibOVR+OpenVR" and a.extra["launch_args"] == "-vrmode OpenVR"
a.package = "rift.superhot_vr"
entry = catalog.CatalogEntry(package="rift.superhot_vr", title="SUPERHOT VR", status="works", xr="OpenXR",
kind="rift", pcvr=["pcvr.xr_timefix"], pcvr_remove=["pcvr.revive"], as_is=True,
verified={"date": "2026-10-05"})
monkeypatch.setattr(catalog, "lookup", lambda pkg: entry)
r = engine.suggest(a)
assert r.patches["pcvr.launch_args"] == {"args": "-vrmode OpenVR"}
assert "another build" in r.notes and "-vrmode OpenVR" in r.notes
# the same build (same VR APIs): the verified recipe decides how the game starts, as before
entry.xr = "LibOVR+OpenVR"
r = engine.suggest(a)
assert "pcvr.launch_args" not in r.patches and "another build" not in r.notes
# a catalog entry that lists the arguments: the build's own ones, none for a build without any
entry.pcvr = ["pcvr.launch_args", "pcvr.xr_timefix"]
assert engine.suggest(a).patches["pcvr.launch_args"] == {"args": "-vrmode OpenVR"}
a.extra["launch_args"] = ""
assert "pcvr.launch_args" not in engine.suggest(a).patches
def test_superhot_catalog_entry_fits_both_builds(tmp_path, monkeypatch):
from frameport.recommend import catalog
monkeypatch.setenv("FRAMEPORT_NO_CATALOG_UPDATE", "1")
entry = catalog.load(refresh=True).get("rift.superhot_vr")
assert entry and "pcvr.launch_args" in entry.pcvr
a = rift.analyze(_dual_unity(tmp_path))
a.package = "rift.superhot_vr"
monkeypatch.setattr(catalog, "lookup", lambda pkg: entry)
assert engine.suggest(a).patches["pcvr.launch_args"] == {"args": "-vrmode OpenVR"}