From 098bd37f355a77abbb5751fc55a91f7ebe74cc6b Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Tue, 3 Nov 2026 20:01:42 +0000 Subject: use patch generator to build patchers --- steamvr/patch-steamvr | 216 -------------------------------------------------- 1 file changed, 216 deletions(-) delete mode 100755 steamvr/patch-steamvr (limited to 'steamvr/patch-steamvr') diff --git a/steamvr/patch-steamvr b/steamvr/patch-steamvr deleted file mode 100755 index f176b0a..0000000 --- a/steamvr/patch-steamvr +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski -# SPDX-License-Identifier: CC0-1.0 - -"""A steamvr launch command wrapper that patches various parts of steamvr -to fix bugs. - -For SteamVR Beta 2.19.1, build 26389675. - -Use at your own risk! -""" - -import hashlib -import os -import stat -import subprocess -import sys -import tempfile -from dataclasses import dataclass -from pathlib import Path - -NAME = Path(__file__).name -MARKER = b"Valve, please fix." - - -@dataclass(frozen=True) -class Edit: - offset: int - replacement: bytes - - -@dataclass(frozen=True) -class Patch: - original_hash: bytes - patched_hash: bytes - edits: tuple[Edit, ...] - - def apply_to(self, data: bytes) -> bytes: - payload = data.removesuffix(MARKER) - digest = sha256(payload) - if digest == self.patched_hash: - return payload + MARKER - if digest != self.original_hash: - if payload != data: - raise LookupError( - "already patched with a another set of patches: verify file integrity and restart" - ) - raise LookupError("unrecognized build: this wrapper may need updating") - result = bytearray(payload) - for edit in self.edits: - end = edit.offset + len(edit.replacement) - if edit.offset < 0 or end > len(result): - raise ValueError("patch extends outside the file") - result[edit.offset : end] = edit.replacement - if sha256(result) != self.patched_hash: - raise ValueError("patched file checksum mismatch") - return bytes(result) + MARKER - - -def patch( - original_hash: str, patched_hash: str, edits: tuple[tuple[int, str], ...] -) -> Patch: - return Patch( - bytes.fromhex(original_hash), - bytes.fromhex(patched_hash), - tuple( - Edit(offset, bytes.fromhex(replacement)) for offset, replacement in edits - ), - ) - - -PATCHES = { - "bin/linux64/vrcompositor": patch( - "4c380adc27832db9afaf83e446080354338bcb4c96f0f0cc561c90cd4d79168f", - "a6d073c17489919e838c3d2ac4941a923bc18172a372f3c32bae3433e95f9dbc", - ( - # Wait for GPU to finish using its resources (see #952) - (0x404941, "84c00f842f570000eb06"), - (0x404951, "488dbbe00d0000e90a570000"), - (0x40A05B, "e830db0600"), - (0x40A060, "e9dca8ffff9090"), - # Stop reusing GPU work identifier across frames - # (allows above patch to work reliably) - (0x41D224, "31c09090909090"), - (0x41DB39, "31c09090909090"), - (0x41DB74, "909090909090"), - # Fix shader descriptor texture array counts (should be 3, not 1) - # for bindings 9, 17, and 20. Affected shaders: - # - distort_none_ps - # - distort_none_ps_layered - # - distort_panorama_ps - # - distort_panorama_solid_color_ps - # - distort_ps_achromatic_nd - (0x404961, "498b5500b80300000089424ceb22"), - (0x404991, "89828c0000008982a4000000eb12"), - (0x4049B1, "4c8bbd18feffffe902200100"), - (0x4169B8, "e9a4dffeff9090"), - ), - ), - "bin/linux64/vrserver": patch( - "a7c9246f8fe36749caa0bfe3984efbb5fa7dbbb28e5263a5c1744f5ba6b0c10b", - "b14314f1fbfe8acb164583853e32c9a6870317bb9cbb278d9cea21862aaebd8f", - ( - # Remove logger calls in forked processes (can cause a deadlock and - # make the process linger preventing SteamVR from starting again) - (0x2C1386, "9090909090"), - (0x2C1577, "9090909090"), - (0x2C160C, "9090909090"), - (0x2C1726, "9090909090"), - (0x2C17FF, "9090909090"), - (0x2C1699, "9090909090"), - ), - ), - "drivers/vrlink/bin/linux64/driver_vrlink.so": patch( - "a33d1da2d1c9851a61e19b87ffc89bbbb6e8b1e503e189e3e19bccd2e6cd3b7f", - "c7d8adbbcd44d577f7891c15e53d6000eaf0b257fde642b09ff286bd00be9468", - ( - # Keep increasing (mod(2^16)) the ACK cutoff instead of clearing it - # (#965) - (0xD91DD, "89836c0d000090909090"), - # Wait for encoder to finish before reconfiguring it - # (ulVideoTimeline == ulTimelineSequence) (#965) - (0x13C2D2, "83bb4c090000007458e9702c0000"), - (0x13C333, "488b8328090000e9ec210000"), - (0x13E524, "e9a9ddffff9090"), - # Clean up failed encoded frames so the above fix doesn't wait - # forever when these trigger a reset - (0x13D735, "e927000000"), - (0x13D761, "488b9d70ffffff4c01f3e9d6feffff"), - # Reset tracking and encoding when starting a new streaming session - (0x1129F1, "57488b7f184885ff740feb04"), - (0x112A01, "488b076a015eff50285fe91013feff"), - (0x11A251, "4c89a30804000031c0894308c3"), - (0x11A297, "e8b5ffffff6690"), - (0x11A2AA, "e84287ffff"), - # Ignore stale tracking IDs and repeated older attempts - (0x11B301, "89d02b47084189c6c1e004"), - (0x11B30C, "0f8853050000"), - (0x11B614, "0f841719fdff"), - (0xECF31, "410fb644240988430ee926e90200"), - ), - ), -} - - -def sha256(data): - return hashlib.sha256(data).digest() - - -def notify(message): - print(f"{NAME}: {message}", file=sys.stderr) - try: - subprocess.run( - ["notify-send", NAME, message], - check=False, - timeout=3, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - except (OSError, subprocess.TimeoutExpired): - pass - - -def replace(path, data): - mode = stat.S_IMODE(path.stat().st_mode) - with tempfile.NamedTemporaryFile( - dir=path.parent, prefix=path.name + ".", delete=False - ) as f: - temporary = Path(f.name) - try: - f.write(data) - f.flush() - os.fchmod(f.fileno(), mode) - os.fsync(f.fileno()) - os.replace(temporary, path) - finally: - temporary.unlink(missing_ok=True) - - -def main(command): - diagnostics = [] - if not command: - print(f"Usage: {NAME} command [args...]", file=sys.stderr) - return 2 - try: - root = Path.cwd() - pending = [] - for relative, patch in PATCHES.items(): - path = (root / relative).resolve() - try: - original = path.read_bytes() - except FileNotFoundError as e: - diagnostics.append(f"Skipped `{relative}`: {e}") - continue - try: - updated = patch.apply_to(original) - except ValueError as e: - raise ValueError(f"Patching `{relative}` failed: {e}") from e - except LookupError as e: - diagnostics.append(f"Skipped `{relative}`: {e}") - continue - if updated != original: - pending.append((path, updated)) - for path, updated in pending: - replace(path, updated) - diagnostics.append(f"Patched {path}") - if diagnostics: - notify("\n".join(diagnostics)) - except (OSError, ValueError) as e: - notify("\n".join([f"Encountered an error: {e}", *diagnostics])) - return 1 - os.execvp(command[0], command) - - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) -- cgit v1.3.1