Files
spoopyghosty0--frameport/native/a32translator/bridge/convert.py
T
UnicronNLandClaude Opus 5.5 626950e52e Gunjack runs: original APK signature for UE4's check, GLES session kept across VR re-entry
Gunjack (CCP, UE 4.10, Gear VR) quit after loading because AndroidMain compares
GameActivity.AndroidThunkJava_GetSignature() with the original signing
certificate, and then crashed in the Frame's runtime when it entered VR again.

a32 bridge:
- frame.a32_bridge: original_signature() = Base64.DEFAULT(SHA-1(signing
  certificate DER)) of the original APK (META-INF/*.RSA|DSA|EC), stored as
  assets/fp_a32_signature.txt for libUE4.so games (convert(), shared with
  bridge/convert.py)
- the bridge loads it at activity creation and answers guest calls on the
  method ID of AndroidThunkJava_GetSignature()Ljava/lang/String; with it
  (replaces the unused hex/OpenSSL prototype)

VrApi bridge: after loading, Unreal leaves VR mode and enters it again on the
same GL context; the first frame of a second OpenXR session there aborted in
vrclient (CSharedSemaphoreLinux::WaitFromGPU from the GL binding's
PreBeginFrame). vrapi_LeaveVrMode now keeps a running GLES session and
vrapi_EnterVrMode reuses it for the same context (another context still gets
a new session; Vulkan unchanged). upstream-patches/runtime.cpp.patch updated,
libvrapi.so and liba32bridge.so rebuilt.

Verified on the Frame: headless launch test passes (72 fps after re-entry);
owner-verified in the headset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:45:43 +02:00

71 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Converts a 32-bit-only APK to run on arm64 through the bridge, for bridge development (FramePort builds do the
same with the patch frame.a32_bridge, src/frameport/patches/frame/a32_bridge.py, which this script uses):
lib/armeabi-v7a/libX.so -> lib/arm64-v8a/libX.a32.so (the guest code) + lib/arm64-v8a/libX.so (the stub)
+ lib/arm64-v8a/liba32bridge.so, liba32rt.so; other ABIs dropped; extractNativeLibs forced on; signed with
FramePort's key for the package; INFO-only start activities get LAUNCHER (Lepton needs it); targetSdkVersion
raised to 23 (older ones get a dialog that takes the focus).
uv run python native/a32translator/bridge/convert.py IN.apk OUT.apk [--replace libvrapi.so=FILE ...]
Build the bridge first (python native/a32translator/bridge/build.py). --replace puts FILE (a 32-bit library) in
place of the APK's own copy.
"""
import argparse
import os
import sys
import tempfile
from pathlib import Path
HERE = Path(__file__).resolve().parent
OUT = HERE.parent / "out" / "bridge"
REPO = HERE.parents[2]
# test builds instead of the artifacts: A32_VRAPI_BRIDGE (the 64-bit VrApi bridge), A32_FRAME_SETTINGS (FrameBridge
# settings, "key=value;..."); the bridge itself always comes from out/bridge (this script is for bridge development)
VRAPI_BRIDGE = Path(os.environ.get("A32_VRAPI_BRIDGE") or REPO / "artifacts/arm64-v8a/libvrapi.so")
FRAME_SETTINGS = (b"scale=1.0\nfoveation_fix=1\ncontroller_fix=1\n"
+ os.environ.get("A32_FRAME_SETTINGS", "").replace(";", "\n").encode())
def main():
ap = argparse.ArgumentParser()
ap.add_argument("apk")
ap.add_argument("out")
ap.add_argument("--replace", action="append", default=[], metavar="LIB=FILE")
a = ap.parse_args()
sys.path.insert(0, str(REPO / "src"))
from pyaxmlparser import APK
from frameport.apk import axml, sign
from frameport.apk.workspace import ApkWorkspace
from frameport.core.events import Reporter
from frameport.patches.frame import a32_bridge
from frameport.tools import overport
package = APK(a.apk).package
replace = {k: Path(v).read_bytes() for k, v in (r.split("=", 1) for r in a.replace)}
def prebuilt(name: str) -> bytes:
if name == "libvrapi.so":
return VRAPI_BRIDGE.read_bytes()
return (OUT / name if name.startswith("liba32") else REPO / "artifacts/arm64-v8a" / name).read_bytes()
def ovrport(name: str, abi: str) -> bytes:
return overport.ensure_runtime_lib(name, abi, Reporter()).read_bytes()
with ApkWorkspace(Path(a.apk)) as ws, tempfile.TemporaryDirectory() as td:
libs = a32_bridge.convert(ws, prebuilt, ovrport, replace)
if ws.has(a32_bridge.SIGNATURE_ASSET):
print("original APK signature for the UE4 check:", ws.read(a32_bridge.SIGNATURE_ASSET).decode().strip())
manifest = ws.read("AndroidManifest.xml")
ws.put("AndroidManifest.xml", axml.fix_launcher(manifest) or manifest) # Lepton starts the LAUNCHER activity
if ws.has(ws.lib("libopenxr_loader_generic.so")): # FrameBridge's settings (frame.adapter in a build)
ws.put(ws.lib("libframe_settings.so"), FRAME_SETTINGS)
unsigned = Path(td) / "unsigned.apk"
ws.write(unsigned)
sign.sign(unsigned, Path(a.out), package)
print(f"{a.out}: {package}, {len(libs)} libraries converted")
if __name__ == "__main__":
main()