From 5b79f77d5dadf5341ed569af01dea198146cd97d Mon Sep 17 00:00:00 2001
From: FakeMichau <49685661+FakeMichau@users.noreply.github.com>
Date: Fri, 22 Aug 2025 13:56:50 +0200
Subject: [PATCH] Fix slDLSSGSetOptions hooks when using steam overlay; force
DLSSG Auto to always mean On; marker fix for Roadcraft
---
OptiScaler/DllNames.h | 1 +
OptiScaler/OptiScaler.vcxproj | 4 +-
OptiScaler/Util.cpp | 15 ++-
OptiScaler/Util.h | 1 +
OptiScaler/dllmain.cpp | 8 ++
OptiScaler/hooks/Kernel_Hooks.h | 35 +++++++
OptiScaler/hooks/Streamline_Hooks.cpp | 139 ++++++++++++++++++++++----
OptiScaler/hooks/Streamline_Hooks.h | 12 +++
OptiScaler/misc/Quirks.h | 4 +
9 files changed, 197 insertions(+), 22 deletions(-)
diff --git a/OptiScaler/DllNames.h b/OptiScaler/DllNames.h
index c2a431f3..45a2e919 100644
--- a/OptiScaler/DllNames.h
+++ b/OptiScaler/DllNames.h
@@ -147,6 +147,7 @@ DEFINE_NAME_VECTORS(slInterposer, "sl.interposer");
DEFINE_NAME_VECTORS(slDlss, "sl.dlss");
DEFINE_NAME_VECTORS(slDlssg, "sl.dlss_g");
DEFINE_NAME_VECTORS(slReflex, "sl.reflex");
+DEFINE_NAME_VECTORS(slPcl, "sl.pcl");
DEFINE_NAME_VECTORS(slCommon, "sl.common");
DEFINE_NAME_VECTORS(xess, "libxess");
diff --git a/OptiScaler/OptiScaler.vcxproj b/OptiScaler/OptiScaler.vcxproj
index 579d1fc9..a9d923fa 100644
--- a/OptiScaler/OptiScaler.vcxproj
+++ b/OptiScaler/OptiScaler.vcxproj
@@ -259,7 +259,7 @@ copy "$(SolutionDir)external\directx_agility_sdk\LICENSE.txt" $(SolutionDir)x64\
copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
- powershell -Command "$date = Get-Date -Format 'yyyyMMdd_HHmmss'; Set-Content -Path '$(ProjectDir)resource_build_date.h' -Value ('#define VER_BUILD_DATE ""' + $date + '"""')
+ powershell -Command "Start-Sleep -Seconds 2; $date = Get-Date -Format 'yyyyMMdd_HHmmss'; Set-Content -Path '$(ProjectDir)resource_build_date.h' -Value ('#define VER_BUILD_DATE ""' + $date + '"""')
@@ -289,7 +289,7 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
powershell -Command "$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'; $fileName = '$(OutDir)OptiScaler_' + $timestamp + '.7z'; $backupFileName = '$(OutDir)OptiScaler_' + $timestamp + '_Backup.7z'; & '7z.exe' a -t7z $fileName '$(TargetPath)'; & '7z.exe' a -t7z $backupFileName '$(OutDir)$(TargetName).*'"
- powershell -Command "$date = Get-Date -Format 'yyyyMMdd_HHmmss'; Set-Content -Path '$(ProjectDir)resource_build_date.h' -Value ('#define VER_BUILD_DATE ""' + $date + '"""')
+ powershell -Command "Start-Sleep -Seconds 2; $date = Get-Date -Format 'yyyyMMdd_HHmmss'; Set-Content -Path '$(ProjectDir)resource_build_date.h' -Value ('#define VER_BUILD_DATE ""' + $date + '"""')
diff --git a/OptiScaler/Util.cpp b/OptiScaler/Util.cpp
index 63b95453..09b8e004 100644
--- a/OptiScaler/Util.cpp
+++ b/OptiScaler/Util.cpp
@@ -18,15 +18,14 @@ typedef BOOL (*PFN_VerQueryValueW)(LPCVOID pBlock, LPCWSTR lpSubBlock, LPVOID* l
/// Caller module filename
std::string Util::WhoIsTheCaller(void* returnAddress)
{
- HMODULE hModule = NULL;
+
char callerPath[MAX_PATH] = { 0 };
// Get the return address from the current function call.
// void* returnAddress = _ReturnAddress();
// Get the base address of the module containing the return address.
- if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
- (LPCSTR) returnAddress, &hModule))
+ if (HMODULE hModule = GetCallerModule(returnAddress); hModule != nullptr)
{
// Get the full path of the calling module.
GetModuleFileNameA(hModule, callerPath, sizeof(callerPath));
@@ -38,6 +37,16 @@ std::string Util::WhoIsTheCaller(void* returnAddress)
return "";
}
+HMODULE Util::GetCallerModule(void* returnAddress)
+{
+ HMODULE hModule = NULL;
+
+ GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ (LPCSTR) returnAddress, &hModule);
+
+ return hModule;
+}
+
std::wstring Util::GetWindowTitle(HWND hwnd)
{
const int maxLength = 512;
diff --git a/OptiScaler/Util.h b/OptiScaler/Util.h
index 6e0493e4..c82ca637 100644
--- a/OptiScaler/Util.h
+++ b/OptiScaler/Util.h
@@ -43,6 +43,7 @@ std::wstring GetWindowTitle(HWND hwnd);
std::optional FindFilePath(const std::filesystem::path& startDir,
const std::filesystem::path fileName);
std::string WhoIsTheCaller(void* returnAddress);
+HMODULE GetCallerModule(void* returnAddress);
MonitorInfo GetMonitorInfoForWindow(HWND hwnd);
MonitorInfo GetMonitorInfoForOutput(IDXGIOutput* pOutput);
diff --git a/OptiScaler/dllmain.cpp b/OptiScaler/dllmain.cpp
index d3ce991a..741a65ee 100644
--- a/OptiScaler/dllmain.cpp
+++ b/OptiScaler/dllmain.cpp
@@ -818,6 +818,14 @@ static void CheckWorkingMode()
StreamlineHooks::hookReflex(slReflex);
}
+ HMODULE slPcl = nullptr;
+ slPcl = GetDllNameWModule(&slPclNamesW);
+ if (slPcl != nullptr)
+ {
+ LOG_DEBUG("sl.pcl.dll already in memory");
+ StreamlineHooks::hookPcl(slPcl);
+ }
+
HMODULE slCommon = nullptr;
slCommon = GetDllNameWModule(&slCommonNamesW);
if (slCommon != nullptr)
diff --git a/OptiScaler/hooks/Kernel_Hooks.h b/OptiScaler/hooks/Kernel_Hooks.h
index e0e04d6d..cee5d02c 100644
--- a/OptiScaler/hooks/Kernel_Hooks.h
+++ b/OptiScaler/hooks/Kernel_Hooks.h
@@ -210,6 +210,23 @@ class KernelHooks
return reflexModule;
}
+ // sl.pcl.dll
+ if (CheckDllName(&lcaseLibName, &slPclNames))
+ {
+ auto pclModule = KernelBaseProxy::LoadLibraryExA_()(lpLibFullPath, NULL, 0);
+
+ if (pclModule != nullptr)
+ {
+ StreamlineHooks::hookPcl(pclModule);
+ }
+ else
+ {
+ LOG_ERROR("Trying to load dll: {}", lcaseLibName);
+ }
+
+ return pclModule;
+ }
+
// sl.common.dll
if (CheckDllName(&lcaseLibName, &slCommonNames))
{
@@ -683,6 +700,24 @@ class KernelHooks
return reflexModule;
}
+ // sl.pcl.dll
+ if (CheckDllNameW(&lcaseLibName, &slPclNamesW) ||
+ (lcaseLibName.contains(L"/versions/") && lcaseLibName.contains(L"/sl_pcl_")))
+ {
+ auto pclModule = KernelBaseProxy::LoadLibraryExW_()(lpLibFullPath, NULL, 0);
+
+ if (pclModule != nullptr)
+ {
+ StreamlineHooks::hookPcl(pclModule);
+ }
+ else
+ {
+ LOG_ERROR("Trying to load dll as sl.pcl: {}", lcaseLibNameA);
+ }
+
+ return pclModule;
+ }
+
// sl.common.dll
if (CheckDllNameW(&lcaseLibName, &slCommonNamesW) ||
(lcaseLibName.contains(L"/versions/") && lcaseLibName.contains(L"/sl_common_")))
diff --git a/OptiScaler/hooks/Streamline_Hooks.cpp b/OptiScaler/hooks/Streamline_Hooks.cpp
index 7ebc72c0..262846ed 100644
--- a/OptiScaler/hooks/Streamline_Hooks.cpp
+++ b/OptiScaler/hooks/Streamline_Hooks.cpp
@@ -24,6 +24,7 @@ decltype(&slAllocateResources) StreamlineHooks::o_slAllocateResources = nullptr;
decltype(&slSetConstants) StreamlineHooks::o_slSetConstants = nullptr;
decltype(&slGetNativeInterface) StreamlineHooks::o_slGetNativeInterface = nullptr;
decltype(&slSetD3DDevice) StreamlineHooks::o_slSetD3DDevice = nullptr;
+decltype(&slGetNewFrameToken) StreamlineHooks::o_slGetNewFrameToken = nullptr;
decltype(&sl1::slInit) StreamlineHooks::o_slInit_sl1 = nullptr;
@@ -46,6 +47,10 @@ StreamlineHooks::PFN_slOnPluginLoad StreamlineHooks::o_reflex_slOnPluginLoad = n
decltype(&slReflexSetOptions) StreamlineHooks::o_slReflexSetOptions = nullptr;
sl::ReflexMode StreamlineHooks::reflexGamesLastMode = sl::ReflexMode::eOff;
+// PCL
+StreamlineHooks::PFN_slGetPluginFunction StreamlineHooks::o_pcl_slGetPluginFunction = nullptr;
+decltype(&slPCLSetMarker) StreamlineHooks::o_slPCLSetMarker = nullptr;
+
// Common
StreamlineHooks::PFN_slGetPluginFunction StreamlineHooks::o_common_slGetPluginFunction = nullptr;
StreamlineHooks::PFN_slOnPluginLoad StreamlineHooks::o_common_slOnPluginLoad = nullptr;
@@ -460,27 +465,27 @@ bool StreamlineHooks::hkcommon_slOnPluginLoad(void* params, const char* loaderJS
sl::Result StreamlineHooks::hkslDLSSGSetOptions(const sl::ViewportHandle& viewport, const sl::DLSSGOptions& options)
{
- if (State::Instance().api != API::Vulkan)
- return o_slDLSSGSetOptions(viewport, options);
+ // Make DLSSG auto always mean On
+ sl::DLSSGOptions newOptions = options;
+ newOptions.mode = newOptions.mode == sl::DLSSGMode::eOff ? sl::DLSSGMode::eOff : sl::DLSSGMode::eOn;
- // Only matters for Vulkan, DX doesn't use this delay
- if (options.mode != sl::DLSSGMode::eOff && !MenuOverlayBase::IsVisible())
- State::Instance().delayMenuRenderBy = 10;
-
- if (MenuOverlayBase::IsVisible())
+ if (State::Instance().api == API::Vulkan)
{
- sl::DLSSGOptions newOptions = options;
- newOptions.mode = sl::DLSSGMode::eOff;
- newOptions.flags |= sl::DLSSGFlags::eRetainResourcesWhenOff;
+ // Only matters for Vulkan, DX doesn't use this delay
+ if (options.mode != sl::DLSSGMode::eOff && !MenuOverlayBase::IsVisible())
+ State::Instance().delayMenuRenderBy = 10;
- LOG_TRACE("DLSSG Modified Mode: {}", (uint32_t) newOptions.mode);
- ReflexHooks::setDlssgDetectedState(false);
- return o_slDLSSGSetOptions(viewport, newOptions);
+ if (MenuOverlayBase::IsVisible())
+ {
+ newOptions.mode = sl::DLSSGMode::eOff;
+ newOptions.flags |= sl::DLSSGFlags::eRetainResourcesWhenOff;
+ ReflexHooks::setDlssgDetectedState(false);
+ }
}
- // Can't tell if eAuto means enabled or disabled
- ReflexHooks::setDlssgDetectedState(options.mode == sl::DLSSGMode::eOn);
- return o_slDLSSGSetOptions(viewport, options);
+ LOG_TRACE("DLSSG Modified Mode: {}", magic_enum::enum_name(newOptions.mode));
+
+ return o_slDLSSGSetOptions(viewport, newOptions);
}
bool StreamlineHooks::hkreflex_slOnPluginLoad(void* params, const char* loaderJSON, const char** pluginJSON)
@@ -528,8 +533,18 @@ void* StreamlineHooks::hkdlssg_slGetPluginFunction(const char* functionName)
return &hkdlssg_slOnPluginLoad;
}
- if (strcmp(functionName, "slDLSSGSetOptions") == 0 && State::Instance().api == API::Vulkan)
+ if (strcmp(functionName, "slDLSSGSetOptions") == 0)
{
+ // Give steam overlay the original as it seems to be hooking it
+ auto steamOverlay = KernelBaseProxy::GetModuleHandleA_()("gameoverlayrenderer64.dll");
+ if (steamOverlay != nullptr)
+ {
+ if (HMODULE callerModule = Util::GetCallerModule(_ReturnAddress()); callerModule == steamOverlay)
+ {
+ return o_dlssg_slGetPluginFunction(functionName);
+ }
+ }
+
o_slDLSSGSetOptions = (decltype(&slDLSSGSetOptions)) o_dlssg_slGetPluginFunction(functionName);
return &hkslDLSSGSetOptions;
}
@@ -582,6 +597,45 @@ void* StreamlineHooks::hkreflex_slGetPluginFunction(const char* functionName)
return o_reflex_slGetPluginFunction(functionName);
}
+sl::Result StreamlineHooks::hkslPCLSetMarker(sl::PCLMarker marker, const sl::FrameToken& frame)
+{
+ // HACK for broken games
+ static uint64_t last_simulation_end_id = 0;
+ if (marker == sl::PCLMarker::eSimulationEnd)
+ {
+ last_simulation_end_id = frame;
+ }
+
+ if (marker == sl::PCLMarker::eSimulationStart && last_simulation_end_id >= frame && o_slGetNewFrameToken)
+ {
+ const uint64_t correction_offset = last_simulation_end_id - frame + 1;
+ uint32_t newFrameId = frame + correction_offset;
+
+ sl::FrameToken* newFramePointer {};
+ auto result = o_slGetNewFrameToken(newFramePointer, &newFrameId);
+
+ LOG_WARN("Simulation start marker sent after end marker, offset: {}", correction_offset);
+
+ result = o_slPCLSetMarker(marker, *newFramePointer);
+ return result;
+ }
+
+ return o_slPCLSetMarker(marker, frame);
+}
+
+void* StreamlineHooks::hkpcl_slGetPluginFunction(const char* functionName)
+{
+ LOG_DEBUG("{}", functionName);
+
+ if (strcmp(functionName, "slPCLSetMarker") == 0)
+ {
+ o_slPCLSetMarker = (decltype(&slPCLSetMarker)) o_pcl_slGetPluginFunction(functionName);
+ return &hkslPCLSetMarker;
+ }
+
+ return o_pcl_slGetPluginFunction(functionName);
+}
+
bool StreamlineHooks::hk_setVoid(void* self, const char* key, void** value)
{
// LOG_DEBUG("{}", key);
@@ -762,6 +816,8 @@ void StreamlineHooks::hookInterposer(HMODULE slInterposer)
KernelBaseProxy::GetProcAddress_()(slInterposer, "slGetNativeInterface"));
o_slSetD3DDevice = reinterpret_cast(
KernelBaseProxy::GetProcAddress_()(slInterposer, "slSetD3DDevice"));
+ o_slGetNewFrameToken = reinterpret_cast(
+ KernelBaseProxy::GetProcAddress_()(slInterposer, "slGetNewFrameToken")); // Not hooked
if (o_slInit != nullptr)
{
@@ -960,6 +1016,55 @@ void StreamlineHooks::hookReflex(HMODULE slReflex)
}
}
+// SL PCL
+
+void StreamlineHooks::unhookPcl()
+{
+ LOG_FUNC();
+
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+
+ if (o_pcl_slGetPluginFunction)
+ {
+ DetourDetach(&(PVOID&) o_pcl_slGetPluginFunction, hkpcl_slGetPluginFunction);
+ o_pcl_slGetPluginFunction = nullptr;
+ }
+
+ DetourTransactionCommit();
+}
+
+void StreamlineHooks::hookPcl(HMODULE slPcl)
+{
+ LOG_FUNC();
+
+ if (!slPcl)
+ {
+ LOG_WARN("Pcl module in NULL");
+ return;
+ }
+
+ if (!(State::Instance().gameQuirks & GameQuirk::FixSlSimulationMarkers))
+ return;
+
+ if (o_pcl_slGetPluginFunction)
+ unhookPcl();
+
+ o_pcl_slGetPluginFunction =
+ reinterpret_cast(KernelBaseProxy::GetProcAddress_()(slPcl, "slGetPluginFunction"));
+
+ if (o_pcl_slGetPluginFunction != nullptr)
+ {
+ LOG_TRACE("Hooking slGetPluginFunction in sl.pcl");
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+
+ DetourAttach(&(PVOID&) o_pcl_slGetPluginFunction, hkpcl_slGetPluginFunction);
+
+ DetourTransactionCommit();
+ }
+}
+
// SL COMMON
void StreamlineHooks::unhookCommon()
diff --git a/OptiScaler/hooks/Streamline_Hooks.h b/OptiScaler/hooks/Streamline_Hooks.h
index 0c86bf27..956d6721 100644
--- a/OptiScaler/hooks/Streamline_Hooks.h
+++ b/OptiScaler/hooks/Streamline_Hooks.h
@@ -33,6 +33,9 @@ class StreamlineHooks
static void unhookReflex();
static void hookReflex(HMODULE slReflex);
+ static void unhookPcl();
+ static void hookPcl(HMODULE slPcl);
+
static void unhookCommon();
static void hookCommon(HMODULE slCommon);
@@ -49,6 +52,7 @@ class StreamlineHooks
static decltype(&slSetConstants) o_slSetConstants;
static decltype(&slGetNativeInterface) o_slGetNativeInterface;
static decltype(&slSetD3DDevice) o_slSetD3DDevice;
+ static decltype(&slGetNewFrameToken) o_slGetNewFrameToken;
static decltype(&sl1::slInit) o_slInit_sl1;
@@ -86,6 +90,7 @@ class StreamlineHooks
static PFN_slGetPluginFunction o_dlssg_slGetPluginFunction;
static PFN_slOnPluginLoad o_dlssg_slOnPluginLoad;
static decltype(&slDLSSGSetOptions) o_slDLSSGSetOptions;
+ static decltype(&slDLSSGGetState) o_slDLSSGGetState;
static bool hkdlssg_slOnPluginLoad(void* params, const char* loaderJSON, const char** pluginJSON);
static sl::Result hkslSetConstants(const sl::Constants& values, const sl::FrameToken& frame,
@@ -105,6 +110,13 @@ class StreamlineHooks
static bool hkreflex_slSetConstants_sl1(const void* data, uint32_t frameIndex, uint32_t id);
static void* hkreflex_slGetPluginFunction(const char* functionName);
+ // PCL
+ static PFN_slGetPluginFunction o_pcl_slGetPluginFunction;
+ static decltype(&slPCLSetMarker) o_slPCLSetMarker;
+
+ static void* hkpcl_slGetPluginFunction(const char* functionName);
+ static sl::Result hkslPCLSetMarker(sl::PCLMarker marker, const sl::FrameToken& frame);
+
// Common
static PFN_slGetPluginFunction o_common_slGetPluginFunction;
static PFN_slOnPluginLoad o_common_slOnPluginLoad;
diff --git a/OptiScaler/misc/Quirks.h b/OptiScaler/misc/Quirks.h
index d0d3e0ad..ce001103 100644
--- a/OptiScaler/misc/Quirks.h
+++ b/OptiScaler/misc/Quirks.h
@@ -32,6 +32,7 @@ enum class GameQuirk : uint64_t
VulkanDLSSBarrierFixup,
ForceUnrealEngine,
NoFSRFGFirstSwapchain,
+ FixSlSimulationMarkers,
// Don't forget to add the new entry to printQuirks
_
};
@@ -199,6 +200,7 @@ static const QuirkEntry quirkTable[] = {
QUIRK_ENTRY("minecraft.windows.exe", GameQuirk::KernelBaseHooks),
QUIRK_ENTRY("prey.exe", GameQuirk::DontUseNTShared, GameQuirk::DisableOptiXessPipelineCreation,
GameQuirk::DisableDxgiSpoofing),
+ QUIRK_ENTRY("roadcraft - retail.exe", GameQuirk::FixSlSimulationMarkers),
// VULKAN
// ------
@@ -275,6 +277,8 @@ static void printQuirks(flag_set& quirks)
spdlog::info("Quirk: Skipping upscaling for first 10 frames");
if (quirks & GameQuirk::NoFSRFGFirstSwapchain)
spdlog::info("Quirk: Skip turning the first swapchain created into an FSR swapchain");
+ if (quirks & GameQuirk::FixSlSimulationMarkers)
+ spdlog::info("Quirk: Correct simulation start marker's frame id");
return;
}