From f5dfe066ae60892aca6cd712ffe8b2d6cd13ffb7 Mon Sep 17 00:00:00 2001 From: cdozdil Date: Wed, 3 Jun 2026 17:08:39 +0300 Subject: [PATCH] Added late loading option for ASI files --- OptiScaler.ini | 5 ++ OptiScaler/Config.cpp | 3 + OptiScaler/Config.h | 1 + OptiScaler/dllmain.cpp | 69 ++++++++++++++++++- .../inputs/FG/Streamline_Inputs_Dx12.cpp | 5 +- 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/OptiScaler.ini b/OptiScaler.ini index f468290c..dc1387df 100644 --- a/OptiScaler.ini +++ b/OptiScaler.ini @@ -929,6 +929,11 @@ Path=auto ; true or false - Default (auto) is false LoadAsiPlugins=auto +; Delay before loading late-load *.asi files in plugins folder +; To set an ASI plugin to load late, add -loadlate to the it's filename before the extension, for example "myplugin-loadlate.asi" +; Integer value in seconds - Default (auto) is 30 +LateAsiPluginsDelay=auto + ; Loads SpecialK64.dll from game's exe folder ; Please create an empty SpecialK.dxgi file next to SpecialK64.dll ; to set SpecialK's working mode, otherwise it will not be activated diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp index 78c2ae67..d412f237 100644 --- a/OptiScaler/Config.cpp +++ b/OptiScaler/Config.cpp @@ -652,6 +652,7 @@ bool Config::Reload(std::filesystem::path iniPath) LoadSpecialK.set_from_config(readBool("Plugins", "LoadSpecialK")); LoadReShade.set_from_config(readBool("Plugins", "LoadReShade")); LoadAsiPlugins.set_from_config(readBool("Plugins", "LoadAsiPlugins")); + LateAsiPluginsDelay.set_from_config(readInt("Plugins", "LateAsiPluginsDelay")); } // HDR @@ -1322,6 +1323,8 @@ bool Config::SaveIni() ini.SetValue("Plugins", "LoadSpecialK", GetBoolValue(Instance()->LoadSpecialK.value_for_config()).c_str()); ini.SetValue("Plugins", "LoadReShade", GetBoolValue(Instance()->LoadReShade.value_for_config()).c_str()); ini.SetValue("Plugins", "LoadAsiPlugins", GetBoolValue(Instance()->LoadAsiPlugins.value_for_config()).c_str()); + ini.SetValue("Plugins", "LateAsiPluginsDelay", + GetIntValue(Instance()->LateAsiPluginsDelay.value_for_config()).c_str()); } // inputs diff --git a/OptiScaler/Config.h b/OptiScaler/Config.h index 3b713a2f..cd0f97c4 100644 --- a/OptiScaler/Config.h +++ b/OptiScaler/Config.h @@ -462,6 +462,7 @@ class Config CustomOptional LoadSpecialK { false }; CustomOptional LoadReShade { false }; CustomOptional LoadAsiPlugins { false }; + CustomOptional LateAsiPluginsDelay { 30 }; // Frame Generation CustomOptional FGInput { FGInput::NoFG }; diff --git a/OptiScaler/dllmain.cpp b/OptiScaler/dllmain.cpp index e1f6bc3f..e2f7def4 100644 --- a/OptiScaler/dllmain.cpp +++ b/OptiScaler/dllmain.cpp @@ -49,6 +49,7 @@ #include static std::vector _asiHandles; +static std::vector _lateLoadingEntries; static bool _passThruMode = false; typedef const char*(CDECL* PFN_wine_get_version)(void); @@ -226,7 +227,16 @@ void LoadAsiPlugins() if (ext == L".asi") { - HMODULE hMod = NtdllProxy::LoadLibraryExW_Ldr(entry.path().c_str(), NULL, 0); + std::wstring fileName = entry.path().filename().wstring(); + std::transform(fileName.begin(), fileName.end(), fileName.begin(), + [](wchar_t c) { return std::towlower(c); }); + + HMODULE hMod = nullptr; + + if (fileName.rfind(L"-loadlate") != std::wstring::npos) + _lateLoadingEntries.push_back(entry); + else + hMod = NtdllProxy::LoadLibraryExW_Ldr(entry.path().c_str(), NULL, 0); if (hMod != nullptr) { @@ -266,6 +276,63 @@ void LoadAsiPlugins() DWORD err = GetLastError(); LOG_ERROR(L"Failed to load: {}, error {:X}", entry.path().wstring(), err); } + + if (_lateLoadingEntries.size() > 0) + { + std::thread( + []() + { + auto delay = Config::Instance()->LateAsiPluginsDelay.value_or_default(); + LOG_INFO("Waiting {} seconds before loading late-load plugins...", delay); + std::this_thread::sleep_for(std::chrono::seconds(delay)); + + for (const auto& entry : _lateLoadingEntries) + { + HMODULE hMod = NtdllProxy::LoadLibraryExW_Ldr(entry.path().c_str(), NULL, 0); + + if (hMod != nullptr) + { + LOG_INFO(L"Loaded late: {}", entry.path().wstring()); + _asiHandles.push_back(hMod); + + auto init = + (PFN_InitializeASI) KernelBaseProxy::GetProcAddress_()(hMod, "InitializeASI"); + + auto patchResult = + (PFN_PatchResult) KernelBaseProxy::GetProcAddress_()(hMod, "PatchResult"); + + if (init != nullptr) + init(); + + if (patchResult != nullptr) + { + auto pr = patchResult(); + if (pr) + { + LOG_INFO("Game patching is successful"); + State::Instance().isOptiPatcherSucceed = true; + LOG_INFO("Disabling spoofing"); + + if (!Config::Instance()->DxgiSpoofing.has_value()) + Config::Instance()->DxgiSpoofing.set_volatile_value(false); + + if (!Config::Instance()->VulkanSpoofing.has_value()) + Config::Instance()->VulkanSpoofing.set_volatile_value(false); + + if (!Config::Instance()->VulkanExtensionSpoofing.has_value()) + Config::Instance()->VulkanExtensionSpoofing.set_volatile_value(false); + } + } + } + else + { + DWORD err = GetLastError(); + LOG_ERROR(L"Failed to load: {}, error {:X}", entry.path().wstring(), err); + } + } + }) + .detach(); + } } } } diff --git a/OptiScaler/inputs/FG/Streamline_Inputs_Dx12.cpp b/OptiScaler/inputs/FG/Streamline_Inputs_Dx12.cpp index fe8efc4e..f960e535 100644 --- a/OptiScaler/inputs/FG/Streamline_Inputs_Dx12.cpp +++ b/OptiScaler/inputs/FG/Streamline_Inputs_Dx12.cpp @@ -129,7 +129,7 @@ bool Sl_Inputs_Dx12::setConstants(const sl::Constants& values, uint32_t frameId) if (!config->FGEnabled.value_or_default()) { - LOG_TRACE("FG not active or paused"); + LOG_TRACE("FG not enabled"); return true; } else @@ -140,13 +140,12 @@ bool Sl_Inputs_Dx12::setConstants(const sl::Constants& values, uint32_t frameId) } else if (!fgOutput->IsActive() || fgOutput->IsPaused()) { - LOG_TRACE("FG not active or paused"); + LOG_TRACE("FG not active or paused (A:{}, P:{})", fgOutput->IsActive(), fgOutput->IsPaused()); return true; } } // Frame data part - // Nukem's function, licensed under GPLv3 auto loadCameraMatrix = [&]() {