Skip to content

Commit 8cf9456

Browse files
authored
bugfix(globaldata): Fix the handling of documents folder redirection by using SHGetKnownFolderPath() (#2479)
The runtime requires Windows Vista or higher
1 parent 6e4e5f0 commit 8cf9456

4 files changed

Lines changed: 117 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: 53 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,54 @@ UnsignedInt GlobalData::generateExeCRC()
13131304

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

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: 62 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,61 @@ UnsignedInt GlobalData::generateExeCRC()
13331311

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

0 commit comments

Comments
 (0)