Oculus/Meta PCVR games, native to SteamVR's Library

1 October 2026

Playing Oculus/Meta PCVR games inside SteamVR has been possible since December 2016, thanks to Revive github.com/LibreVR/Revive, the MIT-licensed LibOVR to OpenVR/OpenXR translation layer.. Revive hasn’t seen a commit in three years though, and recently SteamVR’s dashboard has had a major overhaul, so the old approach, its own always-running dashboard overlay for launching those games, doesn’t really fit the new UI. The games themselves are fine: Lone Echo, Lone Echo II, Stormland, all still installed, all still runnable through Revive’s translation layer. But SteamVR’s Library, the thing you’re actually looking at with the headset on, had no idea they existed; launching one meant going through Revive’s own UI instead, its icon in the SteamVR bar opening an overlay, or the desktop window.

So I spent eleven days in late September on ReviveNext Landing page at konstruukt.com/projects/revivenext, source on github.com/Konstruukt-Labs/ReviveNext., and the games now sit in SteamVR’s Library with cover art, launching through SteamVR’s normal app flow. You can see in the commit dates: ten commits the first day, nine the second, fourteen the third, then five more across the following week, by which point I was mostly trying to fix bugs that I eventually identified were in SteamVR itself.

Getting the games into the Library

Initialy a wrong wrong approach. Syncing the games into Steam’s desktop Library as shortcuts, art attached, and desktop Steam showed them just fine but it was hard to update art. The updated approach registers every game as a proper SteamVR application through the OpenVR applications API instead, binary launch type, the matched art as its image path, Revive’s action manifest attached. Launch one from the headset and you get SteamVR’s own app flow: a loading screen while the game starts, the game running under its registered identity, and the dashboard when it ends.

Art comes from SteamGridDB steamgriddb.com, a community art database; a free API key is all it needs.. The ReviveNext window walks through the games one at a time, you can search matching both the portrait Library cover and the widescreen home art, and either or both can be picked without a second search. Picks are saved locally with a small ReviveNext badge burned into the corner, so it’s obvious at a glance which entries the tool looks after.

The registration keeps itself current, too. Every time SteamVR starts, a headless ReviveNext run rescans the installed games and refreshes their Library entries. Install or remove a game whenever you like; the next session picks it up. There’s no resident background process, which works because Steam only auto-starts ReviveNext when upi launch SteamVR.

Nothing Meta needs to be running

ReviveNext reads the registry to find installed games, so the Meta Horizon app doesn’t have to be running for games to be found. When a game launches, its entitlement check wants one specific Meta process, and the bundled injector starts it if required, OVRServer_x64.exe, unelevated, no console window. Meta’s own start-up paths are deliberately bypassed. There’s no documented readiness signal, so the injector waits on the process handle for three seconds, which so far has always been long enough for the IPC broker. If SteamVR is the only way you use your headset you can turn all of Meta’s auto-start off and set that service to Manual; ReviveNext doesn’t need it, so it costs nothing.

The haptics bug

