mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-25 04:06:31 +01:00
Compare commits
3
Commits
docs-sweep
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79e329afe5 | ||
|
|
9973833585 | ||
|
|
c239a8cf32 |
No files matched your search
@@ -1,5 +1,5 @@
|
||||
dfeee15b52a530aef0fc90f79140249522adf888d1c88fd57935dd8ce5257fa5 ./arm64-v8a/libVkLayer_fdm_darken.so
|
||||
5becb96ee86fbda0ce98c0e0fd064f8ba092314aceaa400e3dcc785705dc3c23 ./arm64-v8a/libVkLayer_fp_shaderfix.so
|
||||
86974202436e9209e260a690ae3eb0082d7d0f88e17c621dd77c0788ea66bc6d ./arm64-v8a/libVkLayer_fp_shaderfix.so
|
||||
e63d93e086df3b7b29501188b80fb113879e2fd325d7659f68a31ac5bc87b561 ./arm64-v8a/liba32bridge.so
|
||||
81b073b0cdf11fdb09d22dff682884c5046fe13801f18db9aed232e86f3bf173 ./arm64-v8a/liba32rt.so
|
||||
18d8d917e83a6d87008f41908eb478dff635e798844e125abd1e8a5cf31805e2 ./arm64-v8a/liba32stub.so
|
||||
@@ -8,7 +8,7 @@ e63d93e086df3b7b29501188b80fb113879e2fd325d7659f68a31ac5bc87b561 ./arm64-v8a/li
|
||||
321da502e0f8f46f0880aad39fe6b84bdef88f925164568814cf704a1b31a577 ./arm64-v8a/libfp_ovrp.so
|
||||
40defdaddcda53bd2649eb48076fae1622bfc2bfc88e9e03c94fc8b45af2a4f2 ./arm64-v8a/libfp_ovrtrace.so
|
||||
0167c2fde5dfde62897f9d85afc4fc8e97784e988b5b8df6cea8c407240b3424 ./arm64-v8a/libfp_tkeys.so
|
||||
e32f9b3222bfe2e58c193db809ac2cf54086df36fd2ec6651aa2e58308f6b39d ./arm64-v8a/libfp_vk.so
|
||||
0055308856070aa7e09d80bcf66ea52dc0a495829b9da64383e38f4350706226 ./arm64-v8a/libfp_vk.so
|
||||
3c548b567c6984c91bf3f4a2b0bae1336f569f3e9fa9c3204872fd9876f88284 ./arm64-v8a/libfp_xrruntime.so
|
||||
383054f8b3b41dde76d062c71856cd3163655e50009c08454bc1793f393ead65 ./arm64-v8a/libfpg.so
|
||||
4a13a33e112c9e4e3c5ff4337ed6e988a9e5b3602f48d654df148ef5fafbf426 ./arm64-v8a/libfpglmv.so
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -275,6 +275,16 @@ signatures:
|
||||
GraphicsEnv functions it uses were not found or behaved differently, for example after a Lepton update), so the game's
|
||||
OpenGL ES shader fixes (zink_shader_fix) don't apply.
|
||||
suggest: []
|
||||
- id: shader-fix-unmatched
|
||||
# vkshim (vk_shader_fix) / the shader-fix layer (zink_shader_fix), once per session, 5 minutes after the first shader
|
||||
pattern: '(?:vk shim|shader fix layer): \d+ shader fix\(es\) configured, none matched'
|
||||
severity: info
|
||||
diagnosis: This game's recipe has a shader fix (for a GPU hang at one effect or scene), but none of the shaders it
|
||||
fixes showed up in the first minutes of play. If you haven't reached that part of the game yet, that's expected.
|
||||
Otherwise a game update may have changed the shader, so the fix no longer applies and the old hang can come back.
|
||||
If the game freezes or closes there, please send a problem report with diagnostics so the fix can be updated.
|
||||
suggest: []
|
||||
report: true
|
||||
- id: zink-device-lost
|
||||
pattern: 'zink.*DEVICE[_ ]LOST|VK_ERROR_DEVICE_LOST'
|
||||
severity: fatal
|
||||
|
||||
@@ -203,6 +203,7 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
|
||||
| OpenGL ES game: memory and GPU mappings grow every frame (~20 MB/s, `/dev/dri/renderD128` mappings ~6 per frame) until the frame rate collapses; the game's `xrclient_<package>.txt` (Steam logs) has one `SXR_GL_CHECK: error:0x502 ... glGenTextures` before the session starts (GitHub #285, Vader Immortal) | The game leaves GL errors pending on its context; the runtime's GL interop reads glGetError after its own calls and takes the stale error as its own failure. From then on `CVRCompositorSharedTextures::BeginCommandBuffer` allocates a new command buffer every frame instead of reusing one (perf page-fault stacks) | Adapter `gl_error_clear=1` (GLES only) clears pending GL errors before xrCreateSwapchain/xrBeginFrame/xrEndFrame; Vader's catalog recipes set it. Verified headless 2026-10-14: 191 GPU mappings and ~1.07 GB flat over 100 s (before: +430 mappings/s, +22 MB/s), no SXR_GL_CHECK |
|
||||
| Team Beef port slows to a crawl after 5-10 minutes on any map; vrcompositor logs `WaitForPendingPresent: failed to wait for present` more and more often, until its watchdog aborts or the kernel oopses in `drm_gpuvm_validate_deferred_collect` (`msm_ioctl_gem_submit`, from the game's render thread) and the Frame restarts; `xrclient_<package>.txt` has `SXR_GL_CHECK: error:0x500 ... glGenTextures` (GitHub #237, QuestZDoom) | Same runtime GL-interop leak as above: the game leaves GL_INVALID_ENUM pending every frame. `/sys/kernel/debug/dri/0/gem` (readable as steamos) showed the game's GPU VM growing by ~105 objects a second (8,000 after 75 s headless); every GPU submit then has more objects to validate | `adapter.gl_error_clear=1`: suggested for every Team Beef port (`tbxr_libs`), set in QuestZDoom's catalog recipe. Headless 2026-10-24: the game's GPU objects stay flat (1,915 in total over 2 minutes), no SXR_GL_CHECK |
|
||||
| OpenGL ES game: the GPU hangs at one effect (freeze/exit, compositor watchdog, for example Vader Immortal's lightspeed jump) | a shader (as Zink compiles it) reads loop counters/accumulators before setting them | capture the SPIR-V (adapter `zink_shader_dump=1` writes every module to `files/fp_spirv/`, or a GPU capture), then a `zink_shader_fix` entry (same format as `vk_shader_fix`) + `frame.zink_shader_fix` (Vulkan layer under Zink, activated through GraphicsEnv's debug layer list); log `shader fix layer: fixed shader module N`; triage `zink-shader-fix-mismatch` when a Frame update changed the driver's output |
|
||||
| A game with a shader fix in its recipe (`vk_shader_fix` / `zink_shader_fix`, for example VR4, Vader Immortal) hangs the GPU again at the effect it was fixed for, after a game update; the log has `… shader fix(es) configured, none matched after N modules in S s (game updated?)` (triage `shader-fix-unmatched`, info) | fixes match a shader by exact size + SHA-256, so a changed shader is silently left alone; the shim/layer logs that line once per session when no configured fix matched 5 minutes after the first shader (checked only at shader creation; a match logs `fixed shader module`). Not reaching the effect yet gives the same line | capture the shaders again (`adapter.vk_shader_dump=1` / `adapter.zink_shader_dump=1`), play to the hang and send diagnostics; the maintainer updates the catalog's fix for the new shader. Never key this on the game's version string |
|
||||
| 2D Android app: Steam says it's running but nothing shows | Lepton runs apps headless unless the app folder has `lepton-show-flatscreen` | FramePort adds it for apps without VR (agent v28+); reinstall apps installed before |
|
||||
| A phone/tablet app shows nothing in the headset, or a VR app opens as a flat window | FramePort's VR/2D guess (`vr_kind`) is wrong for this app | patch `device.display_mode` → **Flat window** or **VR** (Customize → "Show as VR or as a flat window"), then Update on Frame |
|
||||
| An install button opens another program (or nothing) instead of FramePort | another program owns the link type, or the switch is off, or macOS (Flet can't receive links there) | Settings → Install links → **Use FramePort for these links**; else Add games → Install from a link… (paste the button's address) |
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TAG "FrameBridge"
|
||||
@@ -530,6 +531,36 @@ static VKAPI_ATTR VkResult VKAPI_CALL create_render_pass2_khr(VkDevice device, c
|
||||
}
|
||||
|
||||
static int fixes_applied;
|
||||
#define WATCH_PREFIX "vk shim"
|
||||
// Fix watch: a fix matches by exact size + SHA-256, so a game (or driver) update that changes the shader makes it
|
||||
// silently do nothing. Once per session, when fixes are configured and none has matched WATCH_SECONDS after the first
|
||||
// module, one line says so (triage `shader-fix-unmatched`); checked only when a module is created, never per frame.
|
||||
#define WATCH_SECONDS 300
|
||||
static int watch_matched, watch_done;
|
||||
static unsigned watch_modules;
|
||||
static long watch_start;
|
||||
|
||||
static long watch_now(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (long)ts.tv_sec;
|
||||
}
|
||||
|
||||
static void watch_module(int matched) {
|
||||
if (!nfixes || __atomic_load_n(&watch_done, __ATOMIC_RELAXED)) return;
|
||||
if (matched) {
|
||||
__atomic_store_n(&watch_matched, 1, __ATOMIC_RELAXED);
|
||||
__atomic_store_n(&watch_done, 1, __ATOMIC_RELAXED);
|
||||
return;
|
||||
}
|
||||
unsigned n = __atomic_add_fetch(&watch_modules, 1, __ATOMIC_RELAXED);
|
||||
long now = watch_now(), start = 0;
|
||||
if (__atomic_compare_exchange_n(&watch_start, &start, now, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) return; // first
|
||||
if (now - start >= WATCH_SECONDS && !__atomic_exchange_n(&watch_done, 1, __ATOMIC_RELAXED) &&
|
||||
!__atomic_load_n(&watch_matched, __ATOMIC_RELAXED))
|
||||
LOG(WATCH_PREFIX ": %d shader fix(es) configured, none matched after %u modules in %ld s (game updated?)", nfixes,
|
||||
n, now - start);
|
||||
}
|
||||
|
||||
EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo *ci,
|
||||
const VkAllocationCallbacks *alloc, VkShaderModule *module) {
|
||||
@@ -540,6 +571,7 @@ EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule(VkDevice device, cons
|
||||
if (dump_on && ci) dump_module(ci->pCode, ci->codeSize); // the game's own module, before any fix
|
||||
size_t size = 0;
|
||||
uint32_t *code = nfixes && ci && ci->pCode ? fixed_shader(ci->pCode, ci->codeSize, &size) : NULL;
|
||||
watch_module(code != NULL);
|
||||
if (!code) {
|
||||
VkResult r = real(device, ci, alloc, module);
|
||||
if (pl_on && ci && module) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#define TAG "FrameBridge"
|
||||
#define EXPORT __attribute__((visibility("default")))
|
||||
@@ -225,6 +226,37 @@ static uint32_t *fixed_shader(const uint32_t *code, size_t size, size_t *out_siz
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define WATCH_PREFIX "shader fix layer"
|
||||
// Fix watch: a fix matches by exact size + SHA-256, so a game (or driver) update that changes the shader makes it
|
||||
// silently do nothing. Once per session, when fixes are configured and none has matched WATCH_SECONDS after the first
|
||||
// module, one line says so (triage `shader-fix-unmatched`); checked only when a module is created, never per frame.
|
||||
#define WATCH_SECONDS 300
|
||||
static int watch_matched, watch_done;
|
||||
static unsigned watch_modules;
|
||||
static long watch_start;
|
||||
|
||||
static long watch_now(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (long)ts.tv_sec;
|
||||
}
|
||||
|
||||
static void watch_module(int matched) {
|
||||
if (!nfixes || __atomic_load_n(&watch_done, __ATOMIC_RELAXED)) return;
|
||||
if (matched) {
|
||||
__atomic_store_n(&watch_matched, 1, __ATOMIC_RELAXED);
|
||||
__atomic_store_n(&watch_done, 1, __ATOMIC_RELAXED);
|
||||
return;
|
||||
}
|
||||
unsigned n = __atomic_add_fetch(&watch_modules, 1, __ATOMIC_RELAXED);
|
||||
long now = watch_now(), start = 0;
|
||||
if (__atomic_compare_exchange_n(&watch_start, &start, now, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) return; // first
|
||||
if (now - start >= WATCH_SECONDS && !__atomic_exchange_n(&watch_done, 1, __ATOMIC_RELAXED) &&
|
||||
!__atomic_load_n(&watch_matched, __ATOMIC_RELAXED))
|
||||
LOG(WATCH_PREFIX ": %d shader fix(es) configured, none matched after %u modules in %ld s (game updated?)", nfixes,
|
||||
n, now - start);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- dispatch (per instance / device, by dispatch key)
|
||||
|
||||
#define MAX_HANDLES 16
|
||||
@@ -313,6 +345,7 @@ static VKAPI_ATTR VkResult VKAPI_CALL layer_CreateShaderModule(VkDevice device,
|
||||
if (!next) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
size_t size = 0;
|
||||
uint32_t *copy = ci && ci->pCode && ci->codeSize >= 20 ? fixed_shader(ci->pCode, ci->codeSize, &size) : NULL;
|
||||
watch_module(copy != NULL);
|
||||
if (!copy) return next(device, ci, a, module);
|
||||
VkShaderModuleCreateInfo fixed = *ci;
|
||||
fixed.codeSize = size;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Per-game OpenXR headset identity (GitHub #373)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
|
||||
from ..base import Param, Patch, Suggestion, register
|
||||
|
||||
# Quest 2 deliberately leaves both layers untouched: the compatibility escape hatch.
|
||||
@@ -17,26 +19,94 @@ UUIDS = {"quest2": bytes.fromhex("3064f23915ed4722a6595ea1ecd899c2"),
|
||||
SYSTEM_NAMES = {"": b"Meta Quest 3", "quest3s": b"Meta Quest 3S",
|
||||
"questpro": b"Quest Pro", "quest2": b"Oculus Quest2"}
|
||||
# Verified OVRPort 3.4.3-aa54c3f arm64 dispatcher: file offsets (VAs differ).
|
||||
# Guard both data and the instructions that load those exact answers, before changing either.
|
||||
# Guard both data and the instructions that load those exact answers, before changing either. Other runtimes (the
|
||||
# older 3.4.3-23204ea, later ones) are matched by structure instead (find_identity).
|
||||
NAME_OFFSET, UUID_OFFSET = 0x6034, 0x21338
|
||||
GUARDS = {0x18620: bytes.fromhex("61ffffd021d00091807200910220805203208052c401805262210094"),
|
||||
0x186e4: bytes.fromhex("1f2003d584620810"),
|
||||
0x18778: bytes.fromhex("8000c03de006803de2ffff17")}
|
||||
|
||||
|
||||
QUEST2_NAME = b"Oculus Quest2\0"
|
||||
|
||||
|
||||
def _code_references(data: bytes) -> dict[int, int]:
|
||||
"""{address: number of loads} for the addresses AArch64 code forms with `adr`, or `adrp` + `add` / `ldr q` (the
|
||||
two ways the verified dispatcher loads systemName and the UUID), over the executable PT_LOAD segments."""
|
||||
from ...analysis import elf
|
||||
|
||||
refs: dict[int, int] = {}
|
||||
for seg in elf._elf(data).iter_segments():
|
||||
if seg["p_type"] != "PT_LOAD" or not seg["p_flags"] & 1:
|
||||
continue
|
||||
va0, off, size = seg["p_vaddr"], seg["p_offset"], seg["p_filesz"]
|
||||
words = struct.unpack_from(f"<{size // 4}I", data, off)
|
||||
for i, w in enumerate(words):
|
||||
if w & 0x1F000000 != 0x10000000: # adr / adrp
|
||||
continue
|
||||
imm = ((w >> 5) & 0x7FFFF) << 2 | (w >> 29) & 3
|
||||
imm -= (imm & 1 << 20) << 1
|
||||
pc = va0 + 4 * i
|
||||
if not w & 0x80000000: # adr
|
||||
refs[pc + imm] = refs.get(pc + imm, 0) + 1
|
||||
continue
|
||||
rd, page = w & 0x1F, (pc & ~0xFFF) + (imm << 12)
|
||||
for x in words[i + 1:i + 9]:
|
||||
if (x >> 5) & 0x1F != rd:
|
||||
continue
|
||||
if x & 0xFFC00000 == 0x91000000: # add xD, xN, #imm12
|
||||
target = page + ((x >> 10) & 0xFFF)
|
||||
elif x & 0xFFC00000 == 0x3DC00000: # ldr qT, [xN, #imm12 * 16]
|
||||
target = page + ((x >> 10) & 0xFFF) * 16
|
||||
else:
|
||||
continue
|
||||
refs[target] = refs.get(target, 0) + 1
|
||||
break
|
||||
return refs
|
||||
|
||||
|
||||
def find_identity(data: bytes) -> tuple[int, int] | None:
|
||||
"""(systemName file offset, UUID file offset) of an OVRPort dispatcher: the verified 3.4.3-aa54c3f layout, else
|
||||
found by structure for other runtimes: Quest 2's name and UUID each exactly once in the library, and each loaded
|
||||
by exactly one instruction sequence of its code. None = not found (nothing is changed)."""
|
||||
expected = {NAME_OFFSET: QUEST2_NAME, UUID_OFFSET: UUIDS["quest2"], **GUARDS}
|
||||
if all(data[off:off + len(value)] == value for off, value in expected.items()):
|
||||
return NAME_OFFSET, UUID_OFFSET
|
||||
from ...analysis import elf
|
||||
|
||||
if not (elf.is_64bit(data) and data[5] == 1 and data[18:20] == b"\xb7\x00"): # little-endian AArch64 only
|
||||
return None
|
||||
if data.count(QUEST2_NAME) != 1 or data.count(UUIDS["quest2"]) != 1:
|
||||
return None
|
||||
try:
|
||||
segments = elf.load_segments(data)
|
||||
refs = _code_references(data)
|
||||
except Exception: # noqa: BLE001 - unreadable ELF: refuse
|
||||
return None
|
||||
|
||||
def address(off: int) -> int | None:
|
||||
return next((va + off - o for va, o, size in segments if o <= off < o + size), None)
|
||||
|
||||
name_off, uuid_off = data.find(QUEST2_NAME), data.find(UUIDS["quest2"])
|
||||
if any(refs.get(address(off)) != 1 for off in (name_off, uuid_off)):
|
||||
return None
|
||||
return name_off, uuid_off
|
||||
|
||||
|
||||
def patch_dispatcher(data: bytes, key: str) -> bytes:
|
||||
"""Same-size data-only edit; unknown dispatchers fail atomically. Quest 2 is an exact no-op."""
|
||||
if key not in SYSTEM_NAMES:
|
||||
raise ValueError(f"Unknown headset model: {key!r}")
|
||||
if key == "quest2":
|
||||
return data
|
||||
expected = {NAME_OFFSET: b"Oculus Quest2\0", UUID_OFFSET: UUIDS["quest2"], **GUARDS}
|
||||
if not all(data[off:off + len(value)] == value for off, value in expected.items()):
|
||||
raise ValueError("Unsupported OVRPort headset identity dispatcher: expected 3.4.3-aa54c3f arm64 "
|
||||
"systemName/UUID and load instructions; select Quest 2 to preserve its identity.")
|
||||
found = find_identity(data)
|
||||
if not found:
|
||||
raise ValueError("Unsupported OVRPort headset identity dispatcher: its Quest 2 systemName/UUID and the code "
|
||||
"that loads them weren't found; select Quest 2 to preserve its identity.")
|
||||
name_off, uuid_off = found
|
||||
out = bytearray(data)
|
||||
out[NAME_OFFSET:NAME_OFFSET + 14] = SYSTEM_NAMES[key].ljust(14, b"\0")
|
||||
out[UUID_OFFSET:UUID_OFFSET + 16] = UUIDS[key]
|
||||
out[name_off:name_off + 14] = SYSTEM_NAMES[key].ljust(14, b"\0")
|
||||
out[uuid_off:uuid_off + 16] = UUIDS[key]
|
||||
return bytes(out)
|
||||
|
||||
|
||||
@@ -96,7 +166,8 @@ class HeadsetModel(Patch):
|
||||
except ValueError:
|
||||
# Quest 3 is everyone's default: an APK converted with another OVRPort runtime (e.g. an already converted
|
||||
# copy) keeps OVRPort's Quest 2 answers instead of failing its build
|
||||
ctx.checks.append((self.id, None, "This OVRPort dispatcher isn't a known build; the game keeps seeing a "
|
||||
ctx.checks.append((self.id, None, "This OVRPort dispatcher's headset identity (Quest 2 name and UUID "
|
||||
"and the code that loads them) wasn't found; the game keeps seeing a "
|
||||
"Quest 2 (only FrameBridge's systemName override applies)."))
|
||||
return False
|
||||
ws.put(ws.lib(loader), fixed)
|
||||
|
||||
@@ -189,6 +189,51 @@ def test_dispatcher_rejects_unknown_and_partial_matches():
|
||||
assert patch_dispatcher(b'unknown runtime', 'quest2') == b'unknown runtime'
|
||||
|
||||
|
||||
def _elf_dispatcher(name_refs=1, uuid_refs=1, extra_name=False):
|
||||
"""A small AArch64 ELF laid out like another OVRPort runtime: one R+X PT_LOAD (vaddr == offset) with the code and
|
||||
the name, one RW PT_LOAD 0x8000 above its file offset with the UUID; the code loads the name with adrp + add and
|
||||
the UUID with adr, like the verified dispatcher."""
|
||||
import struct
|
||||
|
||||
from frameport.patches.frame.headset_model import QUEST2_NAME, UUIDS
|
||||
data = bytearray(0x3000)
|
||||
data[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
|
||||
struct.pack_into("<HHIQQQIHHHHHH", data, 16, 3, 183, 1, 0, 64, 0, 0, 64, 56, 2, 64, 0, 0)
|
||||
struct.pack_into("<IIQQQQQQ", data, 64, 1, 5, 0, 0, 0, 0x2000, 0x2000, 0x1000)
|
||||
struct.pack_into("<IIQQQQQQ", data, 120, 1, 6, 0x2000, 0xA000, 0xA000, 0x1000, 0x1000, 0x1000)
|
||||
name_off, uuid_off, uuid_va = 0x1834, 0x2340, 0xA340
|
||||
data[name_off:name_off + 14] = QUEST2_NAME
|
||||
data[uuid_off:uuid_off + 16] = UUIDS["quest2"]
|
||||
if extra_name:
|
||||
data[0x1900:0x1900 + 14] = QUEST2_NAME
|
||||
code = 0x400
|
||||
for _ in range(name_refs): # adrp x1, page(0x1834) = +1 page ; add x1, x1, #0x834 (code in page 0)
|
||||
struct.pack_into("<II", data, code, 0x90000001 | 1 << 29, 0x91000021 | 0x834 << 10)
|
||||
code += 8
|
||||
for _ in range(uuid_refs): # adr x4, uuid
|
||||
delta = uuid_va - code
|
||||
struct.pack_into("<I", data, code, 0x10000004 | (delta & 3) << 29 | ((delta >> 2) & 0x7FFFF) << 5)
|
||||
code += 4
|
||||
return bytes(data), name_off, uuid_off
|
||||
|
||||
|
||||
def test_other_runtime_found_by_structure():
|
||||
"""OVRPort runtimes other than the verified one (the older 3.4.3-23204ea; later ones) keep the Quest 3 default:
|
||||
the name and UUID are found by content and by the code that loads them, not by fixed offsets."""
|
||||
from frameport.patches.frame.headset_model import SYSTEM_NAMES, UUIDS, find_identity, patch_dispatcher
|
||||
blob, name_off, uuid_off = _elf_dispatcher()
|
||||
assert find_identity(blob) == (name_off, uuid_off)
|
||||
fixed = patch_dispatcher(blob, "")
|
||||
assert fixed[name_off:name_off + 14] == SYSTEM_NAMES[""].ljust(14, b"\0")
|
||||
assert fixed[uuid_off:uuid_off + 16] == UUIDS[""]
|
||||
assert sum(a != b for a, b in zip(blob, fixed, strict=True)) <= 30
|
||||
for kw in (dict(name_refs=0), dict(uuid_refs=0), dict(name_refs=2), dict(extra_name=True)):
|
||||
other = _elf_dispatcher(**kw)[0]
|
||||
assert find_identity(other) is None # not loaded once by the code / not unique: refused, unchanged
|
||||
with pytest.raises(ValueError, match="Unsupported OVRPort"):
|
||||
patch_dispatcher(other, "")
|
||||
|
||||
|
||||
def test_unknown_dispatcher_keeps_quest2_instead_of_failing_the_build():
|
||||
"""Quest 3 is the default for every Quest game: an APK converted with another OVRPort runtime (found with an
|
||||
already converted Batman copy) must still build, keeping OVRPort's Quest 2 answers."""
|
||||
@@ -218,6 +263,9 @@ def test_real_dispatcher_when_available():
|
||||
for key in MODELS:
|
||||
fixed = patch_dispatcher(original, key)
|
||||
assert len(fixed) == len(original)
|
||||
older = path.parents[3] / '3.4.3-23204ea/lib/arm64-v8a/libopenxr_loader.so'
|
||||
if older.is_file(): # the older runtime: found by structure (offsets differ)
|
||||
assert patch_dispatcher(older.read_bytes(), '') != older.read_bytes()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('key', MODELS)
|
||||
|
||||
@@ -124,6 +124,18 @@ def test_shader_fix_layer_findings():
|
||||
assert "adapter.zink_shader_dump" in r.suggestions()
|
||||
|
||||
|
||||
def test_shader_fix_unmatched():
|
||||
for prefix in ("vk shim", "shader fix layer"):
|
||||
log = LOG_OK + (f"09-28 17:39:06.000 1147 1174 I FrameBridge: {prefix}: 2 shader fix(es) configured, "
|
||||
"none matched after 812 modules in 300 s (game updated?)\n")
|
||||
r = triage(log, "RUNNING", "com.example.game")
|
||||
f = next(f for f in r.findings if f.id == "shader-fix-unmatched")
|
||||
assert f.severity == "info" and r.verdict != "fail"
|
||||
# a fix that matched logs its own line, not this one
|
||||
log = LOG_OK + "09-28 17:39:06.000 1147 1174 I FrameBridge: vk shim: fixed shader module (100 -> 108 bytes): 0\n"
|
||||
assert "shader-fix-unmatched" not in {f.id for f in triage(log, "RUNNING", "com.example.game").findings}
|
||||
|
||||
|
||||
def test_launcher_signature():
|
||||
r = triage("lepton: APP_ACTIVITY is empty\n", "NEVER_STARTED")
|
||||
assert r.suggestions() == ["frame.launcher"]
|
||||
|
||||
Reference in new issue
Block a user