Skip to content

Commit f661212

Browse files
committed
bugfix(globaldata): Fix the handling of documents folder redirection by using SHGetKnownFolderPath() - Vista+ required
1 parent c8c809c commit f661212

4 files changed

Lines changed: 109 additions & 37 deletions

File tree

Generals/Code/GameEngine/Include/Common/GlobalData.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ class GlobalData : public SubsystemInterface
571571
// just the "leaf name", read from INI. private because no one is ever allowed
572572
// to look at it directly; they must go thru getPath_UserData(). (srj)
573573
AsciiString m_userDataLeafName;
574+
static AsciiString BuildUserDataPathFromIni();
574575

575576
static GlobalData *m_theOriginal; ///< the original global data instance (no overrides)
576577
GlobalData *m_next; ///< next instance (for overrides)

Generals/Code/GameEngine/Source/Common/GlobalData.cpp

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,17 +1172,8 @@ void GlobalData::parseGameDataDefinition( INI* ini )
11721172
ini->initFromINI( TheWritableGlobalData, s_GlobalDataFieldParseTable );
11731173

11741174
TheWritableGlobalData->m_userDataDir.clear();
1175-
1176-
char temp[_MAX_PATH];
1177-
if (::SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true))
1178-
{
1179-
if (temp[strlen(temp)-1] != '\\')
1180-
strcat(temp, "\\");
1181-
strcat(temp, TheWritableGlobalData->m_userDataLeafName.str());
1182-
strcat(temp, "\\");
1183-
CreateDirectory(temp, nullptr);
1184-
TheWritableGlobalData->m_userDataDir = temp;
1185-
}
1175+
TheWritableGlobalData->m_userDataDir = BuildUserDataPathFromIni();
1176+
CreateDirectory(TheWritableGlobalData->m_userDataDir.str(), nullptr);
11861177

11871178
// override INI values with user preferences
11881179
OptionPreferences optionPref;
@@ -1313,3 +1304,50 @@ UnsignedInt GlobalData::generateExeCRC()
13131304

13141305
return exeCRC.get();
13151306
}
1307+
1308+
AsciiString GlobalData::BuildUserDataPathFromIni()
1309+
{
1310+
// VC6 lacks FOLDERID_Documents and KF_FLAG_DEFAULT
1311+
const GUID FOLDERID_Documents = { 0xFDD39AD0, 0x238F, 0x46AF, 0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7 };
1312+
const DWORD KF_FLAG_DEFAULT = 0;
1313+
1314+
typedef HRESULT(WINAPI* PFN_SHGetKnownFolderPath)(const GUID& rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath);
1315+
1316+
AsciiString myDocumentsDirectory;
1317+
HMODULE shell32module = GetModuleHandleA("shell32.dll");
1318+
1319+
// TheSuperHackers @bugfix Mauller 20/03/2026 Fix the handling of folder redirection
1320+
// OneDrive and Group Policy folder redirection is better supported by SHGetKnownFolderPath()
1321+
// SHGetKnownFolderPath() is only supported in windows Vista onwards so we check for it being available
1322+
if (shell32module && GetProcAddress(shell32module, "SHGetKnownFolderPath")) {
1323+
PFN_SHGetKnownFolderPath pSHGetKnownFolderPath = (PFN_SHGetKnownFolderPath)GetProcAddress(shell32module, "SHGetKnownFolderPath");
1324+
1325+
PWSTR pszPath = nullptr;
1326+
HRESULT hr = pSHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, &pszPath);
1327+
1328+
if (SUCCEEDED(hr) && pszPath) {
1329+
myDocumentsDirectory.translate(UnicodeString(pszPath));
1330+
CoTaskMemFree(pszPath);
1331+
}
1332+
}
1333+
else {
1334+
char temp[_MAX_PATH + 1];
1335+
if (SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true)) {
1336+
myDocumentsDirectory = temp;
1337+
}
1338+
}
1339+
1340+
if (myDocumentsDirectory.isEmpty())
1341+
return AsciiString::TheEmptyString;
1342+
1343+
// Now build the full path string
1344+
if (!myDocumentsDirectory.endsWith("\\"))
1345+
myDocumentsDirectory.concat('\\');
1346+
1347+
myDocumentsDirectory.concat(TheWritableGlobalData->m_userDataLeafName.str());
1348+
1349+
if (!myDocumentsDirectory.endsWith("\\"))
1350+
myDocumentsDirectory.concat('\\');
1351+
1352+
return myDocumentsDirectory;
1353+
}

GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ class GlobalData : public SubsystemInterface
576576
// this is private, since we read the info from Windows and cache it for
577577
// future use. No one is allowed to change it, ever. (srj)
578578
AsciiString m_userDataDir;
579+
AsciiString BuildUserDataPathFromRegistry();
579580

580581
static GlobalData *m_theOriginal; ///< the original global data instance (no overrides)
581582
GlobalData *m_next; ///< next instance (for overrides)

GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,32 +1036,10 @@ GlobalData::GlobalData()
10361036

10371037
m_keyboardCameraRotateSpeed = 0.1f;
10381038

1039-
// Set user data directory based on registry settings instead of INI parameters. This allows us to
1040-
// localize the leaf name.
1041-
char temp[_MAX_PATH + 1];
1042-
if (::SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true))
1043-
{
1044-
AsciiString myDocumentsDirectory = temp;
1045-
1046-
if (myDocumentsDirectory.getCharAt(myDocumentsDirectory.getLength() -1) != '\\')
1047-
myDocumentsDirectory.concat( '\\' );
1048-
1049-
AsciiString leafName;
1050-
1051-
if ( !GetStringFromRegistry( "", "UserDataLeafName", leafName ) )
1052-
{
1053-
// Use something, anything
1054-
// [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\" " ) );
1055-
leafName = "Command and Conquer Generals Zero Hour Data";
1056-
}
1057-
1058-
myDocumentsDirectory.concat( leafName );
1059-
if (myDocumentsDirectory.getCharAt( myDocumentsDirectory.getLength() - 1) != '\\')
1060-
myDocumentsDirectory.concat( '\\' );
1061-
1062-
CreateDirectory(myDocumentsDirectory.str(), nullptr);
1063-
m_userDataDir = myDocumentsDirectory;
1064-
}
1039+
// Set user data directory based on registry settings instead of INI parameters.
1040+
// This allows us to localize the leaf name.
1041+
m_userDataDir = BuildUserDataPathFromRegistry();
1042+
CreateDirectory(m_userDataDir.str(), nullptr);
10651043

10661044
//-allAdvice feature
10671045
//m_allAdvice = FALSE;
@@ -1333,3 +1311,57 @@ UnsignedInt GlobalData::generateExeCRC()
13331311

13341312
return exeCRC.get();
13351313
}
1314+
1315+
AsciiString GlobalData::BuildUserDataPathFromRegistry()
1316+
{
1317+
// VC6 lacks FOLDERID_Documents and KF_FLAG_DEFAULT
1318+
const GUID FOLDERID_Documents = { 0xFDD39AD0, 0x238F, 0x46AF, 0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7 };
1319+
const DWORD KF_FLAG_DEFAULT = 0;
1320+
1321+
typedef HRESULT(WINAPI* PFN_SHGetKnownFolderPath)(const GUID& rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath);
1322+
1323+
AsciiString myDocumentsDirectory;
1324+
HMODULE shell32module = GetModuleHandleA("shell32.dll");
1325+
1326+
// TheSuperHackers @bugfix Mauller 20/03/2026 Fix the handling of folder redirection
1327+
// OneDrive and Group Policy folder redirection is better supported by SHGetKnownFolderPath()
1328+
// SHGetKnownFolderPath() is only supported in windows Vista onwards so we check for it being available
1329+
if (shell32module && GetProcAddress(shell32module, "SHGetKnownFolderPath")) {
1330+
PFN_SHGetKnownFolderPath pSHGetKnownFolderPath = (PFN_SHGetKnownFolderPath)GetProcAddress(shell32module, "SHGetKnownFolderPath");
1331+
1332+
PWSTR pszPath = nullptr;
1333+
HRESULT hr = pSHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, &pszPath);
1334+
1335+
if (SUCCEEDED(hr) && pszPath) {
1336+
myDocumentsDirectory.translate(UnicodeString(pszPath));
1337+
CoTaskMemFree(pszPath);
1338+
}
1339+
}
1340+
else {
1341+
char temp[_MAX_PATH + 1];
1342+
if (SHGetSpecialFolderPath(nullptr, temp, CSIDL_PERSONAL, true)) {
1343+
myDocumentsDirectory = temp;
1344+
}
1345+
}
1346+
1347+
if (myDocumentsDirectory.isEmpty())
1348+
return AsciiString::TheEmptyString;
1349+
1350+
// Now build the full path string
1351+
if (!myDocumentsDirectory.endsWith("\\"))
1352+
myDocumentsDirectory.concat('\\');
1353+
1354+
AsciiString leafName;
1355+
if (!GetStringFromRegistry("", "UserDataLeafName", leafName))
1356+
{
1357+
// Use something, anything
1358+
// [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\" " ) );
1359+
leafName = "Command and Conquer Generals Zero Hour Data";
1360+
}
1361+
1362+
myDocumentsDirectory.concat(leafName);
1363+
if (!myDocumentsDirectory.endsWith("\\"))
1364+
myDocumentsDirectory.concat('\\');
1365+
1366+
return myDocumentsDirectory;
1367+
}

0 commit comments

Comments
 (0)