From 2ad8557595af451673e45e6d83affd77911320d2 Mon Sep 17 00:00:00 2001 From: cdozdil Date: Mon, 16 Feb 2026 00:59:33 +0300 Subject: [PATCH] Added registry spoofing option & quirk. Thanks to @artur-graniszewski Added Black Myth quirk --- OptiScaler/Config.cpp | 2 + OptiScaler/Config.h | 1 + OptiScaler/dllmain.cpp | 11 ++ OptiScaler/hooks/Advapi32_Hooks.h | 254 +++++++++++++++++++++++++++++- OptiScaler/misc/Quirks.h | 5 + 5 files changed, 272 insertions(+), 1 deletion(-) diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp index 7cc49a72..319f5916 100644 --- a/OptiScaler/Config.cpp +++ b/OptiScaler/Config.cpp @@ -583,6 +583,7 @@ bool Config::Reload(std::filesystem::path iniPath) TargetVendorId.set_from_config(readUInt("Spoofing", "TargetVendorId")); TargetDeviceId.set_from_config(readUInt("Spoofing", "TargetDeviceId")); UESpoofIntelAtomics64.set_from_config(readBool("Spoofing", "UEIntelAtomics")); + SpoofRegistry.set_from_config(readBool("Spoofing", "Registry")); } // Inputs @@ -1239,6 +1240,7 @@ bool Config::SaveIni() GetIntValue(Instance()->TargetVendorId.value_for_config(), true).c_str()); ini.SetValue("Spoofing", "TargetDeviceId", GetIntValue(Instance()->TargetDeviceId.value_for_config(), true).c_str()); + ini.SetValue("Spoofing", "Registry", GetBoolValue(Instance()->SpoofRegistry.value_for_config()).c_str()); } // Plugins diff --git a/OptiScaler/Config.h b/OptiScaler/Config.h index 46a5c946..415da242 100644 --- a/OptiScaler/Config.h +++ b/OptiScaler/Config.h @@ -405,6 +405,7 @@ class Config CustomOptional TargetDeviceId; CustomOptional SpoofedGPUName { L"NVIDIA GeForce RTX 4090" }; CustomOptional UESpoofIntelAtomics64 { false }; + CustomOptional SpoofRegistry { false }; // Plugins CustomOptional PluginPath { L"plugins" }; diff --git a/OptiScaler/dllmain.cpp b/OptiScaler/dllmain.cpp index c406a428..8908f108 100644 --- a/OptiScaler/dllmain.cpp +++ b/OptiScaler/dllmain.cpp @@ -1306,6 +1306,12 @@ static void printQuirks(flag_set& quirks) state->detectedQuirks.push_back("Disable Resize Skip"); } + if (quirks & GameQuirk::SpoofRegistry) + { + spdlog::info("Quirk: Spoof Registry"); + state->detectedQuirks.push_back("Spoof Registry"); + } + return; } @@ -1459,6 +1465,11 @@ static void CheckQuirks() Config::Instance()->FGXeFGSkipResizeBuffers.set_volatile_value(false); } + if (quirks & GameQuirk::SpoofRegistry && !Config::Instance()->SpoofRegistry.has_value()) + { + Config::Instance()->SpoofRegistry.set_volatile_value(true); + } + // For Luma, we assume if Luma addon in game folder it's used const auto dir = Util::ExePath().parent_path(); bool lumaDetected = false; diff --git a/OptiScaler/hooks/Advapi32_Hooks.h b/OptiScaler/hooks/Advapi32_Hooks.h index f210a5f5..665eda87 100644 --- a/OptiScaler/hooks/Advapi32_Hooks.h +++ b/OptiScaler/hooks/Advapi32_Hooks.h @@ -8,10 +8,14 @@ const HKEY signatureMark = (HKEY) 0xFFFFFFFF13372137; typedef decltype(&RegOpenKeyExW) PFN_RegOpenKeyExW; typedef decltype(&RegEnumValueW) PFN_RegEnumValueW; typedef decltype(&RegCloseKey) PFN_RegCloseKey; +typedef decltype(&RegQueryValueExW) PFN_RegQueryValueExW; +typedef decltype(&RegQueryValueExA) PFN_RegQueryValueExA; static PFN_RegOpenKeyExW o_RegOpenKeyExW = nullptr; static PFN_RegEnumValueW o_RegEnumValueW = nullptr; static PFN_RegCloseKey o_RegCloseKey = nullptr; +static PFN_RegQueryValueExW o_RegQueryValueExW = nullptr; +static PFN_RegQueryValueExA o_RegQueryValueExA = nullptr; static LSTATUS hkRegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { @@ -81,7 +85,241 @@ static LSTATUS hkRegCloseKey(HKEY hKey) return o_RegCloseKey(hKey); } -// Replaces all values returned for SOFTWARE\\NVIDIA Corporation\\Global so could potentially cause issues on Nvidia +// Original implementation: +// https://github.com/artur-graniszewski/dlss-enabler-main/blob/1f8b24722f1b526ffb896ae62b6aa3ca766b0728/Utils/RegistryProxy.cpp#L137 +static LONG hkRegQueryValueExW(HKEY hKey, LPCWSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, + LPDWORD lpcbData) +{ + std::wstring valueName = L""; + + if (lpValueName != NULL) + { + valueName = std::wstring(lpValueName); + } + + if (valueName == L"HwSchMode") + { + // Check if lpcbData is not NULL + if (lpcbData != nullptr) + { + // If lpData is NULL, we're being asked for the required size + if (lpData == nullptr) + { + *lpcbData = sizeof(DWORD); // Indicate the required size + + // Set the type to REG_DWORD + if (lpType) + { + *lpType = REG_DWORD; + } + return ERROR_SUCCESS; + } + + // Check if the buffer is large enough + if (*lpcbData >= sizeof(DWORD)) + { + *(DWORD*) lpData = 2; + + // Set the type to REG_DWORD + if (lpType) + { + *lpType = REG_DWORD; + } + + // Set the size of the data returned + *lpcbData = sizeof(DWORD); + + // Return success + return ERROR_SUCCESS; + } + else + { + // Buffer is too small, return required size + *lpcbData = sizeof(DWORD); + return ERROR_MORE_DATA; + } + } + + // If lpcbData is NULL, return an error + return ERROR_INVALID_PARAMETER; + } + + // Call the original function + auto result = o_RegQueryValueExW(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); + + // Check the result of the query and if the valueName matches + if (result == ERROR_SUCCESS && !State::Instance().isRunningOnNvidia) + { + if (valueName == L"DriverVersion") + { + const std::wstring spoofedValue = L"31.0.15.5244"; + size_t spoofedValueSize = + (spoofedValue.size() + 1) * sizeof(wchar_t); // Size in bytes including null terminator + + if (lpData != nullptr && lpcbData != nullptr) + { + // Check if buffer size is sufficient + if (*lpcbData >= spoofedValueSize) + { + // Copy the spoofed value into lpData + std::memcpy(lpData, spoofedValue.c_str(), spoofedValueSize); + // Update lpcbData with the size of the spoofed value + *lpcbData = static_cast(spoofedValueSize); + } + else + { + // If buffer is too small, set lpcbData to the required size + *lpcbData = static_cast(spoofedValueSize); + result = ERROR_MORE_DATA; // Indicate that buffer was too small + } + } + } + + if (valueName == L"HardwareID") + { + // Handle REG_SZ type + std::wstring data(reinterpret_cast(lpData), *lpcbData / sizeof(wchar_t)); + std::wstring newData = data; + size_t pos = 0; + + // Replace VEN_1002 and VEN_8086 with VEN_10DE in REG_SZ + while ((pos = newData.find(L"VEN_1002", pos)) != std::wstring::npos) + { + newData.replace(pos, 8, L"VEN_10DE"); + pos += 8; // Move past the replacement + } + pos = 0; + while ((pos = newData.find(L"VEN_8086", pos)) != std::wstring::npos) + { + newData.replace(pos, 8, L"VEN_10DE"); + pos += 8; // Move past the replacement + } + + LOG_ERROR(L"NEW PAYLOAD: " + newData); + // Copy the new data back + wcscpy_s(reinterpret_cast(lpData), *lpcbData / sizeof(wchar_t), newData.c_str()); + } + } + + return result; +} + +LONG WINAPI hkRegQueryValueExA(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, + LPDWORD lpcbData) +{ + std::string valueName = ""; + + if (lpValueName != NULL) + { + valueName = std::string(lpValueName); + } + + if (valueName == "HwSchMode") + { + // Check if lpcbData is not NULL + if (lpcbData != nullptr) + { + // If lpData is NULL, we're being asked for the required size + if (lpData == nullptr) + { + *lpcbData = sizeof(DWORD); // Indicate the required size + + // Set the type to REG_DWORD + if (lpType) + { + *lpType = REG_DWORD; + } + return ERROR_SUCCESS; + } + + // Check if the buffer is large enough + if (*lpcbData >= sizeof(DWORD)) + { + *(DWORD*) lpData = 2; + + // Set the type to REG_DWORD + if (lpType) + { + *lpType = REG_DWORD; + } + + // Set the size of the data returned + *lpcbData = sizeof(DWORD); + + // Return success + return ERROR_SUCCESS; + } + else + { + // Buffer is too small, return required size + *lpcbData = sizeof(DWORD); + return ERROR_MORE_DATA; + } + } + + // If lpcbData is NULL, return an error + return ERROR_INVALID_PARAMETER; + } + + // Call the original function + auto result = o_RegQueryValueExA(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); + + // Check the result of the query and if the valueName matches + if (result == ERROR_SUCCESS && !State::Instance().isRunningOnNvidia) + { + if (valueName == "DriverVersion") + { + const std::wstring spoofedValue = L"31.0.15.5244"; + size_t spoofedValueSize = + (spoofedValue.size() + 1) * sizeof(wchar_t); // Size in bytes including null terminator + + if (lpData != nullptr && lpcbData != nullptr) + { + // Check if buffer size is sufficient + if (*lpcbData >= spoofedValueSize) + { + // Copy the spoofed value into lpData + std::memcpy(lpData, spoofedValue.c_str(), spoofedValueSize); + // Update lpcbData with the size of the spoofed value + *lpcbData = static_cast(spoofedValueSize); + } + else + { + // If buffer is too small, set lpcbData to the required size + *lpcbData = static_cast(spoofedValueSize); + result = ERROR_MORE_DATA; // Indicate that buffer was too small + } + } + } + + if (valueName == "HardwareID") + { + // Handle REG_SZ type + std::string data(reinterpret_cast(lpData), *lpcbData / sizeof(char)); + std::string newData = data; + size_t pos = 0; + + // Replace VEN_1002 and VEN_8086 with VEN_10DE in REG_SZ + while ((pos = newData.find("VEN_1002", pos)) != std::wstring::npos) + { + newData.replace(pos, 8, "VEN_10DE"); + pos += 8; // Move past the replacement + } + pos = 0; + while ((pos = newData.find("VEN_8086", pos)) != std::wstring::npos) + { + newData.replace(pos, 8, "VEN_10DE"); + pos += 8; // Move past the replacement + } + + // Copy the new data back + std::memcpy(lpData, newData.c_str(), *lpcbData); + } + } + + return result; +} + static void hookAdvapi32() { LOG_FUNC(); @@ -90,6 +328,14 @@ static void hookAdvapi32() o_RegEnumValueW = reinterpret_cast(DetourFindFunction("Advapi32.dll", "RegEnumValueW")); o_RegCloseKey = reinterpret_cast(DetourFindFunction("Advapi32.dll", "RegCloseKey")); + if (Config::Instance()->SpoofRegistry.value_or_default()) + { + o_RegQueryValueExW = + reinterpret_cast(DetourFindFunction("Advapi32.dll", "RegQueryValueExW")); + o_RegQueryValueExA = + reinterpret_cast(DetourFindFunction("Advapi32.dll", "RegQueryValueExA")); + } + DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); @@ -102,6 +348,12 @@ static void hookAdvapi32() if (o_RegCloseKey) DetourAttach(&(PVOID&) o_RegCloseKey, hkRegCloseKey); + if (o_RegQueryValueExW) + DetourAttach(&(PVOID&) o_RegQueryValueExW, hkRegQueryValueExW); + + if (o_RegQueryValueExA) + DetourAttach(&(PVOID&) o_RegQueryValueExA, hkRegQueryValueExA); + DetourTransactionCommit(); } diff --git a/OptiScaler/misc/Quirks.h b/OptiScaler/misc/Quirks.h index 70f127ac..cff2f3a8 100644 --- a/OptiScaler/misc/Quirks.h +++ b/OptiScaler/misc/Quirks.h @@ -37,6 +37,7 @@ enum class GameQuirk : uint64_t SetVelocityValidNow, SetHudlessValidNow, DisableResizeSkip, + SpoofRegistry, // Quirks that are applied deeper in code CyberpunkHudlessFixes, @@ -197,6 +198,10 @@ static const QuirkEntry quirkTable[] = { QUIRK_ENTRY("prey.exe", GameQuirk::DontUseNTShared, GameQuirk::DisableOptiXessPipelineCreation, GameQuirk::DisableDxgiSpoofing), + // Black Myth: Wukong + // To enable DLSS-FG option + QUIRK_ENTRY_UE(b1, GameQuirk::SpoofRegistry), + // Avowed // NoBarriers needed to avoid post-loading crash with DLSS QUIRK_ENTRY_UE(avowed, GameQuirk::ForceAutoExposure, GameQuirk::DontUseUnrealBarriers, GameQuirk::DisableFSR2Inputs,