Skip to content

Commit 9c02e28

Browse files
committed
Added initial implementation of automagic "Remember this is a Game" functionality to make using Game Bar to screw with that setting obsolete
1 parent ebce563 commit 9c02e28

4 files changed

Lines changed: 342 additions & 5 deletions

File tree

CHANGELOG.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
26.6.10.2
1+
26.6.11
2+
=======
3+
+ Added initial implementation of automagic "Remember this is a Game"
4+
functionality to make using Game Bar to screw with that setting obsolete.
5+
6+
* Currently for any game launched with Special K, if it is not in Game Bar's
7+
game database (HKEY_CURRENT_USER\System\GameConfigStore\), it will be added.
8+
9+
Non-automatic behavior may be added in the future.
10+
11+
26.6.10.2
212
=========
313
+ Change Forza Horizon 5/6 DLSS warning to only display if the NVIDIA driver has
414
loaded sl.interposer.dll; should make things simpler for AMD users :)

include/SpecialK/DLL_VERSION.H

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
#define SK_YEAR 26
44
#define SK_MONTH 6
5-
#define SK_DATE 10
6-
#define SK_REV_N 2
7-
#define SK_REV 2
5+
#define SK_DATE 11
6+
#define SK_REV_N 0
7+
#define SK_REV 0
88

99
#ifndef _A2
1010
#define _A2(a) #a

