Skip to content

Platform shim: stop hanging games in Meta's uninitialized implementation - #20

Merged
Villagers654 merged 3 commits into
Villagers654:mainfrom
marshmallow-mia:fix/platform-shim-uninitialized
Oct 24, 2026
Merged

Villagers654 merged 3 commits into
Villagers654:mainfrom
marshmallow-mia:fix/platform-shim-uninitialized

Conversation

@marshmallow-mia

Copy link
Copy Markdown
Contributor

What this fixes

The platform shim answers initialization, login and entitlement itself, so Meta's real implementation
(LibOVRPlatformImpl64_1_real.dll) is never started. Everything the shim doesn't answer is forwarded to it. Three gaps
let games run into that unstarted implementation, which then either throws or waits for an Oculus service that never
answers under Wine.

Empty message polls crashed the game. When the shim had no message of its own, ovr_PopMessage forwarded the poll
to Meta's implementation. Without its context that throws Context::Get: Called before context was initialized. on
every poll (101 C++ exceptions in 100 empty polls, measured against Meta's DLL). The Lone Echo II Trailer Experience
crashed there right after sign-in, and its crash reporter then hung, which looked like a stall before the first frame.
The user check in ovr_AssetFile_GetList raised the same exception. Both now ask Meta's own
ovr_IsPlatformInitialized, which only reads the context, before forwarding.

An achievement unlock nobody answered. The trailer unlocks an achievement at startup and waits for the reply before
rendering. Forwarded, Meta's implementation returns request 0 and never replies. In offline mode
ovr_Achievements_Unlock is now answered locally, like the achievement definitions and progress already are. Its
update getters (ovr_Message_GetAchievementUpdate, ovr_AchievementUpdate_GetJustUnlocked,
ovr_AchievementUpdate_GetName) answer for that local reply too; handed to Meta's DLL, it caused an access violation.

Older Unity games hung on a black screen under Proton. Older Unity Platform SDKs, such as the one in Blocks,
initialize through ovr_UnityInitWrapperWindows. The shim forwarded it, so Meta's implementation started its online
services (OAF, MQTT), and under Proton Blocks' main thread then waited inside it forever. On Windows, Meta's
implementation returns there, and Blocks renders with main's shim too. The shim now answers it and the Unity initializer
it calls, ovr_PlatformInitializeUnityWindows, like the Windows, Unreal and asynchronous Unity ones (upstream answers
ovr_UnityInitWrapperWindowsAsynchronous since 10855ee). Meta's wrapper always reports true, and so does the shim's.

The trailer's menu on Monado

How it was found

Backtraces of the stalled games (winedbg bt all in the game's prefix):

  • Trailer: main thread in ovr_PopMessage of Meta's implementation, raising the C++ exception; after that fix, the
    main thread looped on ovr_PopMessage waiting for the reply to ovr_Achievements_Unlock (message type 0x593CCBDD).
  • Blocks: main thread waiting inside Meta's implementation, called from ovr_UnityInitWrapperWindows.

Testing

On 0.11.1.2

The synchronous Unity initializers sit beside upstream's asynchronous ones; the build script's export list and its
initializer check cover all six.

  • compat/tests (built with MinGW, run under Wine) pass on the ported branch. Upstream's shim and its own test,
    with only the stub's ovr_PopMessage throwing without a context as Meta's DLL does, die with the unhandled C++
    exception (0xE06D7363) on the first empty poll.
  • The fork CI's MSVC shim exports every new function (ovr_UnityInitWrapperWindows,
    ovr_PlatformInitializeUnityWindows, ovr_Achievements_Unlock and the achievement update getters) alongside
    upstream's asynchronous ones.
  • Fork CI on the ported branch: the CI and Windows workflows pass, Python and native jobs alike.
  • Windows 10, natively (the fork CI's installers):
    • Lone Echo II Trailer Experience, Quest 3 through Virtual Desktop's OpenXR runtime:
      • Main: after sign-in, three empty polls and no unlock. Nothing renders after the bootstrap frame, and the headset
        stays black.
      • This branch (merged): renders the menu (5,010 frames), with the unlock answered locally.
    • Blocks, Quest 3 through Steam Link into SteamVR (OpenVR backend):
      • Main: renders. Meta's implementation returns from ovr_UnityInitWrapperWindows on Windows, and Blocks goes on
        to the entitlement check.
      • This branch (merged): renders the same, with the shim answering both Unity initializers ("unity init wrapper:
        success", "initialize unity windows: success" in its log). The headset view changes between captures 10 s apart
        with both builds.
      • Virtual Desktop's OpenXR runtime crashes Blocks on start with main and this branch alike (0xC000041D).
  • On Linux under Proton, in VR with the fork CI's MSVC builds of main and of this branch (Monado with a simulated
    headset; Quest 3 on WiVRn and through Steam Link into SteamVR):
    • Lone Echo II Trailer Experience:
      • Main: after sign-in three empty polls, then nothing (1 frame).
      • This branch: renders its menu (5,013 frames), with the achievement unlock answered locally. On the Quest 3 it
        renders too (WiVRn 7,468 frames, SteamVR 7,677).
    • Blocks:
      • Main: a black window (65 trace lines, no shim log).
      • This branch: renders (21,714 trace lines) on Monado, and also on the Quest 3 through WiVRn and XRizer and
        through SteamVR.
    • Lone Echo 3.17.4 (in its first 70 s it logs in and checks the entitlement, and unlocks no achievement):
      • Main: renders as well (3,898 frames on Monado in 70 s), but the forwarded empty polls throw there too. With
        Wine's exception log on, main's shim causes 2,414 C++ exceptions (0xE06D7363) in 60 s; Lone Echo catches
        them and carries on, where the trailer hangs.
      • This branch: renders the same (3,911 frames) with no C++ exception at all. Every other exception in the log is
        the same on both. Login, entitlement and the org-scoped ID are answered locally. On the Quest 3 through SteamVR
        it was played by hand with the controllers.

Earlier, on 7289ef6

  • compat/tests: new cases for polling before Meta's implementation is started, the asset list check, achievement
    unlocks in both modes, and all four initializers. Main's shim fails the polling test with the same unhandled C++
    exception the game hit.
  • The shim builds with -Werror; its export list is identical to the release shim's (1266 names).
  • A Wine harness calling the fork CI's shim builds in front of Meta's real DLL, main against this branch:
    • Empty polls: 101 C++ exceptions in 100 polls inside Meta's DLL, against none.
    • ovr_AssetFile_GetList: one exception from the user check, against none.
    • Offline achievement unlock: no reply ever, against a reply on the next poll.
    • Reading that reply's achievement update: an access violation before the third commit, "just unlocked" with its
      name after.
    • ovr_UnityInitWrapperWindows: Meta's online services start, against a local answer.
  • Real headset (Quest 3) and simulated, with the CI builds:
    • Lone Echo II Trailer Experience: hangs after sign-in on main (Monado); renders its menu with this branch on Monado,
      WiVRn and SteamVR (Steam Link). Controls were checked by hand on an earlier run with the same code.
    • Blocks: black window on main (Monado); renders with this branch on Monado and WiVRn through XRizer and on SteamVR.

The shim answers initialization locally, so Meta's real implementation never
gets a context. Its context-bound calls throw "Context::Get: Called before
context was initialized." without one, yet the shim still forwarded every
empty ovr_PopMessage poll (and the user check in ovr_AssetFile_GetList) to
it. The Lone Echo II trailer crashed there right after sign-in, and its crash
reporter then hung, which looked like a stall before the first frame.

Ask Meta's own ovr_IsPlatformInitialized, which only reads the context,
before forwarding those calls.

The trailer also unlocks an achievement at startup and waits for the reply
before rendering. Answer ovr_Achievements_Unlock locally in offline mode,
like the achievement definitions and progress already are.
Older Unity Platform SDKs, such as the one in Blocks, initialize through
ovr_UnityInitWrapperWindows, which the shim forwarded to Meta's
implementation. That waits for an Oculus service that never answers under
Wine, so the game hung on its main thread before its first frame, with a
black window and nothing in the headset.

The shim now answers ovr_UnityInitWrapperWindows and the Unity initializer
it calls, ovr_PlatformInitializeUnityWindows, like the Windows, Unreal and
asynchronous Unity ones. Meta's wrapper always reports true, and so does
the shim's.
In offline mode ovr_Achievements_Unlock is answered with a local message.
A game that reads the result through ovr_Message_GetAchievementUpdate and
the ovr_AchievementUpdate getters handed that message to Meta's
implementation, which treats it as one of its own: an access violation
in a harness against Meta's DLL. The shim now answers those getters for
its own messages, with the update just unlocked under the requested name.

@Villagers654 Villagers654 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Villagers654
Villagers654 merged commit 0cfd5bd into Villagers654:main Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants