From 0645053431884fafdcc7ede11694058b16d2b3af Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 14 Sep 2013 23:07:59 -0500 Subject: [PATCH] Add support for standard file paths on Windows. - If the current user does not have write permissions for the directory zdoom.exe is located in, use standard folder paths located in their home directory instead. This is a common scenario when people put ZDoom into Program Files. (Ironically, zdoom.ini used to be in AppData, buth then people complained when it wasn't in the same directory as zdoom.exe, so it got turned into zdoom-.ini so at least it could retain some multi-user support. I'm not sure when the AppData support was removed, though, since it should have still been kept around for migrating configs to the new name.) --- src/m_misc.cpp | 3 +- src/m_misc.h | 2 +- src/m_specialpaths.cpp | 242 +++++++++++++++++++++++++++++++++-------- src/p_glnodes.cpp | 4 +- 4 files changed, 202 insertions(+), 49 deletions(-) diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 14b9d9b1b8..c550c4593c 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -646,8 +646,9 @@ void M_ScreenShot (const char *filename) if (dirlen == 0) { autoname = M_GetScreenshotsPath(); + dirlen = autoname.Len(); } - else if (dirlen > 0) + if (dirlen > 0) { if (autoname[dirlen-1] != '/' && autoname[dirlen-1] != '\\') { diff --git a/src/m_misc.h b/src/m_misc.h index 327df3de4f..90844221f5 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -53,7 +53,7 @@ FString M_ZLibError(int zerrnum); #ifdef __unix__ FString GetUserFile (const char *path); // Prepends ~/.zdoom to path #endif -FString M_GetCachePath(); +FString M_GetCachePath(bool create); FString M_GetAutoexecPath(); FString M_GetCajunPath(const char *filename); FString M_GetConfigPath(bool for_reading); diff --git a/src/m_specialpaths.cpp b/src/m_specialpaths.cpp index 6f77551aee..cd116e538a 100644 --- a/src/m_specialpaths.cpp +++ b/src/m_specialpaths.cpp @@ -14,6 +14,114 @@ #if defined(_WIN32) +#include "version.h" // for GAMENAME +typedef HRESULT (*GKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR *); + +//=========================================================================== +// +// IsProgramDirectoryWritable +// +// If the program directory is writable, then dump everything in there for +// historical reasons. Otherwise, known folders get used instead. +// +//=========================================================================== + +bool UseKnownFolders() +{ + // Cache this value so the semantics don't change during a single run + // of the program. (e.g. Somebody could add write access while the + // program is running.) + static INTBOOL iswritable = -1; + FString testpath; + HANDLE file; + + if (iswritable >= 0) + { + return !iswritable; + } + testpath << progdir << "writest"; + file = CreateFile(testpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (file != INVALID_HANDLE_VALUE) + { + CloseHandle(file); + Printf("Using program directory for storage\n"); + iswritable = true; + return false; + } + Printf("Using known folders for storage\n"); + iswritable = false; + return true; +} + +//=========================================================================== +// +// GetKnownFolder +// +// Returns the known_folder if SHGetKnownFolderPath is available, otherwise +// returns the shell_folder using SHGetFolderPath. +// +//=========================================================================== + +bool GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create, FString &path) +{ + static GKFP SHGetKnownFolderPath = NULL; + static bool tested = false; + + if (!tested) + { + tested = true; + HMODULE shell32 = GetModuleHandle("shell32.dll"); + if (shell32 != NULL) + { + SHGetKnownFolderPath = (GKFP)GetProcAddress(shell32, "SHGetKnownFolderPath"); + } + } + + char pathstr[MAX_PATH]; + + // SHGetKnownFolderPath knows about more folders than SHGetFolderPath, but is + // new to Vista, hence the reason we support both. + if (SHGetKnownFolderPath == NULL) + { + if (shell_folder < 0) + { // Not supported by SHGetFolderPath + return false; + } + if (create) + { + shell_folder |= CSIDL_FLAG_CREATE; + } + if (FAILED(SHGetFolderPathA(NULL, shell_folder, NULL, 0, pathstr))) + { + return false; + } + path = pathstr; + return true; + } + else + { + PWSTR wpath; + if (FAILED(SHGetKnownFolderPath(known_folder, create ? KF_FLAG_CREATE : 0, NULL, &wpath))) + { + return false; + } + // FIXME: Support Unicode, at least for filenames. This function + // has no MBCS equivalent, so we have to convert it since we don't + // support Unicode. :( + bool converted = false; + if (WideCharToMultiByte(GetACP(), WC_NO_BEST_FIT_CHARS, wpath, -1, + pathstr, countof(pathstr), NULL, NULL) > 0) + { + path = pathstr; + converted = true; + } + CoTaskMemFree(wpath); + return converted; + } +} + //=========================================================================== // // M_GetCachePath Windows @@ -22,19 +130,14 @@ // //=========================================================================== -FString M_GetCachePath() +FString M_GetCachePath(bool create) { FString path; - char pathstr[MAX_PATH]; - if (0 != SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathstr)) + if (!GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, create, path)) { // Failed (e.g. On Win9x): use program directory path = progdir; } - else - { - path = pathstr; - } // Don't use GAME_DIR and such so that ZDoom and its child ports can // share the node cache. path += "/zdoom/cache"; @@ -89,48 +192,50 @@ FString M_GetConfigPath(bool for_reading) FString path; HRESULT hr; - TCHAR uname[UNLEN+1]; - DWORD unamelen = countof(uname); - - // Because people complained, try for a user-specific .ini in the program directory first. - // If that is not writeable, use the one in the home directory instead. - hr = GetUserName(uname, &unamelen); - if (SUCCEEDED(hr) && uname[0] != 0) + // Construct a user-specific config name + if (UseKnownFolders() && GetKnownFolder(CSIDL_APPDATA, FOLDERID_RoamingAppData, true, path)) { - // Is it valid for a user name to have slashes? - // Check for them and substitute just in case. - char *probe = uname; - while (*probe != 0) - { - if (*probe == '\\' || *probe == '/') - *probe = '_'; - ++probe; - } + path += "/zdoom"; + CreatePath(path); + path += "/zdoom.ini"; + } + else + { // construct "$PROGDIR/zdoom-$USER.ini" + TCHAR uname[UNLEN+1]; + DWORD unamelen = countof(uname); path = progdir; - path += "zdoom-"; - path += uname; - path += ".ini"; - if (for_reading) + hr = GetUserName(uname, &unamelen); + if (SUCCEEDED(hr) && uname[0] != 0) { - if (!FileExists(path.GetChars())) + // Is it valid for a user name to have slashes? + // Check for them and substitute just in case. + char *probe = uname; + while (*probe != 0) { - path = ""; + if (*probe == '\\' || *probe == '/') + *probe = '_'; + ++probe; } + path << "zdoom-" << uname << ".ini"; } else - { // check if writeable - FILE *checker = fopen (path.GetChars(), "a"); - if (checker == NULL) - { - path = ""; - } - else - { - fclose (checker); - } + { // Couldn't get user name, so just use zdoom.ini + path += "zdoom.ini"; } } + + // If we are reading the config file, check if it exists. If not, fallback + // to $PROGDIR/zdoom.ini + if (for_reading) + { + if (!FileExists(path)) + { + path = progdir; + path << "zdoom.ini"; + } + } + return path; } @@ -142,10 +247,31 @@ FString M_GetConfigPath(bool for_reading) // //=========================================================================== +// I'm not sure when FOLDERID_Screenshots was added, but it was probably +// for Windows 8, since it's not in the v7.0 Windows SDK. +static const GUID MyFOLDERID_Screenshots = { 0xb7bede81, 0xdf94, 0x4682, 0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f }; + FString M_GetScreenshotsPath() { FString path; - path = progdir; + + if (!UseKnownFolders()) + { + return progdir; + } + else if (GetKnownFolder(-1, MyFOLDERID_Screenshots, true, path)) + { + path << "/" GAMENAME; + } + else if (GetKnownFolder(CSIDL_MYPICTURES, FOLDERID_Pictures, true, path)) + { + path << "/Screenshots/" GAMENAME; + } + else + { + return progdir; + } + CreatePath(path); return path; } @@ -160,7 +286,28 @@ FString M_GetScreenshotsPath() FString M_GetSavegamesPath() { FString path; - path = progdir; + + if (!UseKnownFolders()) + { + return progdir; + } + // Try standard Saved Games folder + else if (GetKnownFolder(-1, FOLDERID_SavedGames, true, path)) + { + path << "/" GAMENAME; + } + // Try defacto My Documents/My Games folder + else if (GetKnownFolder(CSIDL_PERSONAL, FOLDERID_Documents, true, path)) + { + // I assume since this isn't a standard folder, it doesn't have + // a localized name either. + path << "/My Games/" GAMENAME; + CreatePath(path); + } + else + { + path = progdir; + } return path; } @@ -174,14 +321,14 @@ FString M_GetSavegamesPath() // //=========================================================================== -FString M_GetCachePath() +FString M_GetCachePath(bool create) { FString path; char pathstr[PATH_MAX]; FSRef folder; - if (noErr == FSFindFolder(kLocalDomain, kApplicationSupportFolderType, kCreateFolder, &folder) && + if (noErr == FSFindFolder(kLocalDomain, kApplicationSupportFolderType, create ? kCreateFolder : 0, &folder) && noErr == FSRefMakePath(&folder, (UInt8*)pathstr, PATH_MAX)) { path = pathstr; @@ -409,11 +556,16 @@ FString GetUserFile (const char *file) // //=========================================================================== -FString M_GetCachePath() +FString M_GetCachePath(bool create) { // Don't use GAME_DIR and such so that ZDoom and its child ports can // share the node cache. - return NicePath("~/.config/zdoom/cache"); + FString path = NicePath("~/.config/zdoom/cache"); + if (create) + { + CreatePath(create); + } + return path; } //=========================================================================== diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp index 7dde9c80f8..b07d50aedd 100644 --- a/src/p_glnodes.cpp +++ b/src/p_glnodes.cpp @@ -1041,7 +1041,7 @@ typedef TArray MemFile; static FString CreateCacheName(MapData *map, bool create) { - FString path = M_GetCachePath(); + FString path = M_GetCachePath(create); FString lumpname = Wads.GetLumpFullPath(map->lumpnum); int separator = lumpname.IndexOf(':'); path << '/' << lumpname.Left(separator); @@ -1254,7 +1254,7 @@ errorout: CCMD(clearnodecache) { TArray list; - FString path = M_GetCachePath(); + FString path = M_GetCachePath(false); path += "/"; try