bugfix(globaldata): Fix the handling of documents folder redirection by using SHGetKnownFolderPath()#2479
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Include/Common/GlobalData.h | Adds static AsciiString BuildUserDataPathFromIni() private declaration; header copyright and #pragma once guard are correct. |
| Generals/Code/GameEngine/Source/Common/GlobalData.cpp | Replaces inline SHGetSpecialFolderPath call in parseGameDataDefinition with a new static helper that prefers SHGetKnownFolderPath (runtime-loaded) and falls back gracefully; early-return guard on empty path is in place. |
| GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h | Adds AsciiString BuildUserDataPathFromRegistry() as a non-static member, inconsistent with the analogous static declaration in Generals; no other concerns. |
| GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp | Mirrors the Generals refactor for Zero Hour; BuildUserDataPathFromRegistry() is declared non-static despite using no instance state, unlike its Generals counterpart. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["parseGameDataDefinition / GlobalData()"] --> B["BuildUserDataPathFromIni/Registry()"]
B --> C{"GetProcAddress\nSHGetKnownFolderPath"}
C -- "found (Vista+)" --> D["pSHGetKnownFolderPath(FOLDERID_Documents)"]
D -- "SUCCEEDED && pszPath" --> E["translate wide path → AsciiString\nCoTaskMemFree(pszPath)"]
D -- "failed" --> F["myDocumentsDirectory is empty"]
C -- "not found (pre-Vista)" --> G["SHGetSpecialFolderPath(CSIDL_PERSONAL)"]
G -- "success" --> H["myDocumentsDirectory = temp"]
G -- "failed" --> F
E --> I{"isEmpty?"}
H --> I
F --> I
I -- "yes" --> J["return TheEmptyString"]
I -- "no" --> K["append '\\' + leaf name + '\\'"]
K --> L["return full path"]
L --> M["m_userDataDir = result"]
M --> N["CreateDirectory(m_userDataDir.str(), nullptr)"]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
Line: 579
Comment:
**`BuildUserDataPathFromRegistry` should be `static`**
`BuildUserDataPathFromRegistry()` accesses no instance members — it uses only `GetStringFromRegistry()` (a free function) and Win32 APIs. The analogous function in the Generals module is correctly declared `static`:
```cpp
static AsciiString BuildUserDataPathFromIni(); // Generals
```
Declaring this one `static` as well makes the intent explicit (no `this` dependency), keeps the two modules consistent, and prevents accidental future reliance on instance state.
```suggestion
static AsciiString BuildUserDataPathFromRegistry();
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (7): Last reviewed commit: "bugfix(globaldata): Fix the handling of ..." | Re-trigger Greptile
|
Ah i didn't check building with VC6, will work on this further |
0c251b8 to
9aeb428
Compare
|
Updated by making the fix conditoinal so it only works in non VC6 builds |
9aeb428 to
0378670
Compare
|
Cleaned up the implementations by replicating the whole code block |
|
Removed controversial as this fix does not work in VC6 in the end anyway, so i had to make the fix conditional at compilation |
| AsciiString myDocumentsDirectory; | ||
| myDocumentsDirectory.translate(UnicodeString(pszPath)); | ||
|
|
||
| if (myDocumentsDirectory.getCharAt(myDocumentsDirectory.getLength() -1) != '\\') |
There was a problem hiding this comment.
Can hardcoded path separators be an issue for (future) linux support?
There was a problem hiding this comment.
All of this will need altering to support linux in the future, the main functions for getting the documents folder are windows API.
0378670 to
f08392e
Compare
|
This was pain, but it works for building in VC6. |
f08392e to
122c1ad
Compare
|
Fixed accidental variable shadowing in the legacy pathway. |
50162a8 to
f661212
Compare
|
Do Generals and Zero Hour provide the correct data for both methods even though their code only uses one path? If so, can we not unify this code so it tries one path first and falls back to the second path? |
No, zero hour only has the registry entry and generals only has the ini entry. |
…by using SHGetKnownFolderPath() - Vista+ required
f661212 to
edb5c60
Compare
|
Updated based on feedback. |
Okay, so then if you unified the code to try the registry first then fall back to ini or vice versa, the code would cover both games and can be made common? |
Yes but that is beyond the scope of this change. Mauller could do a Unify change before this change however to streamline the behavior. |
| HRESULT hr = pSHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, &pszPath); | ||
|
|
||
| if (SUCCEEDED(hr) && pszPath) { | ||
| myDocumentsDirectory.translate(UnicodeString(pszPath)); |
There was a problem hiding this comment.
Explicit UnicodeString construction should not be necessary.
| } | ||
| } | ||
|
|
||
| if (myDocumentsDirectory.isEmpty()) |
There was a problem hiding this comment.
Can be simplified with
if (!myDocumentsDirectory.isEmpty())
{
...
}
return myDocumentsDirectory;
This PR improves folder redirection handling that can occur due to Group Policy or OneDrive redirecting the users documents folder.
This only works for non retail builds as it required a function not supported under VC6 as it only appeared in Vista onwards.
SHGetKnownFolderPath() has better handling of folder redirection built into it compared to the legacy SHGetSpecialFolderPath()
Generals and zero hour use different ways to obtain the game folder name which is why the code is different between them.
Edit - To support building in VC6 without compilation conditional, SHGetSpecialFolderPath is obtained as a function pointer if it is available.
Defines for the GUID of the documents folder path are also added locally as these do not exist pre Vista.