Revives controller rumble was dead over Steam Link and on Quest headsets, and the cause took some finding: Modern SteamVR drops legacy TriggerHapticPulse calls made by scene applications without a word, and every game is a scene app. No errors, the controller just never rumbles. The fix was switching to action-based haptics: VibrateLeft and VibrateRight vibration actions bound to /user/hand/*/output/haptic in all five default binding files, fired from the haptics thread as short re-applied bursts with the buffer sample as amplitude, restricted to the hand doing the pulsing. The bursts matter, a single long call gets truncated to a blip over the link. Where actions don’t exist the legacy pulse still fires, which works in that context. Lone Echo’s trigger interactions came back, correctly timed.

The performance pass

The next day went to the Revive improvements. The haptics thread runs at 320 Hz per controller, and it was querying the controller device index, an OpenVR IPC round-trip, on every single iteration; now it only asks when a sample is actually on its way. GetTrackingState recomputed the calibrated-origin transform, two IPC calls plus a 4x4 matrix inversion, every call, and nearly every game calls it once a frame through ovr_GetSessionStatus. That transform only changes on a recenter or an origin-mode switch, so now it’s cached and thrown away on those events. The compositor flushed the D3D11 context before releasing every texture, but Microsoft’s interop contract only asks for that flush when a D3D12 resource is wrapped through D3D11on12; ordinary D3D11 textures have nothing outstanding, so the flush is gated on the same field the acquire and release path uses. Both sides got real games as cover, Lone Echo is D3D11-only and Lone Echo II reaches D3D12 through the interop. And HapticsBuffer::GetState(), which games poll every frame per controller, now works out queue occupancy as the wraparound difference of two 8-bit indices; I checked it against a brute-force reference across all 65,536 possible index pairs, identical output everywhere, right down to the quirk where SamplesQueued reads -1 on equal indices.

Two more went into the OpenXR backend: swapchain image waits are deferred until after frame submission, so the render thread never blocks on an image it won’t touch until next frame, and ovr_EndFrame’s per-frame layer vectors get reserved to their true worst case in one allocation. Which normal use never reaches: VR_IsRuntimeInstalled() always routes to the OpenVR backend. Those two are build-verified only, and labelled as such in the commit message.

Bugs found on the way

Some of the bugs were mine. GDI+ keeps a file open for as long as the Image object lives, so re-picking art quietly failed to overwrite its preview until I released the previous image first. SteamVR refuses to persist manifest paths containing mixed path separators, and doesn’t tell you, so every path handed over gets normalised up front. A missing action manifest leaves a game’s controllers dead, the only clue a log line nobody reads, and the fix, a persistent environment variable pointing at the vendored manifest, only takes effect after a Steam restart; that one earned its own popup in the app. SteamVR also drops every registered entry whose manifest file isn’t where it recorded it, which is why the manifest lives in a stable per-user AppData home instead of next to the exe, which also means an installed copy under Program Files never needs elevation. And I deliberately didn’t implement shortcut deletion, because deleting a Steam shortcut permanently blocks its appid from ever being reassigned.

The build had its own share. vcpkg’s static OpenXR loader links against v143 runtime symbols that a v142 toolset can’t resolve. A resource script that doesn’t define VS_VERSION_INFO as 1 names the version resource with a string no version API ever looks up, so Explorer quietly shows nothing. PowerShell doesn’t expand $env: variables inside a bare native argument. CMake’s regular expressions have no {n} quantifier. And a config-less build defaults to Debug, leaving the installer target with no files to package.

Two bugs that aren’t mine

The two biggest finds were in Valve’s code, and they’re both filed with evidence. The first one I logged is that SteamVR’s normal behaviour for library-launched NON STEAM applications: end the app, the session ends. From the outside that’s exactly what it looks like. Then I captured the logs, and vrmonitor states the actual reason verbatim, “Quitting SteamVR at the request of a driver”, eight milliseconds after the application’s quit transition completes. The vrlink driver, which both Steam Frame wireless sessions and Quest-over-Steam-Link sessions run, is asking for everything to quit, and the Steam client gets signalled too. SteamVR’s own Media Player triggers it, so it isn’t anything my registration did, and it fires with every third-party overlay disabled, so it isn’t an overlay either. There are no crash dumps, because nothing crashed; the exit is orderly and driver-requested. The aftermath is worse though. Reconnecting from the headset (Steam Link on Quest or Steam Frame alike) shows “Steam Launch Failed”, and once SteamVR is coaxed back up it sits in a loop re-requesting static properties about three times a second until a watchdog aborts it every eleven seconds, a black unresponsive window on the desktop. That loop survives a Steam restart and a headset reboot; on the Frame, only a full PC reboot clears it. Filed with three captured log sets: steamcommunity.com/app/250820/discussions/3.

The second is that the Frame’s Library never fetches the NON STEAM cards art at all. Cards for registered applications come up as placeholders on Steam Frame, while the same PC on the same build serves the identical registrations to a Quest over Steam Link with art intact. The server side is healthy, the image endpoint returns 200 with byte-identical PNGs, so I ran two instrumented ninety-second browsing sessions, one per headset, web cache wiped beforehand and a TCP logger on the server port. The Quest session fetched a registered game’s portrait cover live, a 593 KB server-resized image. The Frame session made zero image requests. Nothing on the headset ever asks. It’s documented as known behaviour in the repository for now, and filed with the evidence bundle. steamcommunity.com/app/250820/discussions/3, including the instrumented A/B session logs.

Where it stands

There’s an installer now, NSIS, with a post-install page explaining how games become visible in the Library. CI builds and publishes a release on every push to main, versioned with a CalVer stamp everywhere a version belongs, from the window title to Add/Remove Programs. Bug reports and feature requests stay open to everyone support is gated to anyone that has donated via Github sponsors because like everyone I have bills to pay 😉 github.com/sponsors/BrianGilbert, the ReviveNext repository’s own sponsor button..