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-<user>.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.)
This commit is contained in:
Randy Heit 2013-09-14 23:07:59 -05:00
parent da02a44126
commit 0645053431
4 changed files with 202 additions and 49 deletions

View File

@ -646,8 +646,9 @@ void M_ScreenShot (const char *filename)
if (dirlen == 0) if (dirlen == 0)
{ {
autoname = M_GetScreenshotsPath(); autoname = M_GetScreenshotsPath();
dirlen = autoname.Len();
} }
else if (dirlen > 0) if (dirlen > 0)
{ {
if (autoname[dirlen-1] != '/' && autoname[dirlen-1] != '\\') if (autoname[dirlen-1] != '/' && autoname[dirlen-1] != '\\')
{ {

View File

@ -53,7 +53,7 @@ FString M_ZLibError(int zerrnum);
#ifdef __unix__ #ifdef __unix__
FString GetUserFile (const char *path); // Prepends ~/.zdoom to path FString GetUserFile (const char *path); // Prepends ~/.zdoom to path
#endif #endif
FString M_GetCachePath(); FString M_GetCachePath(bool create);
FString M_GetAutoexecPath(); FString M_GetAutoexecPath();
FString M_GetCajunPath(const char *filename); FString M_GetCajunPath(const char *filename);
FString M_GetConfigPath(bool for_reading); FString M_GetConfigPath(bool for_reading);

View File

@ -14,6 +14,114 @@
#if defined(_WIN32) #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 // M_GetCachePath Windows
@ -22,19 +130,14 @@
// //
//=========================================================================== //===========================================================================
FString M_GetCachePath() FString M_GetCachePath(bool create)
{ {
FString path; FString path;
char pathstr[MAX_PATH]; if (!GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, create, path))
if (0 != SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathstr))
{ // Failed (e.g. On Win9x): use program directory { // Failed (e.g. On Win9x): use program directory
path = progdir; path = progdir;
} }
else
{
path = pathstr;
}
// Don't use GAME_DIR and such so that ZDoom and its child ports can // Don't use GAME_DIR and such so that ZDoom and its child ports can
// share the node cache. // share the node cache.
path += "/zdoom/cache"; path += "/zdoom/cache";
@ -89,11 +192,19 @@ FString M_GetConfigPath(bool for_reading)
FString path; FString path;
HRESULT hr; HRESULT hr;
// Construct a user-specific config name
if (UseKnownFolders() && GetKnownFolder(CSIDL_APPDATA, FOLDERID_RoamingAppData, true, path))
{
path += "/zdoom";
CreatePath(path);
path += "/zdoom.ini";
}
else
{ // construct "$PROGDIR/zdoom-$USER.ini"
TCHAR uname[UNLEN+1]; TCHAR uname[UNLEN+1];
DWORD unamelen = countof(uname); DWORD unamelen = countof(uname);
// Because people complained, try for a user-specific .ini in the program directory first. path = progdir;
// If that is not writeable, use the one in the home directory instead.
hr = GetUserName(uname, &unamelen); hr = GetUserName(uname, &unamelen);
if (SUCCEEDED(hr) && uname[0] != 0) if (SUCCEEDED(hr) && uname[0] != 0)
{ {
@ -106,31 +217,25 @@ FString M_GetConfigPath(bool for_reading)
*probe = '_'; *probe = '_';
++probe; ++probe;
} }
path << "zdoom-" << uname << ".ini";
}
else
{ // Couldn't get user name, so just use zdoom.ini
path += "zdoom.ini";
}
}
path = progdir; // If we are reading the config file, check if it exists. If not, fallback
path += "zdoom-"; // to $PROGDIR/zdoom.ini
path += uname;
path += ".ini";
if (for_reading) if (for_reading)
{ {
if (!FileExists(path.GetChars())) if (!FileExists(path))
{ {
path = ""; path = progdir;
} path << "zdoom.ini";
}
else
{ // check if writeable
FILE *checker = fopen (path.GetChars(), "a");
if (checker == NULL)
{
path = "";
}
else
{
fclose (checker);
}
} }
} }
return path; 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 M_GetScreenshotsPath()
{ {
FString path; 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; return path;
} }
@ -160,7 +286,28 @@ FString M_GetScreenshotsPath()
FString M_GetSavegamesPath() FString M_GetSavegamesPath()
{ {
FString path; FString path;
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; path = progdir;
}
return path; return path;
} }
@ -174,14 +321,14 @@ FString M_GetSavegamesPath()
// //
//=========================================================================== //===========================================================================
FString M_GetCachePath() FString M_GetCachePath(bool create)
{ {
FString path; FString path;
char pathstr[PATH_MAX]; char pathstr[PATH_MAX];
FSRef folder; 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)) noErr == FSRefMakePath(&folder, (UInt8*)pathstr, PATH_MAX))
{ {
path = pathstr; 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 // Don't use GAME_DIR and such so that ZDoom and its child ports can
// share the node cache. // share the node cache.
return NicePath("~/.config/zdoom/cache"); FString path = NicePath("~/.config/zdoom/cache");
if (create)
{
CreatePath(create);
}
return path;
} }
//=========================================================================== //===========================================================================

View File

@ -1041,7 +1041,7 @@ typedef TArray<BYTE> MemFile;
static FString CreateCacheName(MapData *map, bool create) static FString CreateCacheName(MapData *map, bool create)
{ {
FString path = M_GetCachePath(); FString path = M_GetCachePath(create);
FString lumpname = Wads.GetLumpFullPath(map->lumpnum); FString lumpname = Wads.GetLumpFullPath(map->lumpnum);
int separator = lumpname.IndexOf(':'); int separator = lumpname.IndexOf(':');
path << '/' << lumpname.Left(separator); path << '/' << lumpname.Left(separator);
@ -1254,7 +1254,7 @@ errorout:
CCMD(clearnodecache) CCMD(clearnodecache)
{ {
TArray<FFileList> list; TArray<FFileList> list;
FString path = M_GetCachePath(); FString path = M_GetCachePath(false);
path += "/"; path += "/";
try try