openvr_fsr
openvr_fsr copied to clipboard
Half-Life: Alyx fails to boot with modified dll file
As the title says, the game fails to boot on my system using the FSR-Enabled openvr_api.dll file, claiming it failed to initialize VR space. RenderScale set to 0.59, all other settings as default.
Yes, this is known and mentioned in the readme :) No short-term fix, I'm afraid.
@fholger it's not about "game doesn't like replacing dll", your DLL simply doesn't have certain functionality. And maybe there actually is a short-term fix:
List of symbols in original DLL:
openvr_api.dll LiquidVR openvr_api.dll VRCompositorSystemInternal openvr_api.dll VRControlPanel openvr_api.dll VRHeadsetView openvr_api.dll VROculusDirect openvr_api.dll VRPaths openvr_api.dll VRRenderModelsInternal openvr_api.dll VRSceneGraph openvr_api.dll VRTrackedCameraInternal openvr_api.dll VRVirtualDisplay openvr_api.dll VR_GetGenericInterface openvr_api.dll VR_GetInitToken openvr_api.dll VR_GetRuntimePath openvr_api.dll VR_GetStringForHmdError openvr_api.dll VR_GetVRInitErrorAsEnglishDescription openvr_api.dll VR_GetVRInitErrorAsSymbol openvr_api.dll VR_InitInternal openvr_api.dll VR_InitInternal2 openvr_api.dll VR_IsHmdPresent openvr_api.dll VR_IsInterfaceVersionValid openvr_api.dll VR_IsRuntimeInstalled openvr_api.dll VR_RuntimePath openvr_api.dll VR_ShutdownInternal
And this is list of symbols in your DLL:
openvr_api.dll VRHeadsetView openvr_api.dll VR_GetGenericInterface openvr_api.dll VR_GetInitToken openvr_api.dll VR_GetRuntimePath openvr_api.dll VR_GetStringForHmdError openvr_api.dll VR_GetVRInitErrorAsEnglishDescription openvr_api.dll VR_GetVRInitErrorAsSymbol openvr_api.dll VR_InitInternal openvr_api.dll VR_InitInternal2 openvr_api.dll VR_IsHmdPresent openvr_api.dll VR_IsInterfaceVersionValid openvr_api.dll VR_IsRuntimeInstalled openvr_api.dll VR_RuntimePath openvr_api.dll VR_ShutdownInternal
I'm not sure but maybe exporting empty placeholder functions may help. If not, every function from original library must be correctly implemented and exported in new library.
I am aware of that, but these functions are not part of the source code Valve released for OpenVR, so there is no template for these functions. But since they are not part of the released source, they should also not typically be used by games due to not being exposed in the headers. Of course, it's possible Valve uses a custom build or functionality for HL Alyx, but that most definitely rules out the easy fix.
I am aware of that, but these functions are not part of the source code Valve released for OpenVR, so there is no template for these functions.
Looking at the assembly code of openvr_api.dll
from the game and using the source of openvr_fsr as a template, it is possible to deduce the source code of the missing exported functions.
I loaded C:\Steam\steamapps\common\Half-Life Alyx\game\bin\win64\openvr_api.dll
in IDA Freeware and could confirm @romanshuvalov list of missing functions. They all have the same pattern and basically just call VR_GetGenericInterface()
. E.g., this is the disassembled LiquidVR
function:
public LiquidVR
LiquidVR proc near
sub rsp, 28h
mov rax, cs:m_pLiquidVR
test rax, rax
jnz short loc_1800012D5
xor edx, edx
lea rcx, aIliquidvr001 ; "ILiquidVR_001"
call VR_GetGenericInterface
mov cs:m_pLiquidVR, rax
loc_1800012D5:
add rsp, 28h
retn
This can be translated to C as something like:
static const char * const ILiquidVR_Version = "ILiquidVR_001";
public IntPtr m_pLiquidVR;
void *LiquidVR()
{
if ( m_pLiquidVR == nullptr )
{
m_pLiquidVR = VR_GetGenericInterface( ILiquidVR_Version, NULL);
}
return m_pLiquidVR;
}
So to recreate these functions it would require declaring the constants, the pointers and the functions:
static const char * const ILiquidVR_Version = "ILiquidVR_001";
static const char * const IVRCompositorSystemInternal_Version = "IVRCompositorSystemInternal_001";
static const char * const IVRControlPanel_Version = "IVRControlPanel_006";
static const char * const IVROculusDirect_Version = "IVROculusDirect_001";
static const char * const IVRPaths_Version = "IVRPaths_001";
static const char * const IVRRenderModelsInternal_Version = "IVRRenderModelsInternal_XXX";
static const char * const IVRSceneGraph_Version = "IVRSceneGraph_001";
static const char * const IVRTrackedCameraInternal_Version = "IVRTrackedCameraInternal_001";
static const char * const IVRVirtualDisplay_Version = "IVRVirtualDisplay_001";
public IntPtr m_pLiquidVR;
public IntPtr m_pVRCompositorSystemInternal;
public IntPtr m_pVRControlPanel;
public IntPtr m_pVROculusDirect;
public IntPtr m_pVRPaths;
public IntPtr m_pVRRenderModelsInternal;
public IntPtr m_pVRSceneGraph;
public IntPtr m_pVRTrackedCameraInternal;
public IntPtr m_pVRVirtualDisplay;
void *LiquidVR() {
if ( m_pLiquidVR == nullptr )
m_pLiquidVR = VR_GetGenericInterface( ILiquidVR_Version, nullptr);
return m_pLiquidVR;
}
void *VRCompositorSystemInternal() {
if ( m_pVRCompositorSystemInternal == nullptr )
m_pVRCompositorSystemInternal = VR_GetGenericInterface( IVRCompositorSystemInternal_Version, nullptr);
return m_pVRCompositorSystemInternal;
}
void *VRControlPanel() {
if ( m_pVRControlPanel == nullptr )
m_pVRControlPanel = VR_GetGenericInterface( IVRControlPanel_Version, nullptr);
return m_pVRControlPanel;
}
void *VROculusDirect() {
if ( m_pVROculusDirect == nullptr )
m_pVROculusDirect = VR_GetGenericInterface( IVROculusDirect_Version, nullptr);
return m_pVROculusDirect;
}
void *VRPaths() {
if ( m_pVRPaths == nullptr )
m_pVRPaths = VR_GetGenericInterface( IVRPaths_Version, nullptr);
return m_pVRPaths;
}
void *VRRenderModelsInternal() {
if ( m_pVRRenderModelsInternal == nullptr )
m_pVRRenderModelsInternal = VR_GetGenericInterface( IVRRenderModelsInternal_Version, nullptr);
return m_pVRRenderModelsInternal;
}
void *VRSceneGraph() {
if ( m_pVRSceneGraph == nullptr )
m_pVRSceneGraph = VR_GetGenericInterface( IVRSceneGraph_Version, nullptr);
return m_pVRSceneGraph;
}
void *VRTrackedCameraInternal() {
if ( m_pVRTrackedCameraInternal == nullptr )
m_pVRTrackedCameraInternal = VR_GetGenericInterface( IVRTrackedCameraInternal_Version, nullptr);
return m_pVRTrackedCameraInternal;
}
void *VRVirtualDisplay() {
if ( m_pVRVirtualDisplay == nullptr )
m_pVRVirtualDisplay = VR_GetGenericInterface( IVRVirtualDisplay_Version, nullptr);
return m_pVRVirtualDisplay;
}
Also, the pointers must be cleared (set to nullptr
) at the end of VR_ShutdownInternal
:
m_pLiquidVR = nullptr;
m_pVRCompositorSystemInternal = nullptr;
m_pVRControlPanel = nullptr;
m_pVROculusDirect = nullptr;
m_pVRPaths = nullptr;
m_pVRRenderModelsInternal = nullptr;
m_pVRSceneGraph = nullptr;
m_pVRTrackedCameraInternal = nullptr;
m_pVRVirtualDisplay = nullptr;
Will these changes make openvr_fsr work with Half-Life: Alyx? I have no idea, but it might be worth giving it a try.
Progress report summary: I added the missing functions, they don't seem to be called but the code runs further along anyway. An access violation in IVRSystem_GetRecommendedRenderTargetSize()
crashes the game.
Starting Half-Life: Alyx with unpatched openvr_api.dll v. 2.0:
- Shows a pop-up "Unable to Start Game" - "A game file appears to be missing or corrupted. In the Steam client go to the game's properties. In the 'Local Files' tab select 'Verify Integrity of Game Cache' to have Steam double-check the game's installation."
- Shows a pop-up "Error" - "CAppSystemDict::LoadSystemAndDependencies(): CAppSystemDict:Unable to load module vr (Dependency of application), error 127"
- Steam automatically downloads the game original openvr_api.dll.
- No openvr_mod.log is created.
- The game crashes and saves a minidump. Among other details, the minidump contains:
VR NOT ENABLED
Launch path: C:\Steam\steamapps\common\Half-Life Alyx\game\bin\win64\hlvr.exe
g_pRenderDeviceMgr is NULL. Driver data unavailable
Console History (reversed)
8(227.976031): CAppSystemDict:Unable to load module vr (Dependency of application), error 127
7(0.325067): Sending Steam API content notification
6(0.324831): WARNING: Local content might be corrupt or missing files
5(0.319209): SteamVR initialization begin...
4(0.212197): CSteam3Client::Activate succeeded. SteamID is [U:1:xxxxxxxx] (xxxxxxxxxxxxxxxxx), AppID is 546560
3(0.207766): SteamAPI_Init succeeded. SteamID is [U:1:xxxxxxxx] (xxxxxxxxxxxxxxxxx), AppID is 546560
2(0.055898): Steam AppId(546560)
1(0.053951): Unable to read steam.inf from hlvr!
I patched openvr_api_public.cpp
, including empty functions and some calls to Log()
the functions being called, e.g.:
VR_EXPORT_INTERFACE void *VR_CALLTYPE LiquidVR();
void *LiquidVR() {
Log() << "TRACE: [openvr_api_public.cpp] LiquidVR()\n";
return nullptr;
}
With this new DLL the game still won't start, but:
- There are no pop-up errors, and the DLL is not replaced by Steam.
- Minidump show progress, including a message with "Recommended render target size":
SteamVR Version: 1.20.4
SteamVR HMD: (0Hz)
Launch path: C:\Steam\steamapps\common\Half-Life Alyx\game\bin\win64\hlvr.exe
g_pRenderDeviceMgr is NULL. Driver data unavailable
Console History (reversed)
6(0.839426): Recommended render target size: 424 x 428
5(0.324118): SteamVR initialization begin...
4(0.212707): CSteam3Client::Activate succeeded. SteamID is [U:1:xxxxxxxx] (xxxxxxxxxxxxxxxxx), AppID is 546560
3(0.209934): SteamAPI_Init succeeded. SteamID is [U:1:xxxxxxxx] (xxxxxxxxxxxxxxxxx), AppID is 546560
2(0.054402): Steam AppId(546560)
1(0.052336): Unable to read steam.inf from hlvr!
-
openvr_mod.log
is created, with:
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
TRACE: [openvr_api_public.cpp] VR_InitInternal2()
Initializing hooks...
TRACE: [openvr_api_public.cpp] VR_LoadHmdSystemInternal()
TRACE: [openvr_api_public.cpp] VR_IsInterfaceVersionValid()
TRACE: [openvr_api_public.cpp] VR_GetInitToken()
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
Requested interface IVRSystem_021
Injecting GetRecommendedRenderTargetSize into IVRSystem_021
Analyzing the exception in the minidump:
ExceptionAddress: 00007fff4d5dd852 (openvr_api!`anonymous namespace'::IVRSystem_GetRecommendedRenderTargetSize+0x0000000000000082)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 0000000000000001
Parameter[1]: 00007fff4d81a7e8
Attempt to write to address 00007fff4d81a7e8
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.
FAULTING_LOCAL_VARIABLE_NAME: pnWidth
STACK_TEXT:
00000024`88ccf5d0 00007fff`4d738f3b : 00007fff`43f6ab98 00007fff`4d81a7e8 00007fff`4d849dc8 00007fff`4d81bb20 : openvr_api!`anonymous namespace'::IVRSystem_GetRecommendedRenderTargetSize+0x82
00000024`88ccf600 00007fff`3d1118ac : 000001dc`00000000 00000000`ff00ff00 000001dc`c7fe0700 00007fff`3d47f458 : vr+0x8f3b
00000024`88ccf800 00007fff`3d15b99a : 00000000`ff00ff00 00000000`00000000 00000000`00000000 00007fff`46f1770d : engine2+0x1018ac
00000024`88ccf8d0 00007fff`3d15f638 : 00007ff6`73680000 00000000`00000000 00000000`0000000a 00007fff`3d490700 : engine2+0x14b99a
00000024`88ccf970 00007ff6`73681313 : 00000000`0000000a 00000000`00000000 000001dc`c52c6213 00000000`00000000 : engine2!Source2Main+0x1c8
00000024`88ccfa00 00007ff6`736815f7 : 00000000`0000000a 00000000`00000000 00000000`00000000 00000000`00000000 : hlvr+0x1313
00000024`88ccfb50 00007fff`c7cd7034 : 00000000`00000000 00000000`00000000 00000000`00000000 00007fff`5b4b0000 : hlvr+0x15f7
00000024`88ccfb90 00007fff`c8542651 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
00000024`88ccfbc0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
FAULTING_SOURCE_LINE: C:\Steam\steamapps\common\!openvr_fsr_v2.0\openvr_fsr-fsr_v2.0\src\postprocess\VrHooks.cpp
FAULTING_SOURCE_CODE:
44: if (Config::Instance().fsrEnabled && Config::Instance().renderScale < 1) {
> 45: *pnWidth *= Config::Instance().renderScale;
46: *pnHeight *= Config::Instance().renderScale;
47: }
So I patched IVRSystem_GetRecommendedRenderTargetSize()
:
if (Config::Instance().fsrEnabled && Config::Instance().renderScale < 1) {
Log() << "TRACE: IVRSystem_GetRecommendedRenderTargetSize()\n";
Log() << "pnWidth: " << pnWidth << ", *pnWidth: " << *pnWidth << ", Config::Instance().renderScale: " << Config::Instance().renderScale << std::endl;
Log() << "pnHeight: " << pnHeight << ", *pnHeight: " << *pnHeight << std::endl;
*pnWidth *= Config::Instance().renderScale;
*pnHeight *= Config::Instance().renderScale;
}
... and the new openvr_mod.log
has:
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
TRACE: [openvr_api_public.cpp] VR_InitInternal2()
Initializing hooks...
TRACE: [openvr_api_public.cpp] VR_LoadHmdSystemInternal()
TRACE: [openvr_api_public.cpp] VR_IsInterfaceVersionValid()
TRACE: [openvr_api_public.cpp] VR_GetInitToken()
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
Requested interface IVRSystem_021
Injecting GetRecommendedRenderTargetSize into IVRSystem_021
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
Requested interface IVRSystem_021
TRACE: IVRSystem_GetRecommendedRenderTargetSize()
pnWidth: 00007FFF4DE19140, *pnWidth: 2124, Config::Instance().renderScale: 0.2
pnHeight: 00007FFF4DE19144, *pnHeight: 2144
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
Requested interface IVRApplications_007
TRACE: [openvr_api_public.cpp] VR_GetGenericInterface()
Requested interface IVROverlay_024
TRACE: IVRSystem_GetRecommendedRenderTargetSize()
pnWidth: 00007FFF4DDEA7E8, *pnWidth: 1953724787, Config::Instance().renderScale: 0.2
pnHeight: 00007FFF4DE19DC8, *pnHeight: 0
FWIW, the DLL works in another game without issues (Blade Runner 9732).
Sorry about the long post.
Any ideas?
There has to be a better way to do this, but I got the game to start by adding this to IVRSystem_GetRecommendedRenderTargetSize()
:
Log() << "TRACE: IVRSystem_GetRecommendedRenderTargetSize()\n";
Log() << "pnWidth: " << pnWidth << std::endl;
Log() << "pnHeight: " << pnHeight << std::endl;
if (pnWidth == reinterpret_cast<uint32_t*>(0x000000003FF00000)) {
Log() << "INVALID POINTER (address)" << std::endl;
return;
}
if (*pnWidth == 0 || *pnHeight == 0) {
Log() << "INVALID POINTER (== 0)" << std::endl;
return;
}
Log() << "*pnWidth: " << *pnWidth << std::endl;
Log() << "*pnHeight: " << *pnHeight << std::endl;
With resulting openvr_mod.log
:
Initializing hooks...
Requested interface IVRSystem_021
Injecting GetRecommendedRenderTargetSize into IVRSystem_021
Requested interface IVRSystem_021
TRACE: IVRSystem_GetRecommendedRenderTargetSize()
pnWidth: 00007FFAEF5E9140
pnHeight: 00007FFAEF5E9144
*pnWidth: 2204
*pnHeight: 2236
Requested interface IVRApplications_007
Requested interface IVROverlay_024
TRACE: IVRSystem_GetRecommendedRenderTargetSize()
pnWidth: 00007FFAEF5BA7E8
pnHeight: 00007FFAEF5E9DC8
INVALID POINTER (== 0)
Requested interface IXrProto_001
Requested interface IVRInput_010
Requested interface IVROverlayView_003
Requested interface IVRInputInternal_002
Requested interface IVRMailbox_001
Requested interface IVRChaperone_003
Requested interface IVRCompositor_026
Injecting Submit into IVRCompositor_026
Requested interface IVRRenderModels_006
TRACE: IVRSystem_GetRecommendedRenderTargetSize()
pnWidth: 000000003FF00000
pnHeight: 00000000FFFFFFFF
INVALID POINTER (address)
Requested interface IVRControlPanel_006
Creating post-processing resources
Input texture is in SRGB color space
Creating output textures in format 28
Using AMD FidelityFX SuperResolution
Requested interface IVRSystem_022
Raw projection for eye 0: l -1.27994, r 1, t -1.19175, b 1.11061
Display is canted by 0 RAD
Projection center for eye 0: 0.561392, 0.517621
Requested interface IVRSystem_022
Raw projection for eye 1: l -1, r 1.27994, t -1.19175, b 1.11061
Display is canted by -0 RAD
Projection center for eye 1: 0.438608, 0.517621
Creating upscaled texture of size 5425x2750
Requested interface IVRSystem_022
Raw projection for eye 0: l -1.27994, r 1, t -1.19175, b 1.11061
Display is canted by 0 RAD
Projection center for eye 0: 0.561392, 0.517621
Requested interface IVRSystem_022
Raw projection for eye 1: l -1, r 1.27994, t -1.19175, b 1.11061
Display is canted by -0 RAD
Projection center for eye 1: 0.438608, 0.517621
Creating sharpened texture of size 5425x2750
Injecting PSSetSamplers into D3D11DeviceContext
Creating replacement sampler for 000002669387A1E0 with MIP LOD bias -0.376811
Creating replacement sampler for 00000266938786E0 with MIP LOD bias -0.376811
Creating replacement sampler for 0000026693879FA0 with MIP LOD bias -0.376811
Creating replacement sampler for 0000026693879C20 with MIP LOD bias -0.376811
Creating replacement sampler for 000002662E658220 with MIP LOD bias -0.376811
Creating replacement sampler for 000002669378C2A0 with MIP LOD bias -0.376811
Creating replacement sampler for 0000026693879460 with MIP LOD bias -0.376811
Creating replacement sampler for 000002669387A420 with MIP LOD bias -0.376811
Creating replacement sampler for 0000026693878DA0 with MIP LOD bias -0.376811
Creating replacement sampler for 0000000000000000 with MIP LOD bias -0.376811
Creating shader resource view for input texture 00000265D7037C38
Texture has size 4178x2118 and format 27
Creating replacement sampler for 000002662E657A20 with MIP LOD bias -0.376811
Creating replacement sampler for 00000266938796A0 with MIP LOD bias -0.376811
Creating replacement sampler for 0000026693878B60 with MIP LOD bias -0.376811
Creating replacement sampler for 000002669387A2E0 with MIP LOD bias -0.376811
Creating replacement sampler for 000002669378B520 with MIP LOD bias -0.376811
Creating replacement sampler for 000002669378B1E0 with MIP LOD bias -0.376811
Creating replacement sampler for 00000266151492A0 with MIP LOD bias -0.376811
Creating replacement sampler for 0000026693879B20 with MIP LOD bias -0.376811
Edit:
Found a better way using IsBadMemPtr
from https://gist.github.com/arbv/531040b8036050c5648fc55471d50352
if (IsBadMemPtr(TRUE, pnWidth, sizeof(pnWidth)) || IsBadMemPtr(TRUE, pnHeight, sizeof(pnHeight))) {
Log() << "INVALID POINTER (IsBadMemPtr)" << std::endl;
return;
}
Confirmed working on the menu.
But when I load a saved game:
Seems to load fine for me.
Not sure what's up with misplaced "sweet spot", I haven't used this mod before.
Edit: I remembered that I got the same Timed out error when tried my older approach (DLL proxy/redirection) after fixing Access violation. Although, it crashed even before menu. Also I wasn't sure on how to export missing functions so I was changing stuff semi-randomly. Perhaps I did something differently? Here's my diff: https://gist.github.com/skrimix/c38311227c26ce703d530d362bb3a4bc
Alyx has dynamic resolution scaling, so depending on the current scene and performance headroom, it may only render to a part of the texture. But the mod isn't aware and will always place the sweet spot as if the whole texture had been rendered to. That's why it's mismatched. Properly supporting this would require some refactoring; given how well Alyx performs compared to most other VR games, I'm not sure it's really worth the effort.
Kudos on getting it to work, though. :)
Using just dummy functions (returning nullptr
) didn't make the game load, but using the code as I posted originally, it load and play fine. From a quick test, the GPU load decrease was quite significant.
My guess is that this patch could also make FSR work in other games as well.
@fholger , would you be interested in adding this patch to your code? If you prefer a pull request, let us know.
And BTW, thanks for your work on openvr_fsr! 😄
Depends. I have no issue adding a bunch of function stubs, but I would be a bit more sceptical of some of the hacks that have been discussed in this thread :) A pull request ist definitely the best route to go, though.
I would be a bit more sceptical of some of the hacks that have been discussed in this thread :)
I wonder is there is a better solution than IsBadMemPtr
in IVRSystem_GetRecommendedRenderTargetSize
to detect an invalid pointer... or if there is any way to avoid the function being called with bad parameters.
A pull request ist definitely the best route to go, though.
Done.
Since this patch is probably not yet final, if anyone wants to give it a try you can get the patched DLL here and see how well (or not) it works in Half-Life: Alyx for you.
Alyx has dynamic resolution scaling, so depending on the current scene and performance headroom, it may only render to a part of the texture. But the mod isn't aware and will always place the sweet spot as if the whole texture had been rendered to. That's why it's mismatched. Properly supporting this would require some refactoring; given how well Alyx performs compared to most other VR games, I'm not sure it's really worth the effort.
Kudos on getting it to work, though. :)
You can close dynamic resolution scaling with (-console -vconsole +vr_fidelity_level_auto 0 +vr_fidelity_level 6) I guess level 6 for the most ideal resolution %151 - 2689x2988. When dynamic resolution working on medium tier computers, fidelity lvl is around lvl3 in most scenes, which is very annoying. If this works, it could offer a much better quality experience. Maybe it's worth it :))
Alyx has dynamic resolution scaling, so depending on the current scene and performance headroom, it may only render to a part of the texture. But the mod isn't aware and will always place the sweet spot as if the whole texture had been rendered to. That's why it's mismatched. Properly supporting this would require some refactoring; given how well Alyx performs compared to most other VR games, I'm not sure it's really worth the effort.
Kudos on getting it to work, though. :)
You can disable dynamic resolution scaling in the launch options: Level 3 is 100%, and it's what I use.
Game doesn't start anymore?