Files
spoopyghosty0--frameport/native/build.py
T
spoopyghosty0 a294359a4e FrameBridge: emulate inverse alpha blending with passthrough (#329)
Star Wars: Beyond Victory's mixed reality showed the room on the game's
objects and black where the room should be. Unreal keeps 1 - coverage in
the scene alpha, and OVRPlugin chains XrCompositionLayerAlphaBlendFB
(colour ONE_MINUS_SRC_ALPHA / SRC_ALPHA) to the eye layer; the Frame's
runtime ignores the struct and blends with the plain texture alpha.

New adapter setting alpha_blend_emul (default on, Vulkan): while
passthrough is emulated and the projection layer asks for inverse
blending, the alpha of the eye image rectangles is inverted in place
(alpha-only blend pass, once per acquired image, queued ahead of the
runtime's submission). layer_debug now also logs the blend factors.
Headless on the Frame: factors (3,2/3,2) seen, inversion runs, 72 fps,
no rejected frames. Not yet checked in the headset.
2026-10-17 01:54:01 -04:00

471 lines
26 KiB
Python

#!/usr/bin/env python3
"""Rebuild the prebuilt native artifacts in ../artifacts (developers only; users never need this).
Everything is fetched on demand into native/.cache (git-ignored):
- Android NDK r27c (27.2.12479018) from Google's repository (checksummed via repository2-3.xml)
- OpenXR headers at the commits each component was written against (KhronosGroup/OpenXR-SDK)
- Temurin JDK 21 (javac) and Android build-tools (d8) for the Java stub classes
Then builds: FrameBridge adapter (arm64 + arm32), VrApi bridge, platform compat, language packs, GL shim, multiview twin interposer (glmv), oculusos stub dex, the
timefix OpenXR layer for Proton games (linux-arm64, glibc; the NDK's clang builds it freestanding), the
OculusHMDConnected helper for Rift games under Proton (win-x64 PE; the NDK's clang + lld-link, no Windows SDK), the
live view's hardware H.264 encoder fp_venc (linux-arm64-bin, static freestanding executable), and rewrites artifacts/SHA256SUMS. Run `frameport parity` afterwards to see which games change.
python native/build.py [--only adapter,bridge,compat,langpack,glshim,eglfmt,glmv,ovrtrace,dex,xrlayer,oculushmd,xrshim,vkshim,zinkfix,foveation,ovrpshim,tlskeys,vrsettings,venc,vroverlay_probe] [--ndk PATH]
"""
from __future__ import annotations
import argparse
import hashlib
import os
import platform
import re
import shutil
import subprocess
import struct
import sys
import urllib.request
import zipfile
from pathlib import Path
from xml.etree import ElementTree
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
CACHE = HERE / ".cache"
ART = Path(os.environ.get("FRAMEPORT_ARTIFACTS_OUT", ROOT / "artifacts"))
NDK_VERSION = "27.2.12479018" # r27c
REPO = "https://dl.google.com/android/repository/"
OPENXR = { # component -> OpenXR-SDK commit its headers came from
"adapter": "f2448a8797c85814aa892efc1ab8707900fbcc78",
"bridge": "7f9285bce1ce8b69bb75554bf788666579d0c35e",
"xrlayer": "f2448a8797c85814aa892efc1ab8707900fbcc78",
}
OPENXR_HEADERS = ("openxr.h", "openxr_platform.h", "openxr_platform_defines.h", "openxr_loader_negotiation.h")
def log(msg):
print(f"== {msg}", flush=True)
def fetch(url: str, dest: Path, sha1: str | None = None) -> Path:
if dest.exists():
return dest
dest.parent.mkdir(parents=True, exist_ok=True)
log(f"download {url}")
tmp = dest.with_suffix(dest.suffix + ".part")
with urllib.request.urlopen(url) as r, open(tmp, "wb") as f:
shutil.copyfileobj(r, f, 1 << 20)
if sha1 and hashlib.sha1(tmp.read_bytes()).hexdigest() != sha1:
raise SystemExit(f"checksum mismatch: {url}")
tmp.replace(dest)
return dest
def repo_archive(path_prefix: str) -> tuple[str, str]:
"""(url, sha1) of a package in Google's repository for this host OS."""
host = {"Linux": "linux", "Darwin": "macosx", "Windows": "windows"}[platform.system()]
idx = fetch(REPO + "repository2-3.xml", CACHE / "repository2-3.xml")
root = ElementTree.fromstring(idx.read_text())
for pkg in root.iter("remotePackage"):
if pkg.get("path") != path_prefix:
continue
for a in pkg.iter("archive"):
if a.findtext("host-os") in (host, None):
return REPO + a.findtext("complete/url"), a.findtext("complete/checksum")
raise SystemExit(f"{path_prefix} not found in the Android repository index")
def ndk(explicit: str | None) -> Path:
if explicit:
return Path(explicit)
dest = CACHE / f"ndk-{NDK_VERSION}"
if not (dest / "source.properties").exists():
url, sha1 = repo_archive(f"ndk;{NDK_VERSION}")
z = fetch(url, CACHE / Path(url).name, sha1)
log("extract NDK")
with zipfile.ZipFile(z) as zf:
zf.extractall(CACHE / "ndk-extract")
top = next((CACHE / "ndk-extract").iterdir())
shutil.move(str(top), str(dest)) # (a plain rename can fail on NTFS while the folder is being scanned)
shutil.rmtree(CACHE / "ndk-extract", ignore_errors=True)
restore_symlinks(z, dest)
for p in (dest / "toolchains/llvm/prebuilt").rglob("bin/*"):
p.chmod(0o755)
return dest
def restore_symlinks(archive: Path, dest: Path) -> None:
"""zipfile writes symlinks (clang -> clang-18, ...) as small text files; replace them with copies of their
targets (copies also work on filesystems without symlinks)."""
with zipfile.ZipFile(archive) as zf:
links = [i for i in zf.infolist() if (i.external_attr >> 16) & 0o170000 == 0o120000]
for _ in range(3): # links to links
for info in links:
rel = Path(*Path(info.filename).parts[1:])
path = dest / rel
target = (path.parent / zf.read(info).decode()).resolve()
if target.is_file() and (not path.exists() or path.stat().st_size < 256):
path.unlink(missing_ok=True)
shutil.copy2(target, path)
def clang_dir(ndk_root: Path) -> Path:
host = {"Linux": "linux-x86_64", "Darwin": "darwin-x86_64", "Windows": "windows-x86_64"}[platform.system()]
return ndk_root / "toolchains/llvm/prebuilt" / host
def openxr_include(component: str) -> Path:
commit = OPENXR[component]
inc = CACHE / f"openxr-{commit[:10]}"
for h in OPENXR_HEADERS:
fetch(f"https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK/{commit}/include/openxr/{h}", inc / "openxr" / h)
return inc
def run(cmd, cwd=None):
cmd = [str(c) for c in cmd]
p = subprocess.run(cmd, cwd=cwd)
if p.returncode:
raise SystemExit(f"failed: {' '.join(cmd[:3])} …")
def exe(tc: Path, name: str) -> Path:
return tc / "bin" / (name + (".cmd" if os.name == "nt" and "clang" in name and "-" in name else ""))
def build_adapter(tc: Path):
src = HERE / "adapter"
shader_tools = tc.parents[3] / "shader-tools" / tc.name
suffix = ".exe" if os.name == "nt" else ""
for header, shaders in (("surface_composite", ("vert", "frag")), ("alpha_blend", ("vert", "frag"))):
names = " and ".join(f"{header}.{stage}" for stage in shaders)
sections = [f"// Generated from {names}.\n"]
for stage in shaders:
binary = CACHE / f"{header}_{stage}.spv"
run([shader_tools / ("glslc" + suffix), "--target-env=vulkan1.0", "-O",
src / f"{header}.{stage}", "-o", binary])
run([shader_tools / ("spirv-val" + suffix), "--target-env", "vulkan1.0", binary])
data = binary.read_bytes()
words = struct.unpack(f"<{len(data)//4}I", data)
sections.append(f"static const uint32_t {header}_{stage}_spv[] = {{\n" +
"\n".join(" " + ",".join(f"0x{v:08x}" for v in words[i:i+8]) + ","
for i in range(0, len(words), 8)) + "\n};\n")
(src / f"{header}_spv.h").write_text("".join(sections), encoding="utf-8")
run([sys.executable, src / "gen_forwarders.py"])
inc = openxr_include("adapter")
common = ["-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror", "-Wno-gnu-conditional-omitted-operand",
"-Wno-missing-field-initializers", "-Wl,-Bsymbolic", "-Wl,-soname,libopenxr_loader_generic.so", "-I", inc]
run([exe(tc, "aarch64-linux-android29-clang"), *common, "-Wl,-z,max-page-size=16384", "frame_adapter.c", "forwarders.S",
"-ldl", "-llog", "-lm", "-o", ART / "arm64-v8a/libopenxr_loader_generic.so"], cwd=src)
run([exe(tc, "armv7a-linux-androideabi29-clang"), *common, "frame_adapter.c", "forwarders_arm32.S", "-ldl", "-llog", "-lm",
"-o", ART / "armeabi-v7a/libopenxr_loader_generic.so"], cwd=src)
def build_xrlayer(tc: Path):
"""OpenXR API layer for Linux aarch64 (glibc), loaded by Proton's wineopenxr host loader on the Frame. Freestanding:
no libc headers or link-time libs; clock_gettime binds to the process's libc at load time."""
src = HERE / "xrlayer"
inc = openxr_include("xrlayer")
out = ART / "linux-arm64"
out.mkdir(parents=True, exist_ok=True)
run([tc / "bin/clang", "--target=aarch64-linux-gnu", "-ffreestanding", "-nostdlibinc", "-fno-stack-protector",
"-fvisibility=hidden", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror", "-I", src / "include", "-I", inc,
f"-ffile-prefix-map={HERE}=native", "-shared", "-nostdlib", "-fuse-ld=lld", "-Wl,--build-id=none",
"-Wl,-z,max-page-size=65536", "-Wl,--hash-style=both", "-Wl,-soname,libxr_frameport_timefix.so",
"timefix_layer.c", "-o", out / "libxr_frameport_timefix.so"], cwd=src)
shutil.copy(src / "XR_APILAYER_FRAMEPORT_timefix.json", out / "XR_APILAYER_FRAMEPORT_timefix.json")
def uapi_arm64(tc: Path) -> Path:
"""Only the kernel UAPI headers of the NDK sysroot (linux/, asm-generic/, drm/, aarch64 asm/), rebuilt every time,
so no bionic libc header can end up on a freestanding build's include path."""
inc = tc / "sysroot/usr/include"
dest = CACHE / "uapi-arm64"
shutil.rmtree(dest, ignore_errors=True)
for d in ("linux", "asm-generic", "drm"):
shutil.copytree(inc / d, dest / d)
shutil.copytree(inc / "aarch64-linux-android/asm", dest / "asm")
return dest
def build_venc(tc: Path):
"""fp_venc (Linux aarch64, runs on the Frame): SteamVR headset view -> V4L2 hardware H.264 encoder -> stdout, for the
live view. Fully static and freestanding (raw syscalls, own _start), so it needs no libc at build or run time.
Goes to linux-arm64-bin/, not linux-arm64/ (that folder is uploaded whole as the OpenXR layer)."""
src = HERE / "venc"
uapi = uapi_arm64(tc)
out = ART / "linux-arm64-bin"
out.mkdir(parents=True, exist_ok=True)
run([tc / "bin/clang", "--target=aarch64-linux-gnu", "-ffreestanding", "-nostdlib", "-nostdlibinc", "-static",
"-fno-stack-protector", "-fno-builtin", "-O2", "-Wall", "-Wextra", "-Werror", "-fuse-ld=lld",
"-Wl,--build-id=none", "-Wl,-z,max-page-size=65536", "-e", "_start", "-I", src / "include", "-I", uapi,
f"-ffile-prefix-map={HERE}=native", "fp_venc.c", "convert.c", "sys.c", "-o", out / "fp_venc"], cwd=src)
OCULUSHMD_IMPORTS = ("CreateEventW", "CreateJobObjectW", "AssignProcessToJobObject", "QueryInformationJobObject",
"CreateProcessW", "ResumeThread", "WaitForSingleObject", "GetExitCodeProcess", "CloseHandle",
"Sleep", "GetCommandLineW", "GetLastError", "GetStdHandle", "WriteFile", "ExitProcess")
def build_oculushmd(tc: Path, out: Path | None = None, defines=()):
"""fp_oculushmd.exe (Windows x64, run by Proton on the Frame): provides the OculusHMDConnected event for Unreal
Rift games. Freestanding: no CRT; the kernel32 import library is generated from a .def by llvm-dlltool."""
src = HERE / "oculushmd"
out = out or ART / "win-x64"
out.mkdir(parents=True, exist_ok=True)
work = CACHE / "oculushmd"
work.mkdir(parents=True, exist_ok=True)
(work / "kernel32.def").write_text("LIBRARY kernel32.dll\nEXPORTS\n" + "".join(f" {n}\n" for n in OCULUSHMD_IMPORTS))
run([tc / "bin/llvm-dlltool", "-m", "i386:x86-64", "-d", work / "kernel32.def", "-l", work / "kernel32.lib"])
run([tc / "bin/clang", "--target=x86_64-pc-windows-msvc", "-ffreestanding", "-nostdlibinc", "-fno-stack-protector",
"-fno-builtin", "-O2", "-Wall", "-Wextra", "-Werror", f"-ffile-prefix-map={HERE}=native", *defines, "-c",
"fp_oculushmd.c", "-o", work / "fp_oculushmd.obj"], cwd=src)
run([tc / "bin/lld-link", "/nologo", "/Brepro", "/nodefaultlib", "/entry:start", "/subsystem:console",
"/dynamicbase", "/highentropyva", "/nxcompat", work / "fp_oculushmd.obj", work / "kernel32.lib",
f"/out:{out / 'fp_oculushmd.exe'}"])
VRSETTINGS_IMPORTS = ("LoadLibraryA", "GetProcAddress", "GetCommandLineA", "GetStdHandle", "WriteFile", "ExitProcess")
def build_vrsettings(tc: Path):
"""fp_vrsettings.exe (Windows x64, run on the PC): SteamVR settings through OpenVR's IVRSettings (see the source)."""
src = HERE / "vrsettings"
out = ART / "win-x64"
out.mkdir(parents=True, exist_ok=True)
work = CACHE / "vrsettings"
work.mkdir(parents=True, exist_ok=True)
(work / "kernel32.def").write_text("LIBRARY kernel32.dll\nEXPORTS\n" + "".join(f" {n}\n" for n in VRSETTINGS_IMPORTS))
run([tc / "bin/llvm-dlltool", "-m", "i386:x86-64", "-d", work / "kernel32.def", "-l", work / "kernel32.lib"])
run([tc / "bin/clang", "--target=x86_64-pc-windows-msvc", "-ffreestanding", "-nostdlibinc", "-fno-stack-protector",
"-fno-builtin", "-O2", "-Wall", "-Wextra", "-Werror", f"-ffile-prefix-map={HERE}=native", "-c",
"fp_vrsettings.c", "-o", work / "fp_vrsettings.obj"], cwd=src)
run([tc / "bin/lld-link", "/nologo", "/Brepro", "/nodefaultlib", "/entry:start", "/subsystem:console",
"/dynamicbase", "/highentropyva", "/nxcompat", work / "fp_vrsettings.obj", work / "kernel32.lib",
f"/out:{out / 'fp_vrsettings.exe'}"])
VROVERLAY_IMPORTS = VRSETTINGS_IMPORTS + ("Sleep",)
def build_vroverlay_probe(tc: Path):
"""fp_vroverlay_probe.exe (Windows x64 diagnostic, run under Proton on the Frame): does an OpenVR overlay app show
up? Not shipped: written to native/.cache/vroverlay_probe/ (opt-in: --only vroverlay_probe)."""
src = HERE / "vroverlay_probe"
work = CACHE / "vroverlay_probe"
work.mkdir(parents=True, exist_ok=True)
(work / "kernel32.def").write_text("LIBRARY kernel32.dll\nEXPORTS\n" + "".join(f" {n}\n" for n in VROVERLAY_IMPORTS))
run([tc / "bin/llvm-dlltool", "-m", "i386:x86-64", "-d", work / "kernel32.def", "-l", work / "kernel32.lib"])
run([tc / "bin/clang", "--target=x86_64-pc-windows-msvc", "-ffreestanding", "-nostdlibinc", "-fno-stack-protector",
"-fno-builtin", "-O2", "-Wall", "-Wextra", "-Werror", f"-ffile-prefix-map={HERE}=native", "-c",
"fp_vroverlay_probe.c", "-o", work / "fp_vroverlay_probe.obj"], cwd=src)
run([tc / "bin/lld-link", "/nologo", "/Brepro", "/nodefaultlib", "/entry:start", "/subsystem:console",
"/dynamicbase", "/highentropyva", "/nxcompat", work / "fp_vroverlay_probe.obj", work / "kernel32.lib",
f"/out:{work / 'fp_vroverlay_probe.exe'}"])
def build_bridge(tc: Path, output="libvrapi.so"):
src = HERE / "vrapi-bridge"
inc = openxr_include("bridge")
run([tc / "bin/clang++", "--target=aarch64-linux-android29", f"--sysroot={tc / 'sysroot'}", "-std=c++17", "-O2", "-g",
"-fPIC", f"-ffile-prefix-map={src}=native/vrapi-bridge", f"-ffile-prefix-map={CACHE}=native/.cache",
f"-ffile-prefix-map={CACHE.resolve()}=native/.cache", # a symlinked cache (worktrees)
"-fvisibility=hidden", "-fvisibility-inlines-hidden", "-Wall", "-Wextra",
"-Wno-missing-field-initializers", "-Werror=return-type", "-DXR_NO_PROTOTYPES", "-DXR_USE_PLATFORM_ANDROID",
"-DXR_USE_GRAPHICS_API_VULKAN", "-DXR_USE_GRAPHICS_API_OPENGL_ES", "-DXR_USE_TIMESPEC", f"-I{inc}", "-shared",
"-static-libstdc++", "runtime.cpp", "graphics.cpp", "input.cpp", "-Wl,--no-undefined",
"-Wl,-z,max-page-size=16384", "-Wl,--version-script=exports.map", "-llog", "-landroid", "-lvulkan", "-lEGL",
"-lGLESv3", "-ldl", "-o", ART / f"arm64-v8a/{output}"], cwd=src)
def build_compat(tc: Path):
src = HERE / "platformcompat"
run([tc / "bin/clang++", "--target=aarch64-linux-android29", f"--sysroot={tc / 'sysroot'}", "-std=c++17", "-O2",
"-fPIC", "-fvisibility=hidden", "-fno-exceptions", "-fno-rtti", "-ffunction-sections", "-fdata-sections",
"-Wall", "-Wextra", "-Werror=return-type", "-shared", "-nostdlib++", "message_type.cpp", "-Wl,--no-undefined",
"-Wl,--gc-sections", "-Wl,-z,max-page-size=16384", "-Wl,-soname,libovrplatformcompat.so",
"-Wl,--version-script=exports.map", "-o", ART / "arm64-v8a/libovrplatformcompat.so"], cwd=src)
def build_langpack(tc: Path):
"""Language packs from the game's files (see langpack/langpack.c): DT_NEEDED-injected into overport's platform loader."""
src = HERE / "langpack"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", f"-ffile-prefix-map={src}=native/langpack", "-Wl,--no-undefined",
"-Wl,-z,max-page-size=16384", "-Wl,-soname,libfp_langpack.so", "langpack.c", "-ldl", "-llog",
"-o", ART / "arm64-v8a/libfp_langpack.so"], cwd=src)
def build_glshim(tc: Path):
src = HERE / "glshim"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-function", "-Wno-incompatible-pointer-types", "-fvisibility=hidden", "-Wl,-soname,libglshim.so",
"-Wl,-z,max-page-size=16384", "glshim.c", "-ldl", "-llog", "-o", ART / "arm64-v8a/libglshim.so"], cwd=src)
def build_eglfmt(tc: Path):
"""Depth-format shim (see glshim/eglfmt.c) that GL-to-GLES wrappers (QuestCraft's LTW) load in place of libEGL.so.
Named libfpg.so: no longer than "libEGL.so", whose dlopen string in the wrapper is rewritten in place. Linked to
libEGL so a dlsym on its handle still finds every EGL function."""
src = HERE / "glshim"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", "-Wl,-soname,libfpg.so", "-Wl,-z,max-page-size=16384", "eglfmt.c",
"-Wl,--no-as-needed", "-lEGL", "-Wl,--as-needed", "-ldl", "-llog", "-o", ART / "arm64-v8a/libfpg.so"], cwd=src)
def build_glmv(tc: Path):
"""Single-view draws of OVR_multiview programs (see glmv/glmv.c, frame.gl_multiview_fbo). Named libfpglmv.so: as
long as "libGLESv3.so", whose dlopen string in the engine is rewritten in place. Linked to libGLESv3 so a dlsym on
its handle still finds every GL function it doesn't wrap."""
src = HERE / "glmv"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", f"-ffile-prefix-map={src}=native/glmv", "-Wl,-soname,libfpglmv.so",
"-Wl,-z,max-page-size=16384", "glmv.c", "-Wl,--no-as-needed", "-lGLESv3", "-Wl,--as-needed", "-lEGL", "-ldl",
"-llog", "-o", ART / "arm64-v8a/libfpglmv.so"], cwd=src)
def build_ovrtrace(tc: Path):
"""Meta Platform SDK tracer (see ovrtrace/ovrtrace.c): diagnostics, the game's engine library loads it first."""
src = HERE / "ovrtrace"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-Wl,-soname,libfp_ovrtrace.so", "-Wl,-z,max-page-size=16384", "ovrtrace.c", "-ldl", "-llog",
"-o", ART / "arm64-v8a/libfp_ovrtrace.so"], cwd=src)
def build_xrshim(tc: Path):
"""FrameBridge extension shim (see xrshim/xrshim.c): DT_NEEDED-injected in front of overport's libopenxr_loader.so."""
src = HERE / "xrshim"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", "-I", openxr_include("adapter"), "-Wl,-soname,libframe_xrshim.so",
"-Wl,-z,max-page-size=16384", "xrshim.c", "-ldl", "-llog", "-o", ART / "arm64-v8a/libframe_xrshim.so"], cwd=src)
def build_vkshim(tc: Path):
"""Vulkan shim (see vkshim/vkshim.c): the engine's dlopen("libvulkan.so") string is pointed at it."""
src = HERE / "vkshim"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", "-Wl,-soname,libfp_vk.so", "-Wl,-z,max-page-size=16384", "vkshim.c",
"-Wl,--no-as-needed", "-lvulkan", "-Wl,--as-needed", "-ldl", "-llog", "-o", ART / "arm64-v8a/libfp_vk.so"],
cwd=src)
def build_zinkfix(tc: Path):
"""Shader-fix Vulkan layer for OpenGL ES games (see zinkfix/zinkfix.c): between Zink and the driver."""
src = HERE / "zinkfix"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", "-Wl,-soname,libVkLayer_fp_shaderfix.so", "-Wl,-z,max-page-size=16384", "zinkfix.c",
"-ldl", "-llog", "-o", ART / "arm64-v8a/libVkLayer_fp_shaderfix.so"], cwd=src)
def build_foveation(tc: Path):
"""Optional coarse-fragment visualization, injected into a game's renderer."""
build_bridge(tc, "libvrapi_foveation.so")
src = HERE / "foveation"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", f"-ffile-prefix-map={src}=native/foveation",
"-Wl,-soname,libVkLayer_fdm_darken.so", "-Wl,-z,max-page-size=16384", "darken.c",
"-ldl", "-llog", "-o", ART / "arm64-v8a/libVkLayer_fdm_darken.so"], cwd=src)
def build_ovrpshim(tc: Path):
"""OVRPlugin frame-loop shim (see ovrpshim/ovrpshim.c) for Unity 2017-2018 built-in Oculus support."""
src = HERE / "ovrpshim"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", "-Wl,-soname,libfp_ovrp.so", "-Wl,-z,max-page-size=16384", "ovrpshim.c", "-ldl",
"-llog", "-o", ART / "arm64-v8a/libfp_ovrp.so"], cwd=src)
def build_tlskeys(tc: Path):
"""Thread-key pool for Unity (see tlskeys/tlskeys.c, patch frame.unity_tls_key_pool)."""
src = HERE / "tlskeys"
run([exe(tc, "aarch64-linux-android29-clang"), "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror",
"-fvisibility=hidden", f"-ffile-prefix-map={HERE}=native", "-Wl,-soname,libfp_tkeys.so",
"-Wl,-z,max-page-size=16384", "tlskeys.c", "-llog", "-o", ART / "arm64-v8a/libfp_tkeys.so"], cwd=src)
def build_luajit(tc: Path):
run([sys.executable, HERE / "luajit/build.py", "--clang-dir", tc / "bin",
"--out", ART / "arm64-v8a/libfp_luajit.so"])
def build_a32(tc: Path):
"""The 32-bit ARM bridge (a32translator/bridge, patch frame.a32_bridge): its own build script, then the three
files every bridged game ships (liba32rt.so is 32-bit ARM code: it runs inside the translator)."""
bridge = HERE / "a32translator" / "bridge"
run([sys.executable, bridge / "build.py", "--ndk", tc.parents[3]], cwd=bridge)
for name in ("liba32bridge.so", "liba32rt.so", "liba32stub.so"):
shutil.copyfile(HERE / "a32translator/out/bridge" / name, ART / "arm64-v8a" / name)
def build_dex():
javac = shutil.which("javac")
if not javac:
raise SystemExit("javac not found: install a JDK (e.g. Temurin 21) to rebuild the Java stubs")
url, sha1 = repo_archive(next(p for p in _build_tools_paths()))
bt = fetch(url, CACHE / Path(url).name, sha1)
d8 = CACHE / "d8.jar"
if not d8.exists():
with zipfile.ZipFile(bt) as z:
d8.write_bytes(z.read(next(m for m in z.namelist() if m.endswith("lib/d8.jar"))))
out = CACHE / "java-classes"
shutil.rmtree(out, ignore_errors=True)
# compile-only stand-ins for the Android classes the stubs reference (not dexed; the device provides them)
android = CACHE / "android-stubs"
(android / "android/content").mkdir(parents=True, exist_ok=True)
(android / "android/content/Context.java").write_text("package android.content;\npublic abstract class Context {}\n")
android_out = CACHE / "android-classes"
run([javac, "--release", "8", "-d", android_out, android / "android/content/Context.java"])
srcs = sorted((HERE / "java-stubs").rglob("*.java"))
run([javac, "--release", "8", "-cp", android_out, "-d", out, *srcs])
classes = sorted(out.rglob("*.class"))
dexdir = CACHE / "dex"
shutil.rmtree(dexdir, ignore_errors=True)
dexdir.mkdir(parents=True)
run(["java", "-cp", d8, "com.android.tools.r8.D8", "--release", "--min-api", "24", "--output", dexdir, *classes])
shutil.copy(dexdir / "classes.dex", ART / "dex/oculusos-stubs.dex")
def _build_tools_paths():
idx = ElementTree.fromstring(fetch(REPO + "repository2-3.xml", CACHE / "repository2-3.xml").read_text())
paths = [p.get("path") for p in idx.iter("remotePackage") if p.get("path", "").startswith("build-tools;")
and "rc" not in p.get("path")]
return sorted(paths, key=lambda s: [int(x) for x in re.findall(r"\d+", s)], reverse=True)
def write_sums():
lines = []
for f in sorted(p for p in ART.rglob("*") if p.is_file() and p.name != "SHA256SUMS"):
lines.append(f"{hashlib.sha256(f.read_bytes()).hexdigest()} ./{f.relative_to(ART).as_posix()}")
(ART / "SHA256SUMS").write_text("\n".join(lines) + "\n")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--only", default="adapter,bridge,compat,langpack,glshim,eglfmt,glmv,ovrtrace,dex,xrlayer,oculushmd,xrshim,vkshim,zinkfix,foveation,ovrpshim,tlskeys,luajit,vrsettings,venc,a32")
ap.add_argument("--ndk")
args = ap.parse_args()
parts = set(args.only.split(","))
for d in ("arm64-v8a", "armeabi-v7a", "dex"):
(ART / d).mkdir(parents=True, exist_ok=True)
tc = clang_dir(ndk(args.ndk)) if parts - {"dex"} else None
steps = {"adapter": lambda: build_adapter(tc), "bridge": lambda: build_bridge(tc), "compat": lambda: build_compat(tc),
"langpack": lambda: build_langpack(tc),
"glshim": lambda: build_glshim(tc), "eglfmt": lambda: build_eglfmt(tc), "glmv": lambda: build_glmv(tc), "ovrtrace": lambda: build_ovrtrace(tc), "xrshim": lambda: build_xrshim(tc), "dex": build_dex, "xrlayer": lambda: build_xrlayer(tc),
"oculushmd": lambda: build_oculushmd(tc), "vkshim": lambda: build_vkshim(tc),
"zinkfix": lambda: build_zinkfix(tc),
"foveation": lambda: build_foveation(tc),
"ovrpshim": lambda: build_ovrpshim(tc), "tlskeys": lambda: build_tlskeys(tc),
"luajit": lambda: build_luajit(tc),
"vrsettings": lambda: build_vrsettings(tc), "venc": lambda: build_venc(tc),
"vroverlay_probe": lambda: build_vroverlay_probe(tc), "a32": lambda: build_a32(tc)}
for name in ("adapter", "bridge", "compat", "langpack", "glshim", "eglfmt", "glmv", "ovrtrace", "xrshim", "vkshim", "zinkfix", "foveation", "ovrpshim", "tlskeys", "luajit", "dex", "xrlayer", "oculushmd", "vrsettings", "venc",
"vroverlay_probe", "a32"):
if name in parts:
log(f"build {name}")
steps[name]()
write_sums()
log("artifacts/SHA256SUMS updated")
if __name__ == "__main__":
main()