src/config.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3926,7 +3926,7 @@ auto DeclKeybind =
39263926
SK_MessageBox (
39273927
L"Special K has Frame Gen Compatibility Issues in this Game\r\n\r\n"
39283928
L" * Please use Local Injection for best results\r\n\r\n"
3929-
L"Click OK to switch to Local Injection.", L"Special K Incompatibility",
3929+
L"Click OK to switch to Local Injection (game restart required).", L"Special K Incompatibility",
39303930
MB_OKCANCEL|MB_ICONWARNING))
39313931
{
39323932
SK_File_FullCopy (

src/core.cpp

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3403,6 +3403,182 @@ SK_ShutdownCore (const wchar_t* backend)
34033403
return true;
34043404
}
34053405

3406+
std::vector <std::wstring>
3407+
SK_GameConfigStore_GetAllChildKeys (CRegKey& parent_key)
3408+
{
3409+
std::vector <std::wstring> sub_keys;
3410+
3411+
DWORD idx = 0;
3412+
wchar_t wszKeyName [MAX_PATH];
3413+
DWORD capacity = _countof (wszKeyName);
3414+
3415+
while (parent_key.EnumKey (idx++, wszKeyName, &capacity) == ERROR_SUCCESS)
3416+
{
3417+
sub_keys.push_back (wszKeyName);
3418+
capacity = _countof (wszKeyName);
3419+
}
3420+
3421+
return sub_keys;
3422+
}
3423+
3424+
#include <windows.h>
3425+
#include <atlbase.h>
3426+
#include <iostream>
3427+
#include <string>
3428+
#include <vector>
3429+
#include <algorithm>
3430+
#include <wincrypt.h>
3431+
#include <combaseapi.h>
3432+
#include <shlwapi.h>
3433+
3434+
#pragma comment(lib, "crypt32.lib")
3435+
#pragma comment(lib, "shlwapi.lib")
3436+
#pragma comment(lib, "ole32.lib")
3437+
3438+
// Helper to convert lowercase string to SHA1 Hex string
3439+
std::wstring
3440+
GetSha1Utf16Le (const std::wstring& text)
3441+
{
3442+
HCRYPTPROV hProv = 0;
3443+
HCRYPTHASH hHash = 0;
3444+
3445+
std::wstring hexResult = L"";
3446+
3447+
if (CryptAcquireContext (&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
3448+
{
3449+
if (CryptCreateHash (hProv, CALG_SHA1, 0, 0, &hHash))
3450+
{
3451+
// Hash the UTF-16LE bytes directly
3452+
DWORD cbData =
3453+
static_cast <DWORD> (text.length () * sizeof (wchar_t));
3454+
3455+
if (CryptHashData (hHash, reinterpret_cast <const BYTE *>(text.c_str ()), cbData, 0))
3456+
{
3457+
BYTE rgbHash [20] = {};
3458+
DWORD cbHash = sizeof (rgbHash);
3459+
3460+
if (CryptGetHashParam (hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
3461+
{
3462+
wchar_t hex [3];
3463+
3464+
for (DWORD i = 0; i < cbHash; i++)
3465+
{
3466+
swprintf_s (hex, 3, L"%02x", rgbHash [i]);
3467+
3468+
hexResult += hex;
3469+
}
3470+
}
3471+
}
3472+
3473+
CryptDestroyHash (hHash);
3474+
}
3475+
3476+
CryptReleaseContext (hProv, 0);
3477+
}
3478+
3479+
return hexResult;
3480+
}
3481+
3482+
// Helper for DPAPI CryptProtectData using LocalMachine scope
3483+
std::vector<BYTE>
3484+
ProtectLocalMachineUtf16Le (const std::wstring& text)
3485+
{
3486+
DATA_BLOB dataIn;
3487+
DATA_BLOB dataOut;
3488+
3489+
std::vector <BYTE> encryptedBlob;
3490+
3491+
dataIn.pbData = reinterpret_cast <BYTE *>(const_cast <wchar_t *> (text.c_str ()));
3492+
dataIn.cbData = static_cast <DWORD> (text.length () * sizeof (wchar_t));
3493+
3494+
// CRYPTPROTECT_LOCAL_MACHINE matches the PowerShell DataProtectionScope::LocalMachine
3495+
if (CryptProtectData (&dataIn, NULL, NULL, NULL, NULL, CRYPTPROTECT_LOCAL_MACHINE, &dataOut))
3496+
{
3497+
encryptedBlob.assign (dataOut.pbData, dataOut.pbData + dataOut.cbData);
3498+
LocalFree (dataOut.pbData);
3499+
}
3500+
3501+
return encryptedBlob;
3502+
}
3503+
3504+
// Helper to generate an upper/lowercase GUID string via COM API
3505+
std::wstring
3506+
CreateGuidString (void)
3507+
{
3508+
GUID guid;
3509+
std::wstring guidStr = L"";
3510+
3511+
if (SUCCEEDED (CoCreateGuid (&guid)))
3512+
{
3513+
wchar_t wszGuid [40];
3514+
3515+
// StringFromGUID2 includes braces; we can manually format to match PowerShell's GUID string
3516+
swprintf_s (wszGuid, 40, L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
3517+
guid.Data1, guid.Data2, guid.Data3,
3518+
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
3519+
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
3520+
3521+
guidStr = wszGuid;
3522+
}
3523+
3524+
return guidStr;
3525+
}
3526+
3527+
// Scans target key children for an existing absolute path match
3528+
bool
3529+
CheckDuplicate (CRegKey& parentRootKey, const std::wstring& exePath)
3530+
{
3531+
wchar_t child_name [256];
3532+
DWORD capacity = _countof (child_name);
3533+
DWORD idx = 0;
3534+
3535+
while (parentRootKey.EnumKey (idx, child_name, &capacity) == ERROR_SUCCESS)
3536+
{
3537+
CRegKey childKey;
3538+
3539+
if (childKey.Open (parentRootKey, child_name, KEY_READ) == ERROR_SUCCESS)
3540+
{
3541+
wchar_t matched_path [MAX_PATH];
3542+
ULONG chars = _countof (matched_path);
3543+
3544+
if (childKey.QueryStringValue (L"MatchedExeFullPath", matched_path, &chars) == ERROR_SUCCESS)
3545+
{
3546+
if (! _wcsicmp (matched_path, exePath.c_str ()))
3547+
{
3548+
return true;
3549+
}
3550+
}
3551+
}
3552+
3553+
idx++;
3554+
3555+
capacity = _countof (child_name);
3556+
}
3557+
return false;
3558+
}
3559+
3560+
std::wstring
3561+
SK_Win32_ToLowerInvariant (const std::wstring& input)
3562+
{
3563+
if (input.empty ())
3564+
return L"";
3565+
3566+
// Determine the required buffer size
3567+
int size = LCMapStringEx (LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE,
3568+
input.c_str(), -1, nullptr, 0, nullptr, nullptr, 0);
3569+
3570+
std::wstring output (size, 0);
3571+
3572+
// Map the string to lowercase
3573+
LCMapStringEx (LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE,
3574+
input.c_str(), -1, &output[0], size, nullptr, nullptr, 0);
3575+
3576+
// Remove the trailing null terminator that LCMapStringEx includes
3577+
output.resize (size - 1);
3578+
3579+
return output;
3580+
}
3581+
34063582
void
34073583
SK_FrameCallback ( SK_RenderBackend& rb,
34083584
ULONG64 frames_drawn =
@@ -3430,6 +3606,157 @@ SK_FrameCallback ( SK_RenderBackend& rb,
34303606
SK_UTF8ToWideChar (SK_GetFriendlyAppName ()).c_str ()
34313607
);
34323608

3609+
CRegKey hkGameConfigChildRoot;
3610+
if (ERROR_SUCCESS == hkGameConfigChildRoot.Open (HKEY_CURRENT_USER, LR"(System\GameConfigStore\Children)"))
3611+
{
3612+
bool is_a_game = false;
3613+
3614+
auto child_keys =
3615+
SK_GameConfigStore_GetAllChildKeys (hkGameConfigChildRoot);
3616+
3617+
// nb: This is the wrong way to do this, hash the exe name and then look through
3618+
// the multi-string key for children instead.
3619+
for ( auto& child_key : child_keys )
3620+
{
3621+
CRegKey hkChildKey;
3622+
hkChildKey.Open (hkGameConfigChildRoot, child_key.c_str ());
3623+
3624+
ULONG ulMatchedExeFullPathLen = MAX_PATH;
3625+
wchar_t wszMatchedExeFullPath [MAX_PATH] = {};
3626+
3627+
if (hkChildKey.QueryStringValue (L"MatchedExeFullPath",
3628+
wszMatchedExeFullPath,
3629+
&ulMatchedExeFullPathLen) == ERROR_SUCCESS)
3630+
{
3631+
if (StrStrIW (wszMatchedExeFullPath, SK_GetHostApp ()))
3632+
{
3633+
if (config.system.log_level > 0)
3634+
SK_ImGui_Warning (L"This is a game!");
3635+
3636+
is_a_game = true;
3637+
break;
3638+
}
3639+
}
3640+
}
3641+
3642+
if (! is_a_game)
3643+
{
3644+
wchar_t wszTargetExePath [MAX_PATH * 2] = { };
3645+
wchar_t wszExeParentDir [MAX_PATH * 2] = { };
3646+
3647+
HANDLE hFile =
3648+
CreateFileW (SK_GetFullyQualifiedApp (), GENERIC_READ, FILE_SHARE_READ, nullptr,
3649+
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
3650+
3651+
if (hFile != INVALID_HANDLE_VALUE)
3652+
{
3653+
SK_File_GetNameFromHandle (hFile, wszTargetExePath, MAX_PATH);
3654+
CloseHandle (hFile);
3655+
}
3656+
3657+
PathResolve (wszTargetExePath, nullptr, PRF_VERIFYEXISTS | PRF_REQUIREABSOLUTE);
3658+
wszTargetExePath [0] = SK_GetHostPath ()[0]; // Ensure drive letter case matches host path
3659+
3660+
wcsncpy_s (wszExeParentDir, MAX_PATH, wszTargetExePath, _TRUNCATE);
3661+
PathRemoveFileSpec (wszExeParentDir);
3662+
3663+
{
3664+
auto wszHostAppLower =
3665+
SK_Win32_ToLowerInvariant (SK_GetHostApp ());
3666+
3667+
std::vector <BYTE> parentBlob =
3668+
ProtectLocalMachineUtf16Le (wszHostAppLower.c_str ());
3669+
3670+
// Calculate current 64-bit UTC file time
3671+
FILETIME ft = {};
3672+
GetSystemTimeAsFileTime (&ft);
3673+
3674+
ULARGE_INTEGER
3675+
uli = {};
3676+
uli.LowPart = ft.dwLowDateTime;
3677+
uli.HighPart = ft.dwHighDateTime;
3678+
3679+
ULONGLONG lastAccessedTime = uli.QuadPart;
3680+
3681+
CRegKey hkGameConfigParentsRoot;
3682+
hkGameConfigParentsRoot.Open (HKEY_CURRENT_USER, LR"(System\GameConfigStore\Parents)");
3683+
3684+
std::wstring parentKeyName =
3685+
GetSha1Utf16Le (wszHostAppLower.c_str ());
3686+
3687+
auto childGuid = CreateGuidString ();
3688+
auto gameGuid = CreateGuidString ();
3689+
3690+
if (config.system.log_level > 0)
3691+
SK_ImGui_Warning (SK_FormatStringW (L"GUID: %ws", childGuid.c_str ()).c_str ());
3692+
3693+
// Create subkeys inside Children and Parents path trees
3694+
CRegKey parentSubKey, childSubKey;
3695+
if (parentSubKey.Create (hkGameConfigParentsRoot, parentKeyName.c_str (), REG_NONE, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE) == ERROR_SUCCESS &&
3696+
childSubKey.Create (hkGameConfigChildRoot, childGuid.c_str (), REG_NONE, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE) == ERROR_SUCCESS)
3697+
{
3698+
// Write properties to the Child GUID target path
3699+
childSubKey.SetStringValue (L"ExeParentDirectory", wszExeParentDir);
3700+
childSubKey.SetDWORDValue (L"Type", 0x1);
3701+
childSubKey.SetDWORDValue (L"Revision", 0x1);
3702+
childSubKey.SetDWORDValue (L"Flags", 0x11);
3703+
childSubKey.SetBinaryValue (L"Parent", parentBlob.data (),
3704+
static_cast <ULONG> (parentBlob.size ()));
3705+
childSubKey.SetStringValue (L"GameDVR_GameGUID", gameGuid.c_str ());
3706+
childSubKey.SetStringValue (L"MatchedExeFullPath", wszTargetExePath);
3707+
childSubKey.SetQWORDValue (L"LastAccessed", lastAccessedTime);
3708+
3709+
// Update parent's 'Children' MultiString properties
3710+
std::vector <std::wstring> childrenArray;
3711+
ULONG multiSize = 0;
3712+
3713+
// Query existing MultiString buffer sizing requirement
3714+
if (parentSubKey.QueryMultiStringValue (L"Children", NULL, &multiSize) == ERROR_SUCCESS && multiSize > 2)
3715+
{ std::vector <wchar_t> buffer (multiSize);
3716+
if (parentSubKey.QueryMultiStringValue (L"Children", buffer.data (), &multiSize) == ERROR_SUCCESS)
3717+
{
3718+
wchar_t* p = buffer.data ();
3719+
while (*p)
3720+
{
3721+
std::wstring standardStr (p);
3722+
3723+
if (! standardStr.empty () && std::wcsspn (standardStr.c_str (), L" \t\n\r") != standardStr.length ())
3724+
{
3725+
childrenArray.push_back (standardStr);
3726+
}
3727+
3728+
p += standardStr.length () + 1;
3729+
}
3730+
}
3731+
}
3732+
3733+
// Ensure uniqueness and avoid array duplicates before adding
3734+
if (std::find (childrenArray.begin (), childrenArray.end (), childGuid) == childrenArray.end ())
3735+
{
3736+
childrenArray.push_back (childGuid);
3737+
}
3738+
3739+
// Reconstruct MultiString structure bytes block (sequences ending with double null bytes)
3740+
std::vector <wchar_t> multiStringBuffer;
3741+
3742+
for (const auto& str : childrenArray)
3743+
{
3744+
multiStringBuffer.insert (multiStringBuffer.end (),
3745+
str.begin (), str.end ());
3746+
multiStringBuffer.push_back (L'\0');
3747+
}
3748+
3749+
multiStringBuffer.push_back (L'\0'); // Double null terminator
3750+
3751+
// Finalize update inside parent multi-string table
3752+
RegSetValueExW (parentSubKey.m_hKey, L"Children", 0, REG_MULTI_SZ,
3753+
reinterpret_cast <const BYTE *> (multiStringBuffer.data ()),
3754+
static_cast <DWORD> (multiStringBuffer.size () * sizeof (wchar_t)));
3755+
}
3756+
}
3757+
}
3758+
}
3759+
34333760
//void SK_NVAPI_DumpProfileSettings (void);
34343761
// SK_NVAPI_DumpProfileSettings ();
34353762

0 commit comments

Comments
 (0)