diff --git a/frame/openxr-compat/README.md b/frame/openxr-compat/README.md index 4d5eb60..5978979 100644 --- a/frame/openxr-compat/README.md +++ b/frame/openxr-compat/README.md @@ -238,7 +238,7 @@ from the binary; runtime execution of those paths remains unverified. Prebuilt library SHA-256: ``` -aace99b7bcdb3c79fa844d75a9483f8f4b16bd6e669fb3003515df8c673ce54b +479d31c374f137906e03f73209b581d65d7e6a41b8476e6425fa55a6aa40bd68 ``` APK SHA-256: diff --git a/frame/openxr-compat/compat_logic.h b/frame/openxr-compat/compat_logic.h index a5c6e57..9d94fbc 100644 --- a/frame/openxr-compat/compat_logic.h +++ b/frame/openxr-compat/compat_logic.h @@ -35,6 +35,7 @@ inline ExtensionPlan extensions(const Names& requested, const Names& available, } return p; } +// 1.1's grip_surface is XR_EXT_palm_pose's palm_ext renamed (same pose), not the grip pose. inline std::string rewritePath(std::string p, bool palm) { const std::string suffix = "/input/grip_surface/pose"; if (palm && (p == "/user/hand/left" + suffix || p == "/user/hand/right" + suffix)) diff --git a/frame/openxr-compat/layer.cpp b/frame/openxr-compat/layer.cpp index a5433a7..79b858c 100644 --- a/frame/openxr-compat/layer.cpp +++ b/frame/openxr-compat/layer.cpp @@ -121,7 +121,8 @@ XrResult XRAPI_CALL destroySession(XrSession s) try { XrResult XRAPI_CALL locateSpaces(XrSession s, const XrSpacesLocateInfo* in, XrSpaceLocations* out) try { auto d = get(s); if (!d) return XR_ERROR_HANDLE_INVALID; if (!d->locate) return XR_ERROR_FUNCTION_UNSUPPORTED; - LOG("xrLocateSpaces -> xrLocateSpacesKHR (core/KHR type aliases)"); + static std::once_flag logged; // called every frame + std::call_once(logged, [] { LOG("xrLocateSpaces -> xrLocateSpacesKHR (core/KHR type aliases)"); }); return frame::locate(d->locate, s, in, out); } GUARD_END XrResult XRAPI_CALL threadSettings(XrSession s, XrAndroidThreadTypeKHR type, uint32_t tid) try { @@ -226,7 +227,9 @@ XrResult XRAPI_CALL createLayer(const XrInstanceCreateInfo* info, const XrApiLay d->gipa = next; d->instance = *instance; d->destroy = proc(next, *instance, "xrDestroyInstance"); // A successful runtime instance must expose xrDestroyInstance. - if (!d->destroy) { LOG("invalid downstream: missing xrDestroyInstance"); return XR_ERROR_INITIALIZATION_FAILED; } + if (!d->destroy) { // nothing could ever destroy it, so don't hand it out + LOG("invalid downstream: missing xrDestroyInstance"); *instance = XR_NULL_HANDLE; return XR_ERROR_INITIALIZATION_FAILED; + } try { d->enabled = std::move(plan.downstream); d->stubbed = std::move(plan.stubbed); d->palm = frame::has(d->enabled, "XR_EXT_palm_pose"); diff --git a/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so b/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so index a58721a..608cdd1 100755 Binary files a/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so and b/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so differ diff --git a/tests/test_frame_apk_vr.py b/tests/test_frame_apk_vr.py index 186ae58..8b801bc 100644 --- a/tests/test_frame_apk_vr.py +++ b/tests/test_frame_apk_vr.py @@ -202,6 +202,20 @@ class VRTests(unittest.TestCase): z.writestr('classes.dex', b'') self.assertEqual(frame_android.xr_compat_files(str(flat)), {}) + def test_xr_compat_layer_missing(self): + with tempfile.TemporaryDirectory() as d: + apk = Path(d) / 'a.apk' + with zipfile.ZipFile(apk, 'w') as z: + z.writestr('lib/arm64-v8a/libopenxr_loader.so', b'') + with patch.object(frame_android, 'XR_COMPAT', d): + with self.assertRaisesRegex(frame_android.FrameError, 'build.sh'): + frame_android.xr_compat_files(str(apk)) + + def test_patch_rejects_corrupt_manifest_cleanly(self): + with patch.object(frame_android, 'apk_info', side_effect=struct.error('bad')): + with self.assertRaises(frame_android.FrameError): + frame_android.patch('x.apk', 'y.apk') + def test_install_adds_layer_to_vr_apps(self): base = {'package': 'org.test.vr', 'label': 'VR', 'abis': [], 'min_sdk': None, 'vr': True, 'vr_activity': True, 'launchable': True} diff --git a/ui/frame_android.py b/ui/frame_android.py index cec2e07..f709a9d 100644 --- a/ui/frame_android.py +++ b/ui/frame_android.py @@ -10,7 +10,7 @@ Python stdlib only. CLI: python3 ui/frame_android.py install APK [--vr|--flat] [--no-xr-compat] | info APK | patch SRC DST [--add NAME=PATH ...] list | launch PKG | stop PKG | remove PKG | probe PKG """ -import json, os, re, shlex, shutil, subprocess, sys, threading, time, zlib +import json, os, re, shlex, shutil, struct, subprocess, sys, threading, time, zlib import frame_apk import frame_host @@ -86,8 +86,11 @@ def xr_compat_files(apk_path): return {} add = {} for entry, rel in XR_COMPAT_FILES.items(): - with open(os.path.join(XR_COMPAT, rel), 'rb') as f: - add[entry] = f.read() + try: + with open(os.path.join(XR_COMPAT, rel), 'rb') as f: + add[entry] = f.read() + except OSError: + raise FrameError("the OpenXR compatibility layer isn't built; run frame/openxr-compat/build.sh") return add @@ -326,7 +329,7 @@ def patch(src, dst, add=None): result['patched'] = (['launcher'] if manifest != original else []) + \ (['openxr-compat'] if add and set(XR_COMPAT_FILES) <= set(add) else []) return result - except (OSError, ValueError, zipfile.BadZipFile, frame_apk.ApkError) as e: + except (OSError, ValueError, IndexError, struct.error, zipfile.BadZipFile, frame_apk.ApkError) as e: raise FrameError(str(e)) from e diff --git a/ui/frame_apk_sign.py b/ui/frame_apk_sign.py index 091820d..68580c5 100644 --- a/ui/frame_apk_sign.py +++ b/ui/frame_apk_sign.py @@ -126,15 +126,20 @@ def signing_key(path=None): f.flush() os.fsync(f.fileno()) try: - os.link(tmp, path) + os.link(tmp, path) # never replaces a key another process wrote first except FileExistsError: pass + except OSError: # no hard links (FAT/exFAT): plain rename + if not path.exists(): + os.replace(tmp, path) finally: - os.unlink(tmp) - os.chmod(path, 0o600) + if os.path.exists(tmp): + os.unlink(tmp) + os.chmod(path, 0o600) # Windows ignores this; the per-user app-data folder is the protection there key = json.loads(path.read_text()) if key['n'].bit_length() != 2048 or key['e'] != 65537: - raise ValueError('invalid cached APK signing key') + raise ValueError(f'invalid cached APK signing key; delete {path} to make a new one ' + '(re-signed apps then need reinstalling)') rsa_verify(b'key check', rsa_sign(b'key check', key), key['n'], key['e']) return key