diff --git a/Generals/Code/GameEngine/Include/Common/GlobalData.h b/Generals/Code/GameEngine/Include/Common/GlobalData.h index 6c86e5c872a..ffe8a519dc2 100644 --- a/Generals/Code/GameEngine/Include/Common/GlobalData.h +++ b/Generals/Code/GameEngine/Include/Common/GlobalData.h @@ -571,6 +571,7 @@ class GlobalData : public SubsystemInterface // just the "leaf name", read from INI. private because no one is ever allowed // to look at it directly; they must go thru getPath_UserData(). (srj) AsciiString m_userDataLeafName; + static AsciiString BuildUserDataPathFromIni(); static GlobalData *m_theOriginal; ///< the original global data instance (no overrides) GlobalData *m_next; ///< next instance (for overrides) diff --git a/Generals/Code/GameEngine/Source/Common/GlobalData.cpp b/Generals/Code/GameEngine/Source/Common/GlobalData.cpp index a3cf9c0dac0..a2b17958a2d 100644 --- a/Generals/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/Generals/Code/GameEngine/Source/Common/GlobalData.cpp @@ -1172,17 +1172,8 @@ void GlobalData::parseGameDataDefinition( INI* ini ) ini->initFromINI( TheWritableGlobalData, s_GlobalDataFieldParseTable ); TheWritableGlobalData->m_userDataDir.clear(); - - char temp[_MAX_PATH]; - if (::SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true)) - { - if (temp[strlen(temp)-1] != '\\') - strcat(temp, "\\"); - strcat(temp, TheWritableGlobalData->m_userDataLeafName.str()); - strcat(temp, "\\"); - CreateDirectory(temp, nullptr); - TheWritableGlobalData->m_userDataDir = temp; - } + TheWritableGlobalData->m_userDataDir = BuildUserDataPathFromIni(); + CreateDirectory(TheWritableGlobalData->m_userDataDir.str(), nullptr); // override INI values with user preferences OptionPreferences optionPref; @@ -1313,3 +1304,55 @@ UnsignedInt GlobalData::generateExeCRC() return exeCRC.get(); } + +AsciiString GlobalData::BuildUserDataPathFromIni() +{ +#if defined(_MSC_VER) && (_MSC_VER < 1300) + // VC6 lacks FOLDERID_Documents and KF_FLAG_DEFAULT + const GUID FOLDERID_Documents = { 0xFDD39AD0, 0x238F, 0x46AF, 0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7 }; + const DWORD KF_FLAG_DEFAULT = 0; +#endif + + typedef HRESULT(WINAPI* PFN_SHGetKnownFolderPath)(const GUID& rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath); + + AsciiString myDocumentsDirectory; + HMODULE shell32module = GetModuleHandleA("shell32.dll"); + PFN_SHGetKnownFolderPath pSHGetKnownFolderPath = nullptr; + + // TheSuperHackers @bugfix Mauller 20/03/2026 Fix the handling of folder redirection + // OneDrive and Group Policy folder redirection is better supported by SHGetKnownFolderPath() + // SHGetKnownFolderPath() is only supported in windows Vista onwards so we check for it being available + if (shell32module) { + pSHGetKnownFolderPath = (PFN_SHGetKnownFolderPath)GetProcAddress(shell32module, "SHGetKnownFolderPath"); + } + + if (pSHGetKnownFolderPath) { + PWSTR pszPath = nullptr; + HRESULT hr = pSHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, &pszPath); + + if (SUCCEEDED(hr) && pszPath) { + myDocumentsDirectory.translate(UnicodeString(pszPath)); + CoTaskMemFree(pszPath); + } + } + else { + char temp[_MAX_PATH + 1]; + if (SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true)) { + myDocumentsDirectory = temp; + } + } + + if (myDocumentsDirectory.isEmpty()) + return AsciiString::TheEmptyString; + + // Now build the full path string + if (!myDocumentsDirectory.endsWith("\\")) + myDocumentsDirectory.concat('\\'); + + myDocumentsDirectory.concat(TheWritableGlobalData->m_userDataLeafName.str()); + + if (!myDocumentsDirectory.endsWith("\\")) + myDocumentsDirectory.concat('\\'); + + return myDocumentsDirectory; +} diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 8fcde36e5d5..4f25d868f05 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -576,6 +576,7 @@ class GlobalData : public SubsystemInterface // this is private, since we read the info from Windows and cache it for // future use. No one is allowed to change it, ever. (srj) AsciiString m_userDataDir; + AsciiString BuildUserDataPathFromRegistry(); static GlobalData *m_theOriginal; ///< the original global data instance (no overrides) GlobalData *m_next; ///< next instance (for overrides) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 8527e8a70cd..502517aa329 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -1036,32 +1036,10 @@ GlobalData::GlobalData() m_keyboardCameraRotateSpeed = 0.1f; - // Set user data directory based on registry settings instead of INI parameters. This allows us to - // localize the leaf name. - char temp[_MAX_PATH + 1]; - if (::SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true)) - { - AsciiString myDocumentsDirectory = temp; - - if (myDocumentsDirectory.getCharAt(myDocumentsDirectory.getLength() -1) != '\\') - myDocumentsDirectory.concat( '\\' ); - - AsciiString leafName; - - if ( !GetStringFromRegistry( "", "UserDataLeafName", leafName ) ) - { - // Use something, anything - // [MH] had to remove this, otherwise mapcache build step won't run... DEBUG_CRASH( ( "Could not find registry key UserDataLeafName; defaulting to \"Command and Conquer Generals Zero Hour Data\" " ) ); - leafName = "Command and Conquer Generals Zero Hour Data"; - } - - myDocumentsDirectory.concat( leafName ); - if (myDocumentsDirectory.getCharAt( myDocumentsDirectory.getLength() - 1) != '\\') - myDocumentsDirectory.concat( '\\' ); - - CreateDirectory(myDocumentsDirectory.str(), nullptr); - m_userDataDir = myDocumentsDirectory; - } + // Set user data directory based on registry settings instead of INI parameters. + // This allows us to localize the leaf name. + m_userDataDir = BuildUserDataPathFromRegistry(); + CreateDirectory(m_userDataDir.str(), nullptr); //-allAdvice feature //m_allAdvice = FALSE; @@ -1333,3 +1311,62 @@ UnsignedInt GlobalData::generateExeCRC() return exeCRC.get(); } + +AsciiString GlobalData::BuildUserDataPathFromRegistry() +{ +#if defined(_MSC_VER) && (_MSC_VER < 1300) + // VC6 lacks FOLDERID_Documents and KF_FLAG_DEFAULT + const GUID FOLDERID_Documents = { 0xFDD39AD0, 0x238F, 0x46AF, 0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7 }; + const DWORD KF_FLAG_DEFAULT = 0; +#endif + + typedef HRESULT(WINAPI* PFN_SHGetKnownFolderPath)(const GUID& rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath); + + AsciiString myDocumentsDirectory; + HMODULE shell32module = GetModuleHandleA("shell32.dll"); + PFN_SHGetKnownFolderPath pSHGetKnownFolderPath = nullptr; + + // TheSuperHackers @bugfix Mauller 20/03/2026 Fix the handling of folder redirection + // OneDrive and Group Policy folder redirection is better supported by SHGetKnownFolderPath() + // SHGetKnownFolderPath() is only supported in windows Vista onwards so we check for it being available + if (shell32module) { + pSHGetKnownFolderPath = (PFN_SHGetKnownFolderPath)GetProcAddress(shell32module, "SHGetKnownFolderPath"); + } + + if (pSHGetKnownFolderPath) { + PWSTR pszPath = nullptr; + HRESULT hr = pSHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, &pszPath); + + if (SUCCEEDED(hr) && pszPath) { + myDocumentsDirectory.translate(UnicodeString(pszPath)); + CoTaskMemFree(pszPath); + } + } + else { + char temp[_MAX_PATH + 1]; + if (SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true)) { + myDocumentsDirectory = temp; + } + } + + if (myDocumentsDirectory.isEmpty()) + return AsciiString::TheEmptyString; + + // Now build the full path string + if (!myDocumentsDirectory.endsWith("\\")) + myDocumentsDirectory.concat('\\'); + + AsciiString leafName; + if (!GetStringFromRegistry("", "UserDataLeafName", leafName)) + { + // Use something, anything + // [MH] had to remove this, otherwise mapcache build step won't run... DEBUG_CRASH( ( "Could not find registry key UserDataLeafName; defaulting to \"Command and Conquer Generals Zero Hour Data\" " ) ); + leafName = "Command and Conquer Generals Zero Hour Data"; + } + + myDocumentsDirectory.concat(leafName); + if (!myDocumentsDirectory.endsWith("\\")) + myDocumentsDirectory.concat('\\'); + + return myDocumentsDirectory; +}