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; }