Files
Radicade/Tools/patch-quest-standalone-login.py
T
2026-10-16 23:33:13 -07:00

92 lines
4.4 KiB
Python

"""Experimental Quest 20230907 independent login; requires server platform-7 guard.
Preserves Quest VR setup before switching account initialization to standalone.
Redirects auth parameters, platform enum, and title-screen error-page navigation.
Produces an UNSIGNED APK. Does not assert that login/gameplay is supported.
The v1 full-initializer replacement failed with error 3 and no usable hands.
This v3 experiment restores SetVRDisplayMode, the password page, and Quest input.
Error navigation currently applies to all title-screen errors; not release-ready.
"""
import argparse
import hashlib
import struct
import zipfile
from pathlib import Path
EXPECTED = 'd9a72f2906ecee6cd292fa094fa57df8904b125dee161a04887603a3d0d3449e'
LIB = 'lib/arm64-v8a/libil2cpp.so'
REDIRECTS = (
('GetAuthParams', 0x3d0a930, 0x396ad48),
('get_CurrentPlatform', 0x3d096fc, 0x43d6eb8),
('Title-screen error to password page', 0x39c0f40, 0x39bbd80),
('Saved-account auto-login error to password page', 0x39c67e0, 0x39bbd80),
)
def patch(data):
if hashlib.sha256(data).hexdigest() != EXPECTED:
raise ValueError('Unknown IL2CPP binary; refusing to patch')
result = bytearray(data)
phoff = struct.unpack_from('<Q', data, 32)[0]
entsize, count = struct.unpack_from('<HH', data, 54)
segments = [struct.unpack_from('<IIQQQQQQ', data, phoff + i * entsize) for i in range(count)]
def offset(rva):
for typ, flags, off, va, _, size, _, _ in segments:
if typ == 1 and flags & 1 and va <= rva < va + size:
return off + rva - va
raise ValueError('Address outside file-backed executable segment')
for name, src, dst in REDIRECTS:
delta = dst - src
if delta % 4 or not -(1 << 27) <= delta < (1 << 27):
raise ValueError('Branch target out of range')
offset(dst)
struct.pack_into('<I', result, offset(src), 0x14000000 | ((delta // 4) & 0x3ffffff))
print(f'{name}: 0x{src:x} -> 0x{dst:x}')
# Preserve the original prologue and Quest-specific VR display setup through
# 0x3d09f14. Then run independent initialization and restore the original frame.
start = 0x3d09f18
words = [0xaa1303e0, 0xaa1f03e1,
0x94000000 | (((0x43d6f0c - (start + 8)) // 4) & 0x3ffffff)]
for addr in (0x3d0a124, 0x3d0a12c, 0x3d0a130, 0x3d0a138):
words.append(struct.unpack_from('<I', data, offset(addr))[0])
words.append(0xd65f03c0)
struct.pack_into('<' + 'I' * len(words), result, offset(start), *words)
print('Preserved Quest VR initialization; redirected account initialization after it')
# InputManager.FMJDLCGGGKP switches on CurrentPlatform. Standalone (7)
# originally returns no VR input; map it to the Oculus hardware-aware path.
table = 0xc35a550
for typ, flags, off, va, _, size, _, _ in segments:
if typ == 1 and va <= table and table + 36 <= va + size:
table_offset = off + table - va
assert table + struct.unpack_from('<i', data, table_offset + 28)[0] == 0x460a2b0
assert table + struct.unpack_from('<i', data, table_offset + 4)[0] == 0x460a2c0
result[table_offset + 28:table_offset + 32] = data[table_offset + 4:table_offset + 8]
break
else:
raise ValueError('Input dispatch table not found')
print('Standalone input dispatch uses existing Oculus hardware-specific handler')
return bytes(result)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('input', type=Path)
parser.add_argument('output', type=Path)
parser.add_argument('--apply', action='store_true')
args = parser.parse_args()
if args.output.exists():
parser.error('Output already exists; choose a new path')
with zipfile.ZipFile(args.input) as original:
patched = patch(original.read(LIB))
print('Patched library SHA256:', hashlib.sha256(patched).hexdigest())
if not args.apply:
print('Dry run; pass --apply to write unsigned APK')
return
with zipfile.ZipFile(args.output, 'w') as output:
for info in original.infolist():
if info.filename.upper().startswith('META-INF/'):
continue
output.writestr(info, patched if info.filename == LIB else original.read(info))
print('Wrote unsigned experimental APK:', args.output)
if __name__ == '__main__':
main()