mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-26 00:02:33 +01:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09deb83e4a |
No files matched your search
@@ -347,6 +347,13 @@ signatures:
|
||||
diagnosis: The game crashed creating a very large image (over 4096 px, for example in video players). OVRPort's OpenXR dispatcher aborts on such swapchains.
|
||||
suggest: [frame.swapchain_limit]
|
||||
supersedes: [native-crash]
|
||||
- id: cortopia-org-id-wait
|
||||
# Meta's C# wrapper logs this when the native call returned request id 0 (no callback ever follows); Cortopia's
|
||||
# start-up awaits it forever (GitHub #360)
|
||||
pattern: 'An unknown error occurred\. Request failed\.[\s\S]{0,600}?Cortopia\.Scripts\.Platform\.Meta'
|
||||
severity: error
|
||||
diagnosis: The game waits forever on its loading screen for an answer from Meta's platform (an org-scoped user id, used as its analytics id) that OVRPort's platform library doesn't give. The patch makes the game skip that one request, as it does without Wi-Fi.
|
||||
suggest: [frame.cortopia_org_id]
|
||||
- id: ovr-microphone-crash
|
||||
pattern: 'ovr_Microphone_GetOutputBufferMaxSize\+'
|
||||
severity: fatal
|
||||
|
||||
+16
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
# Patches
|
||||
|
||||
Every patch and game setting FramePort knows: 169 entries in 5 groups. A game's recipe is its chosen patches and settings; tested games get theirs from the catalog ([GAMES.md](GAMES.md)), and other games get the patches FramePort suggests from its analysis. You change them on the game page under Customize.
|
||||
Every patch and game setting FramePort knows: 170 entries in 5 groups. A game's recipe is its chosen patches and settings; tested games get theirs from the catalog ([GAMES.md](GAMES.md)), and other games get the patches FramePort suggests from its analysis. You change them on the game page under Customize.
|
||||
|
||||
Each entry says what the patch does, whether it changes the APK or only the install, when FramePort suggests it, its options, the catalog games whose recipe names it and the launch-test signatures that suggest it. Patches that are on by default are on for every game, so the catalog rarely names them.
|
||||
|
||||
@@ -11,7 +11,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
|
||||
## Groups
|
||||
|
||||
- [Patches from OVRPort](#group-overport) (25)
|
||||
- [Steam Frame patches](#group-frame) (55)
|
||||
- [Steam Frame patches](#group-frame) (56)
|
||||
- [Game settings](#group-adapter) (68)
|
||||
- [Files and settings on the Frame](#group-device) (8)
|
||||
- [PC VR (Proton and Revive)](#group-pcvr) (13)
|
||||
@@ -52,6 +52,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
|
||||
| [Skip Meta avatars](#frame-avatar_stub) | `frame.avatar_stub` | Steam Frame patches | 1 |
|
||||
| [bHaptics: skip the Bluetooth permission request](#frame-bhaptics_permission) | `frame.bhaptics_permission` | Steam Frame patches | 5 |
|
||||
| [BlazeRush: controllers without Meta avatars, own shader cache](#frame-blazerush) | `frame.blazerush` | Steam Frame patches | 1 |
|
||||
| [Cortopia games: don't wait for Meta's org-scoped id](#frame-cortopia_org_id) | `frame.cortopia_org_id` | Steam Frame patches | |
|
||||
| [Epic Online Services: stop the keychain retry loop](#frame-eos_keychain_loop) | `frame.eos_keychain_loop` | Steam Frame patches | |
|
||||
| [OpenGL ES: multiview shaders on flat panels (HUD, menus)](#frame-gl_multiview_fbo) | `frame.gl_multiview_fbo` | Steam Frame patches | 1 |
|
||||
| [GL shim (Mesa GLSL compatibility)](#frame-gl_shim) | `frame.gl_shim` | Steam Frame patches | 2 |
|
||||
@@ -585,6 +586,19 @@ Makes BlazeRush's menus and controllers work without Meta's avatars, and lets it
|
||||
|
||||
> BlazeRush 1.0.349 only (Klownicle's guide, GitHub #57). Its menus wait for Meta's avatar, which never comes on the Frame, before they create the game's own controllers: the second menu ignored all input. Two instructions in libtargemapp.so skip that wait and the avatar mesh drawing; the game's shader cache gets a new folder name (cache/br_shader_v1), so programs compiled before the GL shim's shader fixes aren't reused. Only this exact build is changed (code fingerprint and every instruction checked); anything else is left as it is.
|
||||
|
||||
### <a id="frame-cortopia_org_id"></a>Cortopia games: don't wait for Meta's org-scoped id
|
||||
|
||||
Gets Cortopia games (for example Escaping Wonderland) past their loading screen.
|
||||
|
||||
- `frame.cortopia_org_id`
|
||||
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
|
||||
- **Suggested (on):** Cortopia's Meta platform start-up waits for an org-scoped user id that OVRPort's platform library never answers (stuck loading screen).
|
||||
- **Suggested by launch-test signatures:**
|
||||
- `cortopia-org-id-wait` (error): The game waits forever on its loading screen for an answer from Meta's platform (an org-scoped user id, used as its analytics id) that OVRPort's platform library doesn't give.
|
||||
- **Source:** [patches/frame/cortopia_org_id.py](../src/frameport/patches/frame/cortopia_org_id.py)
|
||||
|
||||
> Cortopia Studios' games (e.g. Escaping Wonderland) ask Meta's platform for an org-scoped user id at start when on Wi-Fi, only to use it as their analytics id. OVRPort's platform library doesn't answer that request ("An unknown error occurred. Request failed."), so the game waits forever on its loading screen. Makes the game take its own offline path for that one request (the analytics id stays empty); the licence check and the logged-in user before it are unchanged. Found with Cpp2IL in libil2cpp.so.
|
||||
|
||||
### <a id="frame-eos_keychain_loop"></a>Epic Online Services: stop the keychain retry loop
|
||||
|
||||
Stops Epic Online Services from retrying its login keychain forever (the game would never finish starting).
|
||||
|
||||
@@ -45,6 +45,7 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
|
||||
| Launch test "failed" although the game runs in the headset: frames paced, then `OVRPortVrApi: EnterVrMode: no tracked head pose before deadline`, then a crash (e.g. `zink: DEVICE LOST` + SIGSEGV in a `gdrv` thread) (GitHub #57, BlazeRush) | the VrApi bridge ends the VR session after 30 s without head tracking, which only happens without a worn headset; the game's renderer then crashes while tearing down | none needed: launch tests (`triage(..., headless=True)`) treat an exit or crash after a `headless_end` line (catalog/triage.yaml) as a pass when frames were paced before it (info finding `headless-end`); play sessions ignore it. Add new such markers to `headless_end` |
|
||||
| Launch test EXITED, launch.log has `Target app (<pkg>) did not start` and no `Start proc …:<pkg>` (GitHub #179) | Lepton occasionally doesn't start the app on the first start after an APK change; Android's `WebViewLoader-: System.exit` at boot is not the game (triage `app-not-started`) | start it again |
|
||||
| A game keeps no progress between starts although it saves (its storage gets a new `Profiles/Oculus/<id>/`-style folder per start; Walkabout Mini Golf, GitHub #182) | OVRPort's platform loader answers `ovr_GetLoggedInUserID` with a made-up id (6e18 + `random()`), new at every start; games file saves under the user id | `frame.ovr_stable_user` (default for games with the platform library): a fixed id per game and keystore. Old progress: copy the newest old profile into the new id's folder |
|
||||
| A Cortopia game (Escaping Wonderland, GitHub #360) stays on its loading screen (frames paced); launch.log: `E Unity: An unknown error occurred. Request failed.` with `OculusPlatformExtension:ToUniTask` / `Cortopia.Scripts.Platform.Meta.<Initialize>d__13` | after the licence check and the logged-in user, the game asks for `Users.GetOrgScopedID` when on Wi-Fi (its analytics id); OVRPort's `ovr_User_GetOrgScopedID` returns request id 0, Meta's C# wrapper logs that line and never calls back, so the awaited start-up never finishes | `frame.cortopia_org_id` (suggested from the analysis, triage `cortopia-org-id-wait`): the `b.ne` after `cmp w0, #2` on `get_internetReachability` in `<Initialize>d__13.MoveNext` -> `b` (Cpp2IL), the game's own no-Wi-Fi path; the same "Request failed." line in another game = another request OVRPort doesn't implement |
|
||||
| A Unity app's text field shows a caret for a moment and loses focus; no keyboard appears (for example Stremio VR login) | Unity's TMP_InputField/InputField wait for Android's on-screen keyboard and close themselves without one (Lepton has none); headless VR apps also have no focused Android window, so no key press reaches them | `frame.unity_text_input` (Cpp2IL finds `TouchScreenKeyboardShouldBeUsed`/`isKeyboardUsingEvents`, rewritten to false/true) + `device.text_input_window` (lepton-show-flatscreen: Steam's keyboard and Type on Frame work) |
|
||||
| FramePort's games disappear after Steam restarts, especially after a devkit registration (GitHub #263) | Steam saves `Shortcuts` / `AppName` with capital letters; older FramePort reads them case-sensitively and writes a second shortcut map that Steam ignores | agent v86 reads and updates keys case-insensitively, keeping Steam's spelling, appids and tags; update FramePort and add the missing games to the library again |
|
||||
| Play gives Steam's "Game configuration unavailable"; Steam's console_log: `GameAction [AppID <id>] … RequestingLicense → UpdatingAppInfo → LaunchApp failed with AppError_9` | this Frame's Steam never loaded FramePort's shortcuts.vdf entry (it treats the id as a store app); see the casing fix above, other causes remain unconfirmed (GitHub #21/#30) | automatic since agent v43: Play registers the game through Steam's devkit interface ("Devkit Game: …", `devkit_register`) and starts that; uninstall removes it. Agent v58: a devkit entry Steam forgot after a restart is added again live on Play (no NOT_IN_LIBRARY → no Steam restart), and installs/art updates of devkit games only copy art (no Steam restart, GitHub #41) |
|
||||
|
||||
@@ -414,6 +414,11 @@
|
||||
"title": "BlazeRush: controllers without Meta avatars, own shader cache",
|
||||
"group": "Steam Frame patches"
|
||||
},
|
||||
"frame.cortopia_org_id": {
|
||||
"anchor": "frame-cortopia_org_id",
|
||||
"title": "Cortopia games: don't wait for Meta's org-scoped id",
|
||||
"group": "Steam Frame patches"
|
||||
},
|
||||
"frame.eos_keychain_loop": {
|
||||
"anchor": "frame-eos_keychain_loop",
|
||||
"title": "Epic Online Services: stop the keychain retry loop",
|
||||
|
||||
@@ -43,7 +43,9 @@ IL2CPP_METADATA = "assets/bin/Data/Managed/Metadata/global-metadata.dat"
|
||||
# 17: headset (the Meta headset a VR app was made for: Gear VR / Go games run through frame.a32_bridge) (2026-10)
|
||||
# 18: headset "quest" also for 32-bit apps that require 6DoF head tracking (Santa Cruz builds, e.g. Sports
|
||||
# Scramble: Touch controllers on the a32 bridge) (2026-10)
|
||||
ANALYSIS_VERSION = 19
|
||||
# 20: cortopia_org_id (Cortopia's Meta start-up waits for Users.GetOrgScopedID: frame.cortopia_org_id,
|
||||
# GitHub #360) (2026-10)
|
||||
ANALYSIS_VERSION = 20
|
||||
|
||||
|
||||
# Android versions by API level (for messages); the Frame's Lepton container runs Android 11 (API 30)
|
||||
@@ -378,6 +380,10 @@ def analyze(path: Path, deep: bool = True, data_bytes: int | None = None) -> Ana
|
||||
"text_fields": unity_text_fields(il2cpp_meta) if il2cpp_meta else [],
|
||||
# Meta's OVRManager raises MSAA at runtime (frame.unity_runtime_msaa_off); Oculus XR Plugin (multiview)
|
||||
"ovr_runtime_msaa": bool(il2cpp_meta) and b"\0useRecommendedMSAALevel\0" in il2cpp_meta,
|
||||
# Cortopia's Meta platform start-up awaits an org-scoped user id OVRPort never answers
|
||||
# (frame.cortopia_org_id, GitHub #360)
|
||||
"cortopia_org_id": bool(il2cpp_meta) and b"\0Cortopia.Scripts.Platform.Meta\0" in il2cpp_meta
|
||||
and b"\0GetOrgScopedID\0" in il2cpp_meta,
|
||||
"sdl_java": sdl_java,
|
||||
"godot_clipboard": godot_clipboard,
|
||||
"vivox_api31": vivox_api31,
|
||||
|
||||
@@ -331,6 +331,8 @@
|
||||
"Copy this theme as a file": "",
|
||||
"Corrects graphics code the Frame's drivers reject (black screen with sound).": "",
|
||||
"Corrects specific shaders that hang the game on the Frame. Comes from a game's recipe.": "",
|
||||
"Cortopia games: don't wait for Meta's org-scoped id": "",
|
||||
"Cortopia Studios' games (e.g. Escaping Wonderland) ask Meta's platform for an org-scoped user id at start when on Wi-Fi, only to use it as their analytics id. OVRPort's platform library doesn't answer that request (\"An unknown error occurred. Request failed.\"), so the game waits forever on its loading screen. Makes the game take its own offline path for that one request (the analytics id stays empty); the licence check and the logged-in user before it are unchanged. Found with Cpp2IL in libil2cpp.so.": "",
|
||||
"Couldn't check the saves on the Frame: {error}": "",
|
||||
"Couldn't check this PC": "",
|
||||
"Couldn't connect: {exc}": "",
|
||||
@@ -652,6 +654,7 @@
|
||||
"Generate OVRPort config": "",
|
||||
"Generates no-op stubs for ovr_* functions the game imports but OVRPort's platform loader lacks (for example Espire 1/2, Wallace & Gromit, The Climb 2). Symptom: UnsatisfiedLinkError / 'cannot locate symbol ovr_...'. Online/store features stay unavailable.": "",
|
||||
"Get FramePort ready": "",
|
||||
"Gets Cortopia games (for example Escaping Wonderland) past their loading screen.": "",
|
||||
"Getting FramePort ready": "",
|
||||
"GiB": "",
|
||||
"GitHub issue opened": "",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Cortopia Studios' Meta platform start-up waits forever for an org-scoped user id (Escaping Wonderland, GitHub #360).
|
||||
|
||||
Cortopia's games start Meta's platform in `Cortopia.Scripts.Platform.Meta.OculusPlatform.Initialize` (an async
|
||||
method, `<Initialize>d__13.MoveNext`): Core.AsyncInitialize, Entitlements.IsUserEntitledToApplication,
|
||||
Users.GetLoggedInUser, and then, only when `Application.internetReachability` is ReachableViaLocalAreaNetwork,
|
||||
Users.GetOrgScopedID(user id), whose answer only becomes the game's AnalyticsID. OVRPort's platform library has no
|
||||
`ovr_User_GetOrgScopedID` (an export that returns request id 0, runtimes 3.4.3-23204ea and -aa54c3f); Meta's C#
|
||||
wrapper then logs "An unknown error occurred. Request failed." and never calls the callback, so the awaited task never
|
||||
finishes and the game stays on its loading screen with frames still paced.
|
||||
|
||||
The game's own code skips that request when the device isn't on Wi-Fi/LAN (then the analytics id stays empty, as it
|
||||
does for offline players). This patch takes that path always: the `b.ne` after `cmp w0, #2` that follows the call to
|
||||
`get_internetReachability` in that method becomes an unconditional `b`. The licence check and the logged-in user come
|
||||
before it and are untouched. Methods are found with Cpp2IL; the site must match exactly once, with the
|
||||
GetOrgScopedID call inside the skipped part, or nothing is changed."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ..base import ApkContext, Patch, Suggestion, register
|
||||
from .unity_text_input import ALL_TARGETS, METADATA, Il2cppReturnPatch, _executable, can_rewrite_il2cpp
|
||||
|
||||
PLATFORM = "Assembly-CSharp/Cortopia/Scripts/Platform/Meta/OculusPlatform.cs"
|
||||
APPLICATION = "UnityEngine.CoreModule/UnityEngine/Application.cs"
|
||||
USERS = "Oculus.Platform/Oculus/Platform/Users.cs"
|
||||
WANTED = {PLATFORM: ["MoveNext"], APPLICATION: ["get_internetReachability"], USERS: ["GetOrgScopedID"]}
|
||||
for _cls, _names in WANTED.items(): # one Cpp2IL run (and cache entry) serves every IL2CPP patch
|
||||
ALL_TARGETS.setdefault(_cls, [])
|
||||
ALL_TARGETS[_cls] += [n for n in _names if n not in ALL_TARGETS[_cls]]
|
||||
|
||||
CMP_W0_2 = 0x7100081F # cmp w0, #2 (subs wzr, w0, #2): NetworkReachability.ReachableViaLocalAreaNetwork
|
||||
|
||||
|
||||
def _word(data: bytes | bytearray, off: int) -> int:
|
||||
return int.from_bytes(data[off:off + 4], "little")
|
||||
|
||||
|
||||
def _bl_target(word: int, off: int) -> int | None:
|
||||
if word & 0xFC000000 != 0x94000000:
|
||||
return None
|
||||
imm = word & 0x3FFFFFF
|
||||
return off + ((imm - (1 << 26) if imm & (1 << 25) else imm) << 2)
|
||||
|
||||
|
||||
def skip_org_id(lib: bytes, method: tuple[int, int], reachability: int, org_scoped_id: int
|
||||
) -> tuple[bytes | None, list[str]]:
|
||||
"""In the method at (file offset, length): `bl get_internetReachability; cmp w0, #2; b.ne X` -> `b X`, when the
|
||||
skipped code [b.ne + 4, X) calls GetOrgScopedID. Offsets are file offsets (one code segment: bl's relative
|
||||
displacement is the same in the file). Returns (patched library or None, notes); raises when the code differs."""
|
||||
start, length = method
|
||||
if start % 4 or not any(a <= start and start + length <= b for a, b in _executable(lib)):
|
||||
raise RuntimeError(f"OculusPlatform.Initialize: offset {start:#x} isn't code in libil2cpp.so")
|
||||
sites, done = [], []
|
||||
for off in range(start, start + length - 8, 4):
|
||||
if _bl_target(_word(lib, off), off) != reachability or _word(lib, off + 4) != CMP_W0_2:
|
||||
continue
|
||||
br = off + 8
|
||||
w = _word(lib, br)
|
||||
if w & 0xFF00001F == 0x54000001: # b.ne
|
||||
imm = (w >> 5) & 0x7FFFF
|
||||
target = br + ((imm - (1 << 19) if imm & (1 << 18) else imm) << 2)
|
||||
if br < target <= start + length and any(_bl_target(_word(lib, o), o) == org_scoped_id
|
||||
for o in range(br + 4, target, 4)):
|
||||
sites.append((br, target))
|
||||
elif w & 0xFC000000 == 0x14000000: # already an unconditional b
|
||||
done.append(br)
|
||||
if not sites:
|
||||
if done:
|
||||
return None, ["OculusPlatform.Initialize: org-scoped id request already skipped"]
|
||||
raise RuntimeError("OculusPlatform.Initialize: the network check before GetOrgScopedID wasn't found")
|
||||
if len(sites) > 1:
|
||||
raise RuntimeError(f"OculusPlatform.Initialize: {len(sites)} candidate checks; not guessing which one")
|
||||
br, target = sites[0]
|
||||
out = bytearray(lib)
|
||||
out[br:br + 4] = (0x14000000 | (((target - br) >> 2) & 0x3FFFFFF)).to_bytes(4, "little")
|
||||
return bytes(out), [f"OculusPlatform.Initialize: skips Users.GetOrgScopedID (b.ne -> b at {br:#x})"]
|
||||
|
||||
|
||||
class CortopiaOrgId(Patch):
|
||||
id = "frame.cortopia_org_id"
|
||||
title = "Cortopia games: don't wait for Meta's org-scoped id"
|
||||
description = ("Cortopia Studios' games (e.g. Escaping Wonderland) ask Meta's platform for an org-scoped user id "
|
||||
"at start when on Wi-Fi, only to use it as their analytics id. OVRPort's platform library doesn't "
|
||||
"answer that request (\"An unknown error occurred. Request failed.\"), so the game waits forever "
|
||||
"on its loading screen. Makes the game take its own offline path for that one request (the "
|
||||
"analytics id stays empty); the licence check and the logged-in user before it are unchanged. "
|
||||
"Found with Cpp2IL in libil2cpp.so.")
|
||||
order = 49
|
||||
|
||||
def applies(self, a):
|
||||
return (a.engine == "Unity" and "libil2cpp.so" in a.libs and "arm64-v8a" in a.abis
|
||||
and bool((a.extra or {}).get("cortopia_org_id")))
|
||||
|
||||
def detect(self, a):
|
||||
if self.applies(a) and can_rewrite_il2cpp(a):
|
||||
return Suggestion(True, "Cortopia's Meta platform start-up waits for an org-scoped user id that "
|
||||
"OVRPort's platform library never answers (stuck loading screen).")
|
||||
return None
|
||||
|
||||
def apply(self, ctx: ApkContext) -> bool:
|
||||
from ...analysis.il2cpp import find_methods
|
||||
|
||||
ws = ctx.ws
|
||||
lib_name = ws.lib("libil2cpp.so")
|
||||
if ws.abi != "arm64-v8a" or not ws.has(lib_name) or not ws.has(METADATA):
|
||||
return False
|
||||
check = "Cortopia platform start-up"
|
||||
version = (ctx.analysis.extra or {}).get("unity_version") or Il2cppReturnPatch._unity_version(ws)
|
||||
if not version:
|
||||
ctx.reporter.check(check, False, "not fixed: unknown Unity version (rescan the game)")
|
||||
return False
|
||||
lib = ws.read(lib_name)
|
||||
ctx.reporter.log(f"{check}: finding the code (Cpp2IL; the first time can take a minute)")
|
||||
try:
|
||||
found = find_methods(lib, ws.read(METADATA), version, ALL_TARGETS)
|
||||
method = found.get(PLATFORM, {}).get("MoveNext")
|
||||
reach = found.get(APPLICATION, {}).get("get_internetReachability")
|
||||
org = found.get(USERS, {}).get("GetOrgScopedID")
|
||||
if not (method and reach and org):
|
||||
ctx.notes.append(f"{check}: code not found in this game")
|
||||
return False
|
||||
patched, notes = skip_org_id(lib, method, reach[0], org[0])
|
||||
except Exception as exc: # noqa: BLE001 - optional fix: the game still builds, unchanged
|
||||
ctx.reporter.check(check, False, f"not fixed: {exc}")
|
||||
return False
|
||||
ctx.notes.extend(notes)
|
||||
if patched is not None:
|
||||
ws.put(lib_name, patched)
|
||||
return patched is not None
|
||||
|
||||
|
||||
register(CortopiaOrgId)
|
||||
@@ -99,6 +99,7 @@ SUMMARIES = {
|
||||
"seasons).",
|
||||
"frame.ovr_microphone": "Stops a crash a few seconds after the logo in games with voice chat.",
|
||||
"frame.ovr_stable_user": "Keeps your progress in games that file their saves under your Meta user.",
|
||||
"frame.cortopia_org_id": "Gets Cortopia games (for example Escaping Wonderland) past their loading screen.",
|
||||
"frame.luajit_pointers": "Lets games with a verified older Lua engine get past a startup crash on the Frame.",
|
||||
"frame.avatar_stub": "Lets games start that would stop at Meta's avatars (for example BlazeRush). No Meta "
|
||||
"avatars.",
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""frame.cortopia_org_id: skip Cortopia's Users.GetOrgScopedID request (OVRPort never answers it, GitHub #360)."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from frameport.analysis.il2cpp import parse_methods
|
||||
from frameport.patches.base import get, load_all
|
||||
from frameport.patches.frame import cortopia_org_id as C
|
||||
from frameport.patches.frame.unity_text_input import ALL_TARGETS, _executable
|
||||
|
||||
|
||||
def _lib() -> bytes:
|
||||
fx = sorted(Path(__file__).parent.joinpath("fixtures").glob("libfake*_arm64.so"))[0]
|
||||
return fx.read_bytes()
|
||||
|
||||
|
||||
def _bl(at: int, target: int) -> int:
|
||||
return 0x94000000 | (((target - at) >> 2) & 0x3FFFFFF)
|
||||
|
||||
|
||||
def _bne(at: int, target: int) -> int:
|
||||
return 0x54000001 | ((((target - at) >> 2) & 0x7FFFF) << 5)
|
||||
|
||||
|
||||
def _fake(lib: bytes, org_inside: bool = True):
|
||||
"""The shape of Escaping Wonderland 1.0.1.1354's MoveNext: bl reach; cmp w0,#2; b.ne skip; ...;
|
||||
bl GetOrgScopedID; ...; skip:"""
|
||||
(lo, hi), = _executable(lib)[:1]
|
||||
m = (lo + 0x40) & ~3
|
||||
reach, org = m + 0x60, m + 0x70 # call targets only (outside the 64-byte method)
|
||||
assert m + 64 <= hi
|
||||
words = [0xD503201F] * 16 # nop
|
||||
words[2] = _bl(m + 8, reach)
|
||||
words[3] = C.CMP_W0_2
|
||||
words[4] = _bne(m + 16, m + 48) # skip to word 12
|
||||
words[7] = _bl(m + 28, org if org_inside else reach + 8)
|
||||
out = bytearray(lib)
|
||||
for i, w in enumerate(words):
|
||||
out[m + 4 * i:m + 4 * i + 4] = w.to_bytes(4, "little")
|
||||
return bytes(out), (m, 64), reach, org
|
||||
|
||||
|
||||
def test_branch_becomes_unconditional_once():
|
||||
lib, method, reach, org = _fake(_lib())
|
||||
patched, notes = C.skip_org_id(lib, method, reach, org)
|
||||
br = method[0] + 16
|
||||
w = int.from_bytes(patched[br:br + 4], "little")
|
||||
assert w == 0x14000000 | 8 and "skips Users.GetOrgScopedID" in notes[0] # b +32 (to word 12)
|
||||
assert patched[:br] == lib[:br] and patched[br + 4:] == lib[br + 4:]
|
||||
again, notes = C.skip_org_id(patched, method, reach, org)
|
||||
assert again is None and "already" in notes[0]
|
||||
|
||||
|
||||
def test_refuses_when_the_skipped_code_has_no_org_scoped_id_call():
|
||||
lib, method, reach, org = _fake(_lib(), org_inside=False)
|
||||
with pytest.raises(RuntimeError, match="wasn't found"):
|
||||
C.skip_org_id(lib, method, reach, org)
|
||||
|
||||
|
||||
def test_cpp2il_names_are_found_and_requested():
|
||||
cs = ''' private struct <Initialize>d__13 : IAsyncStateMachine
|
||||
{
|
||||
[Address(RVA = "0x269F428", Offset = "0x269F428", Length = "0xEE4")]
|
||||
[Token(Token = "0x6000952")]
|
||||
private override void MoveNext() { }
|
||||
'''
|
||||
assert parse_methods(cs, ["MoveNext"]) == {"MoveNext": (0x269F428, 0xEE4)}
|
||||
for cls, names in C.WANTED.items():
|
||||
assert set(names) <= set(ALL_TARGETS[cls])
|
||||
|
||||
|
||||
def test_suggested_only_for_cortopia_platform_code():
|
||||
load_all()
|
||||
p = get("frame.cortopia_org_id")
|
||||
|
||||
class A:
|
||||
engine, libs, abis = "Unity", ["libil2cpp.so"], ["arm64-v8a"]
|
||||
extra = {"cortopia_org_id": True, "unity_version": "2022.3.19f1"}
|
||||
|
||||
assert p.applies(A) and p.detect(A).recommended
|
||||
A.extra = {"cortopia_org_id": False}
|
||||
assert not p.applies(A) and p.detect(A) is None
|
||||
Reference in new issue
Block a user