mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-06 09:00:38 +02:00
Fix Meta XR Audio metadata lifetime during Batman smoke effects
This commit is contained in:
1 parent
4e00c88949
commit
70d519a5a9
12 files changed
+268
-3
No files matched your search
@@ -3,10 +3,10 @@ a5b03bb7ae5d953d7ded2bf117b0a19c57fae604f16f54310847f8e89593dde6 ./arm64-v8a/li
|
||||
6d2b9f8cea2f2cf33b8ff30d29a2f3d9c4d55549260a867168a24e44abada021 ./arm64-v8a/libfp_vk.so
|
||||
8e00552d1ab90f82749db3caad4f773351fa8a28edcc5ea5cbd1b31c434ff32a ./arm64-v8a/libframe_xrshim.so
|
||||
3dfcb0f2bec47857bcecc7261c58e076d872d4649718c40850f29e10c75477dd ./arm64-v8a/libglshim.so
|
||||
507bb1c62168da9335a5883ec264aabed2ce09d04b6524e67be504a273473f8d ./arm64-v8a/libopenxr_loader_generic.so
|
||||
6c836091a1a3bcbfd526de317c455d156d41875b69c8a6f05e7f761ca24b40f8 ./arm64-v8a/libopenxr_loader_generic.so
|
||||
1feaeafad467c4cafdf2b018a4d84b0bee200c3f711697b4ce97e66a3ba256ca ./arm64-v8a/libovrplatformcompat.so
|
||||
32525cd0a9ee3d9993d8871cd6905bdcf22cde4f4ba28f5db6434ce7d6554758 ./arm64-v8a/libvrapi.so
|
||||
11b7019c6a8d3b2f0f89cf23011f84bbbcb8e907f0935978e1df675339abbb6e ./armeabi-v7a/libopenxr_loader_generic.so
|
||||
309d6c5266b3a0fa4c7b1cb884b28653dc2e76957501c070378a10e8c5682cec ./armeabi-v7a/libopenxr_loader_generic.so
|
||||
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
|
||||
1aa733117cf57ccff7cf23f0425dbfd73b0332a3ef1c905ceeca0ccd0449c4e0 ./linux-arm64/XR_APILAYER_FRAMEPORT_timefix.json
|
||||
28c2430a02bbd8902c5bfb9562c6fd0e318654f9e05c095bfeb94b0450ab1b07 ./linux-arm64/libxr_frameport_timefix.so
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,31 @@
|
||||
# Meta XR Audio metadata lifetime
|
||||
|
||||
FrameBridge automatically handles metadata retirement in the verified AArch64
|
||||
Meta XR Audio Wwise build (`e1619e7fb839badf0ea5ecc22d9f028d0b17c34d`). It requires
|
||||
the build ID and exact function prologues; other builds and 32-bit adapters are
|
||||
left unchanged. No additional patch switch or recipe entry is required.
|
||||
|
||||
The SDK queues terminated metadata for deletion, then drains those queues before
|
||||
reading the current `AkAudioObjects` snapshot. In Batman: Arkham Shadow, that
|
||||
snapshot can still reference queued metadata during smoke-bomb playback. The
|
||||
audio thread subsequently clears its dirty flags at offset 8 through the stale
|
||||
pointer. A hardware watch captured this store in
|
||||
`OculusEndpointSink::ConsumeObjectExperimentalMetadata`, at library offset
|
||||
`0x2ced90`. Reallocated input tree nodes occupy the same 48-byte size class;
|
||||
the stale store corrupts their right pointer and the game crashes later.
|
||||
|
||||
`native/adapter/audio_metadata.h` wraps the audio consumption boundary to make
|
||||
the current snapshot available to metadata cleanup. `audio_metadata_queue.h`
|
||||
retains queued objects still referenced by that snapshot, then reclaims them
|
||||
normally when the references disappear. It preserves the SDK's queue mutex,
|
||||
virtual deleting destructor, audio parameters and processing. There is no timed
|
||||
quarantine, replacement allocator, audio suppression or diagnostic memory arena.
|
||||
|
||||
Synthetic regression tests exercise mixed referenced and unreferenced queue
|
||||
entries, both metadata types, subsequent retirement, and cleanup without a live
|
||||
snapshot. On-headset smoke-bomb tests first showed the observed overwrite stopped,
|
||||
then passed with ordinary allocations and all memory diagnostics removed.
|
||||
|
||||
The existing `frame.adapter` install step includes the repair in its packaged
|
||||
adapter and updates already wrapped APKs. Rebuild the adapters and checksums with
|
||||
`python native/build.py --only adapter`.
|
||||
@@ -53,6 +53,7 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
|
||||
| PC VR game judders/stutters although the GPU keeps up; vrcompositor.txt "Timed out. N total" high or frames dropped | the game misses the headset's refresh (e.g. 96 Hz = 10.4 ms; Stormland's slow frames take 10.7 ms) | `pcvr.steamvr_tuning` (default on): next Play sets SteamVR per-app `preferredRefreshRate` + `motionSmoothingOverride` via `fp_vrsettings.exe` |
|
||||
| Quest app keeps recentering / snapping the view when you turn your head; log shows session state 5→4→3→4→5 within a second | the Frame briefly takes focus; the app recenters on focus changes | unfixed: hiding the dips (debounce) made AC Nexus stay black, so it was removed |
|
||||
| Video player / app finds no local videos; MediaProvider "Requested path /home/steamos/... doesn't appear under ..." | Lepton's /sdcard is a symlink to a host path, so Android's media index rejects every file | upload in the Files tab (Videos = /sdcard/Movies) and browse folders in the app |
|
||||
| Batman: Arkham Shadow closes during smoke-bomb effects; later input-tree corruption | Meta XR Audio Wwise deletes queued metadata still referenced by the current audio frame, then writes through the stale pointer | FrameBridge automatically retires metadata after current-frame references disappear, for the verified AArch64 SDK build only; see [AUDIO_METADATA.md](AUDIO_METADATA.md) |
|
||||
| Game (Unity, GLES) freezes, `zink: DEVICE LOST` | multisampled render-to-texture hangs the GPU | `frame.unity_no_msaa`; if it persists: unfixable → PC version |
|
||||
| Only the Android home screen shows; the log has Unity's VR device as `None` (e.g. Accounting+, Unity 2017) | Unity 2017–2018 built-in Oculus support starts VR only when `com.oculus.systemactivities` is installed; Lepton has no Meta system apps, so Unity falls back to non-VR | `frame.unity_oculus_check`: that package name in libunity.so → `android`, plus `native/ovrpshim` (libfp_ovrp.so): Unity 2017's legacy frame loop (`ovrp_Update2`/`ovrp_BeginFrame`) never calls `ovrp_WaitToBeginFrame`, so without the shim no `xrWaitFrame` happens, `CompositorOpenXR::Update … outside of frame bounds` floods the log and the dashboard freezes. With it: log `ovrp frame loop shim: waited for frame N`, ~71 fps. Accounting+ then stops at "press any button" although its OVRInput gets clean input (`INPUT_PROBE` in the patch logs connected controllers/buttons/input focus per call) — unresolved |
|
||||
| Own-engine game crashes in `libVkLayer_fossilize.so` (Fossilize's recording thread, e.g. Roblox) | same as Deadpool VR: uninitialised pointers in what Fossilize records | `frame.vk_sanitize` now also covers own-engine libraries that load `libvulkan.so` by name |
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Native components
|
||||
|
||||
FrameBridge includes automatic metadata retirement for the verified Meta XR Audio
|
||||
Wwise build; see [AUDIO_METADATA.md](../docs/AUDIO_METADATA.md) for its lifetime
|
||||
invariant and regression coverage. It uses the existing adapter installation.
|
||||
|
||||
Sources of the prebuilt binaries in `../artifacts/` (committed, with `SHA256SUMS`). End users never compile anything;
|
||||
developers rebuild with `python native/build.py` (downloads NDK r27c, OpenXR headers at pinned commits, d8 into
|
||||
`native/.cache/`, git-ignored). The adapter, GL shim and platform compat rebuild byte-identically; the VrApi bridge
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// Meta XR Audio Wwise metadata retirement. Enabled automatically for the
|
||||
// exact verified AArch64 SDK build; unrelated libraries remain untouched.
|
||||
#pragma once
|
||||
#if defined(__aarch64__)
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <link.h>
|
||||
#include <string.h>
|
||||
static size_t mx_audio_page;
|
||||
static void *mx_audio_detour(unsigned char *address,void *replacement,const uint32_t expected[4]) {
|
||||
if(memcmp(address,expected,16)) return NULL;
|
||||
unsigned char *trampoline=mmap(NULL,mx_audio_page,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
|
||||
if(trampoline==MAP_FAILED) return NULL;
|
||||
uint32_t branch[2]={0x58000050u,0xd61f0200u}; // ldr x16, +8; br x16
|
||||
memcpy(trampoline,address,16);
|
||||
memcpy(trampoline+16,branch,8);
|
||||
uintptr_t continuation=(uintptr_t)address+16;
|
||||
memcpy(trampoline+24,&continuation,8);
|
||||
__builtin___clear_cache((char*)trampoline,(char*)trampoline+32);
|
||||
if(mprotect(trampoline,mx_audio_page,PROT_READ|PROT_EXEC)) return NULL;
|
||||
unsigned char *page=(unsigned char*)((uintptr_t)address&~(mx_audio_page-1));
|
||||
if(mprotect(page,mx_audio_page,PROT_READ|PROT_WRITE|PROT_EXEC)) return NULL;
|
||||
memcpy(address,branch,8);
|
||||
memcpy(address+8,&replacement,8);
|
||||
__builtin___clear_cache((char*)address,(char*)address+16);
|
||||
mprotect(page,mx_audio_page,PROT_READ|PROT_EXEC);
|
||||
return trampoline;
|
||||
}
|
||||
#include "audio_metadata_queue.h"
|
||||
typedef struct {float previous,current;} MxAudioRamp;
|
||||
static uintptr_t mx_audio_base;
|
||||
static uint64_t (*mx_audio_consume_original)(void*,void*,void*,const void*,MxAudioRamp);
|
||||
static void mx_audio_kill(unsigned experimental) {
|
||||
void *mutex=(void*)(mx_audio_base+(experimental?0x5fe2e0:0x5fe380));
|
||||
int (*trylock)(void*)=(void*)(mx_audio_base+0x5b369c);
|
||||
void (*unlock)(void*)=(void*)(mx_audio_base+0x5b36bc);
|
||||
if(!trylock(mutex))return;
|
||||
mx_audio_drain((void**)(mx_audio_base+(experimental?0x5fe2d8:0x5fe378)),experimental?40:24);
|
||||
unlock(mutex);
|
||||
}
|
||||
static void mx_audio_kill_experimental(void) {mx_audio_kill(1);}
|
||||
static void mx_audio_kill_regular(void) {mx_audio_kill(0);}
|
||||
static uint64_t mx_audio_consume(void *sink,void *main_mix,void *passthrough,const void *objects,MxAudioRamp ramp) {
|
||||
const unsigned char *previous=mx_audio_objects;mx_audio_objects=objects;
|
||||
// An audio thread can enter just after its prologue was patched, before
|
||||
// the installing thread publishes the completed trampoline.
|
||||
uint64_t (*original)(void*,void*,void*,const void*,MxAudioRamp);
|
||||
while(!(original=__atomic_load_n(&mx_audio_consume_original,__ATOMIC_ACQUIRE)))sched_yield();
|
||||
uint64_t result=original(sink,main_mix,passthrough,objects,ramp);
|
||||
mx_audio_objects=previous;return result;
|
||||
}
|
||||
static int mx_audio_find(struct dl_phdr_info *info,size_t size,void *unused) {
|
||||
(void)size;(void)unused;
|
||||
if(!strstr(info->dlpi_name,"/libMetaXRAudioWwise.so"))return 0;
|
||||
if(mx_audio_base)return 1;
|
||||
const unsigned char id[20]={0xe1,0x61,0x9e,0x7f,0xb8,0x39,0xba,0xdf,0x0e,0xa5,0xec,0xc2,0x2d,0x9f,0x02,0x8d,0x0b,0x17,0xc3,0x4d};
|
||||
int match=0;
|
||||
for(unsigned i=0;i<info->dlpi_phnum;i++)if(info->dlpi_phdr[i].p_type==PT_NOTE) {
|
||||
const unsigned char *p=(void*)(info->dlpi_addr+info->dlpi_phdr[i].p_vaddr);
|
||||
const unsigned char *end=p+info->dlpi_phdr[i].p_memsz;
|
||||
while(p+sizeof(Elf64_Nhdr)<=end) {
|
||||
const Elf64_Nhdr *note=(void*)p;
|
||||
const unsigned char *name=p+sizeof(*note),*desc=name+((note->n_namesz+3)&~3u);
|
||||
const unsigned char *next=desc+((note->n_descsz+3)&~3u);
|
||||
if(next>end || next<=p)break;
|
||||
if(note->n_type==NT_GNU_BUILD_ID && note->n_namesz==4 && !memcmp(name,"GNU",4) && note->n_descsz==20 && !memcmp(desc,id,20))match=1;
|
||||
p=next;
|
||||
}
|
||||
}
|
||||
if(!match)return 1;
|
||||
const uint32_t consume[4]={0xd10743ff,0x6d1623e9,0xa9177bfd,0xa9186ffc};
|
||||
const uint32_t kill[4]={0xa9bd7bfd,0xf9000bf5,0xa9024ff4,0x910003fd};
|
||||
unsigned char *base=(void*)info->dlpi_addr;
|
||||
if(memcmp(base+0x2c8fac,consume,16) || memcmp(base+0x2be2c0,kill,16) || memcmp(base+0x2be7b0,kill,16))return 1;
|
||||
__atomic_store_n(&mx_audio_base,info->dlpi_addr,__ATOMIC_RELEASE);
|
||||
void *original=mx_audio_detour(base+0x2c8fac,(void*)mx_audio_consume,consume);
|
||||
__atomic_store_n(&mx_audio_consume_original,original,__ATOMIC_RELEASE);
|
||||
if(!__atomic_load_n(&mx_audio_consume_original,__ATOMIC_ACQUIRE)) {
|
||||
__atomic_store_n(&mx_audio_base,0,__ATOMIC_RELEASE);return 1;
|
||||
}
|
||||
if(!mx_audio_detour(base+0x2be2c0,(void*)mx_audio_kill_experimental,kill) || !mx_audio_detour(base+0x2be7b0,(void*)mx_audio_kill_regular,kill)) {
|
||||
LOG("FrameBridge audio metadata: incomplete installation");return 1;
|
||||
}
|
||||
LOG("FrameBridge audio metadata: metadata reclamation follows current audio-frame references; Meta audio base=%p",base);
|
||||
return 1;
|
||||
}
|
||||
static pthread_mutex_t mx_audio_install_mutex=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void mx_audio_initialize(void) {
|
||||
if(__atomic_load_n(&mx_audio_base,__ATOMIC_ACQUIRE))return;
|
||||
if(pthread_mutex_trylock(&mx_audio_install_mutex))return;
|
||||
if(!mx_audio_page)mx_audio_page=(size_t)sysconf(_SC_PAGESIZE);
|
||||
if(!mx_audio_base)dl_iterate_phdr(mx_audio_find,NULL);
|
||||
pthread_mutex_unlock(&mx_audio_install_mutex);
|
||||
}
|
||||
#else
|
||||
static void mx_audio_initialize(void) {}
|
||||
#endif
|
||||
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
// Experimental lifetime repair for the pinned Meta XR Audio Wwise build.
|
||||
// A queued metadata object must not be reclaimed while the current immutable
|
||||
// AkAudioObjects snapshot still references it. Preserve the SDK's queue mutex,
|
||||
// virtual destructor, list linkage, audio processing, and dirty-flag writes.
|
||||
static _Thread_local const unsigned char *mx_audio_objects;
|
||||
static unsigned mx_audio_held,mx_audio_reclaimed;
|
||||
static int mx_audio_referenced(const void *pointer) {
|
||||
if(!mx_audio_objects)return 0;
|
||||
uint32_t count;memcpy(&count,mx_audio_objects,4);
|
||||
const unsigned char *const *objects;memcpy(&objects,mx_audio_objects+16,8);
|
||||
// Refuse to reclaim on an unrecognised snapshot rather than dereferencing
|
||||
// unbounded arrays. These bounds exceed the game's ordinary voice counts.
|
||||
if(count>4096 || (count && !objects))return 1;
|
||||
for(unsigned i=0;i<count;i++) {
|
||||
const unsigned char *object=objects[i];if(!object)continue;
|
||||
uint32_t metadata_count;const unsigned char *metadata;
|
||||
memcpy(&metadata,object+104,8);memcpy(&metadata_count,object+112,4);
|
||||
if(metadata_count>4096 || (metadata_count && !metadata))return 1;
|
||||
for(unsigned j=0;j<metadata_count;j++) {
|
||||
const void *parameter;memcpy(¶meter,metadata+(size_t)j*24+8,8);
|
||||
if(parameter==pointer)return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Caller holds the original queue's mutex, just as ExecuteKillList did.
|
||||
static void mx_audio_drain(void **head,size_t next_offset) {
|
||||
void **link=head;
|
||||
while(*link) {
|
||||
void *pointer=*link;void **next=(void**)((unsigned char*)pointer+next_offset);
|
||||
if(mx_audio_referenced(pointer)) {
|
||||
link=next;
|
||||
unsigned held=__atomic_fetch_add(&mx_audio_held,1,__ATOMIC_RELAXED);
|
||||
if(held<8)LOG("FrameBridge audio metadata: queued metadata=%p still referenced by audio frame; retained",pointer);
|
||||
} else {
|
||||
*link=*next;
|
||||
void (**table)(void*)=*(void (***)(void*))pointer;
|
||||
table[1](pointer);
|
||||
__atomic_fetch_add(&mx_audio_reclaimed,1,__ATOMIC_RELAXED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ static void fb_log(const char *fmt, ...) {
|
||||
funlockfile(log_file);
|
||||
}
|
||||
#define LOG(...) fb_log(__VA_ARGS__)
|
||||
#include "audio_metadata.h"
|
||||
|
||||
static void *loader;
|
||||
static PFN_xrGetInstanceProcAddr next_gipa;
|
||||
@@ -305,10 +306,11 @@ XRAPI_ATTR XrResult XRAPI_CALL xrCreateInstance(const XrInstanceCreateInfo *info
|
||||
}
|
||||
no_equirect = !has_equirect; no_equirect2 = !has_equirect2; no_cylinder = !has_cylinder; no_cube = !has_cube;
|
||||
free(available);
|
||||
mx_audio_initialize();
|
||||
XrResult result = fn(&fixed, instance);
|
||||
free(names);
|
||||
LOG("xrCreateInstance result=%d", result);
|
||||
if (XR_SUCCEEDED(result)) active_instance = *instance;
|
||||
if (XR_SUCCEEDED(result)) { active_instance = *instance; mx_audio_initialize(); }
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -769,6 +771,7 @@ static XrSession focused_session = XR_NULL_HANDLE;
|
||||
static int kicks_pending = -1; // -1: not yet armed
|
||||
|
||||
XRAPI_ATTR XrResult XRAPI_CALL xrPollEvent(XrInstance instance, XrEventDataBuffer *event) {
|
||||
mx_audio_initialize();
|
||||
PFN_xrPollEvent fn = (PFN_xrPollEvent)lookup(instance, "xrPollEvent");
|
||||
if (!fn) return XR_ERROR_FUNCTION_UNSUPPORTED;
|
||||
if (emulate_scene && event) {
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#define LOG(...) ((void)0)
|
||||
#include "audio_metadata_queue.h"
|
||||
static unsigned destroyed;
|
||||
static void destroy(void *p) {destroyed++;free(p);}
|
||||
static void (*vtable[2])(void*)={NULL,destroy};
|
||||
static void *parameter(size_t next_offset,void *next) {
|
||||
unsigned char *p=calloc(1,48);assert(p);
|
||||
memcpy(p,&(void*){vtable},8);memcpy(p+next_offset,&next,8);p[8]=255;
|
||||
return p;
|
||||
}
|
||||
static void exercise(size_t next_offset) {
|
||||
void *fourth=parameter(next_offset,NULL),*third=parameter(next_offset,fourth);
|
||||
void *second=parameter(next_offset,third),*first=parameter(next_offset,second),*head=first;
|
||||
unsigned char metadata[48]={0},object[120]={0},snapshot[24]={0};
|
||||
memcpy(metadata+8,&first,8);memcpy(metadata+32,&third,8);
|
||||
void *metadata_pointer=metadata;memcpy(object+104,&metadata_pointer,8);
|
||||
uint32_t two=2,one=1;memcpy(object+112,&two,4);
|
||||
void *objects[1]={object},*objects_pointer=objects;
|
||||
memcpy(snapshot,&one,4);memcpy(snapshot+16,&objects_pointer,8);mx_audio_objects=snapshot;
|
||||
unsigned before=destroyed;mx_audio_drain(&head,next_offset);
|
||||
assert(destroyed==before+2 && head==first);
|
||||
void *next;memcpy(&next,(unsigned char*)first+next_offset,8);assert(next==third);
|
||||
memcpy(&next,(unsigned char*)third+next_offset,8);assert(!next);
|
||||
// Audio can still consume and modify the queued metadata in this frame.
|
||||
((unsigned char*)first)[8]=0;((unsigned char*)third)[8]=0;
|
||||
mx_audio_drain(&head,next_offset);assert(destroyed==before+2);
|
||||
// Once the audio snapshot drops the references, both objects are reclaimed.
|
||||
memset(snapshot,0,4);mx_audio_drain(&head,next_offset);
|
||||
assert(destroyed==before+4 && !head);mx_audio_objects=NULL;
|
||||
}
|
||||
int main(void) {
|
||||
exercise(40);exercise(24);
|
||||
assert(destroyed==8);
|
||||
// No active frame preserves the original immediate reclamation behavior.
|
||||
void *head=parameter(40,NULL);mx_audio_drain(&head,40);assert(!head && destroyed==9);
|
||||
puts("Queued metadata survives live frame references; mixed queues unlink correctly; retired objects are reclaimed without leaks.");
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Exercise metadata retirement with synthetic frames, without game files."""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_audio_metadata_retirement(tmp_path):
|
||||
cc = shutil.which("cc") or shutil.which("gcc") or shutil.which("clang")
|
||||
if not cc:
|
||||
pytest.skip("Native metadata regression test needs a host C compiler")
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
binary = tmp_path / ("audio-metadata-test.exe" if os.name == "nt" else "audio-metadata-test")
|
||||
subprocess.run([cc, "-std=c11", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", str(root / "native/adapter"),
|
||||
str(root / "tests/fixtures/src/audio_metadata_test.c"), "-o", str(binary)], check=True)
|
||||
subprocess.run([str(binary)], check=True)
|
||||
@@ -54,6 +54,22 @@ def test_adapter_and_launcher(tmp_path, quest_manifest):
|
||||
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)
|
||||
assert ws.read_lib("libopenxr_loader_generic.so") == artifact(ws.abi, "libopenxr_loader_generic.so")
|
||||
assert ws.read_lib("libopenxr_loader_original.so") == b"original-loader"
|
||||
assert not patch.apply(ctx)
|
||||
|
||||
|
||||
def test_controller_models_adds_xrshim(tmp_path, quest_manifest):
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
Reference in new issue
Block a user