diff --git a/backends/platform/sdl/win32/win32.cpp b/backends/platform/sdl/win32/win32.cpp index 14b97d80506..4024c3b95ac 100644 --- a/backends/platform/sdl/win32/win32.cpp +++ b/backends/platform/sdl/win32/win32.cpp @@ -59,6 +59,10 @@ #define DEFAULT_CONFIG_FILE "scummvm.ini" +OSystem_Win32::OSystem_Win32() : + _isPortable(false) { +} + void OSystem_Win32::init() { // Initialize File System Factory _fsFactory = new WindowsFilesystemFactory(); @@ -112,8 +116,8 @@ void OSystem_Win32::initBackend() { } // Create the savefile manager - if (_savefileManager == 0) - _savefileManager = new WindowsSaveFileManager(); + if (_savefileManager == nullptr) + _savefileManager = new WindowsSaveFileManager(_isPortable); #if defined(USE_SPARKLE) // Initialize updates manager @@ -240,16 +244,19 @@ Common::String OSystem_Win32::getScreenshotsPath() { return screenshotsPath; } - // Use the My Pictures folder. TCHAR picturesPath[MAX_PATH]; - - if (SHGetFolderPathFunc(NULL, CSIDL_MYPICTURES, NULL, SHGFP_TYPE_CURRENT, picturesPath) != S_OK) { - warning("Unable to access My Pictures directory"); - return Common::String(); + if (_isPortable) { + Win32::getProcessDirectory(picturesPath, MAX_PATH); + _tcscat(picturesPath, TEXT("\\Screenshots\\")); + } else { + // Use the My Pictures folder + if (SHGetFolderPathFunc(NULL, CSIDL_MYPICTURES, NULL, SHGFP_TYPE_CURRENT, picturesPath) != S_OK) { + warning("Unable to access My Pictures directory"); + return Common::String(); + } + _tcscat(picturesPath, TEXT("\\ScummVM Screenshots\\")); } - _tcscat(picturesPath, TEXT("\\ScummVM Screenshots\\")); - // If the directory already exists (as it should in most cases), // we don't want to fail, but we need to stop on other errors (such as ERROR_PATH_NOT_FOUND) if (!CreateDirectory(picturesPath, NULL)) { @@ -263,41 +270,48 @@ Common::String OSystem_Win32::getScreenshotsPath() { Common::String OSystem_Win32::getDefaultConfigFileName() { TCHAR configFile[MAX_PATH]; - // Use the Application Data directory of the user profile. - if (SHGetFolderPathFunc(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, configFile) == S_OK) { - _tcscat(configFile, TEXT("\\ScummVM")); - if (!CreateDirectory(configFile, NULL)) { - if (GetLastError() != ERROR_ALREADY_EXISTS) - error("Cannot create ScummVM application data folder"); - } + // if this is the first time the default config file name is requested + // then we need detect if we should run in portable mode. (and if it's + // never requested before the backend is initialized then a config file + // was provided on the command line and portable mode doesn't apply.) + if (!backendInitialized()) { + _isPortable = detectPortableConfigFile(); + } + if (_isPortable) { + // Use the current process directory in portable mode + Win32::getProcessDirectory(configFile, MAX_PATH); _tcscat(configFile, TEXT("\\" DEFAULT_CONFIG_FILE)); + } else { + // Use the Application Data directory of the user profile + if (Win32::getApplicationDataDirectory(configFile)) { + _tcscat(configFile, TEXT("\\" DEFAULT_CONFIG_FILE)); - FILE *tmp = NULL; - if ((tmp = _tfopen(configFile, TEXT("r"))) == NULL) { - // Check windows directory - TCHAR oldConfigFile[MAX_PATH]; - uint ret = GetWindowsDirectory(oldConfigFile, MAX_PATH); - if (ret == 0 || ret > MAX_PATH) - error("Cannot retrieve the path of the Windows directory"); + FILE *tmp = NULL; + if ((tmp = _tfopen(configFile, TEXT("r"))) == NULL) { + // Check windows directory + TCHAR oldConfigFile[MAX_PATH]; + uint ret = GetWindowsDirectory(oldConfigFile, MAX_PATH); + if (ret == 0 || ret > MAX_PATH) + error("Cannot retrieve the path of the Windows directory"); - _tcscat(oldConfigFile, TEXT("\\" DEFAULT_CONFIG_FILE)); - if ((tmp = _tfopen(oldConfigFile, TEXT("r")))) { - _tcscpy(configFile, oldConfigFile); + _tcscat(oldConfigFile, TEXT("\\" DEFAULT_CONFIG_FILE)); + if ((tmp = _tfopen(oldConfigFile, TEXT("r")))) { + _tcscpy(configFile, oldConfigFile); + fclose(tmp); + } + } else { fclose(tmp); } } else { - fclose(tmp); - } - } else { - warning("Unable to access application data directory"); - // Check windows directory - uint ret = GetWindowsDirectory(configFile, MAX_PATH); - if (ret == 0 || ret > MAX_PATH) - error("Cannot retrieve the path of the Windows directory"); + // Check windows directory + uint ret = GetWindowsDirectory(configFile, MAX_PATH); + if (ret == 0 || ret > MAX_PATH) + error("Cannot retrieve the path of the Windows directory"); - _tcscat(configFile, TEXT("\\" DEFAULT_CONFIG_FILE)); + _tcscat(configFile, TEXT("\\" DEFAULT_CONFIG_FILE)); + } } return Win32::tcharToString(configFile); @@ -306,21 +320,54 @@ Common::String OSystem_Win32::getDefaultConfigFileName() { Common::String OSystem_Win32::getDefaultLogFileName() { TCHAR logFile[MAX_PATH]; - // Use the Application Data directory of the user profile. - if (SHGetFolderPathFunc(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, logFile) != S_OK) { - warning("Unable to access application data directory"); - return Common::String(); + if (_isPortable) { + Win32::getProcessDirectory(logFile, MAX_PATH); + } else { + // Use the Application Data directory of the user profile + if (!Win32::getApplicationDataDirectory(logFile)) { + return Common::String(); + } + _tcscat(logFile, TEXT("\\Logs")); + CreateDirectory(logFile, NULL); } - _tcscat(logFile, TEXT("\\ScummVM")); - CreateDirectory(logFile, NULL); - _tcscat(logFile, TEXT("\\Logs")); - CreateDirectory(logFile, NULL); _tcscat(logFile, TEXT("\\scummvm.log")); return Win32::tcharToString(logFile); } +bool OSystem_Win32::detectPortableConfigFile() { + // ScummVM operates in a "portable mode" if there is a config file in the + // same directory as the executable. In this mode, the executable's + // directory is used instead of the user's profile for application files. + // This approach is modeled off of the portable mode in Notepad++. + + // Check if there is a config file in the same directory as the executable. + TCHAR portableConfigFile[MAX_PATH]; + Win32::getProcessDirectory(portableConfigFile, MAX_PATH); + _tcscat(portableConfigFile, TEXT("\\" DEFAULT_CONFIG_FILE)); + FILE *file = _tfopen(portableConfigFile, TEXT("r")); + if (file == NULL) { + return false; + } + fclose(file); + + // Check if we're running from Program Files on Vista+. + // If so then don't attempt to use local files due to UAC. + // (Notepad++ does this too.) + if (Win32::confirmWindowsVersion(6, 0)) { + TCHAR programFiles[MAX_PATH]; + if (SHGetFolderPathFunc(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, programFiles) == S_OK) { + _tcscat(portableConfigFile, TEXT("\\")); + if (_tcsstr(portableConfigFile, programFiles) == portableConfigFile) { + return false; + } + } + } + + return true; +} + namespace { class Win32ResourceArchive final : public Common::Archive { diff --git a/backends/platform/sdl/win32/win32.h b/backends/platform/sdl/win32/win32.h index 126267c385e..c26d285da76 100644 --- a/backends/platform/sdl/win32/win32.h +++ b/backends/platform/sdl/win32/win32.h @@ -28,6 +28,8 @@ class OSystem_Win32 final : public OSystem_SDL { public: + OSystem_Win32(); + virtual void init() override; virtual void initBackend() override; @@ -58,6 +60,10 @@ protected: virtual AudioCDManager *createAudioCDManager() override; HWND getHwnd() { return ((SdlWindow_Win32*)_window)->getHwnd(); } + +private: + bool _isPortable; + bool detectPortableConfigFile(); }; #endif diff --git a/backends/platform/sdl/win32/win32_wrapper.cpp b/backends/platform/sdl/win32/win32_wrapper.cpp index 00ce8fe75cd..e38efe1096c 100644 --- a/backends/platform/sdl/win32/win32_wrapper.cpp +++ b/backends/platform/sdl/win32/win32_wrapper.cpp @@ -28,8 +28,10 @@ #define _WIN32_IE 0x400 #endif #include +#include #include "common/scummsys.h" +#include "common/textconsole.h" #include "backends/platform/sdl/win32/win32_wrapper.h" // VerSetConditionMask, VerifyVersionInfo and SHGetFolderPath didn't appear until Windows 2000, @@ -72,6 +74,33 @@ HRESULT SHGetFolderPathFunc(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, namespace Win32 { +bool getApplicationDataDirectory(TCHAR *applicationDataDirectory) { + if (SHGetFolderPathFunc(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, applicationDataDirectory) != S_OK) { + warning("Unable to access application data directory"); + return false; + } + + _tcscat(applicationDataDirectory, TEXT("\\ScummVM")); + if (!CreateDirectory(applicationDataDirectory, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) { + error("Cannot create ScummVM application data folder"); + } + } + + return true; +} + +void getProcessDirectory(TCHAR *processDirectory, DWORD size) { + GetModuleFileName(NULL, processDirectory, size); + processDirectory[size - 1] = '\0'; // termination not guaranteed + + // remove executable and final path separator + TCHAR *lastSeparator = _tcsrchr(processDirectory, '\\'); + if (lastSeparator != NULL) { + *lastSeparator = '\0'; + } +} + bool confirmWindowsVersion(int majorVersion, int minorVersion) { OSVERSIONINFOEXA versionInfo; DWORDLONG conditionMask = 0; @@ -173,4 +202,4 @@ void freeArgvUtf8(int argc, char **argv) { } #endif -} +} // End of namespace Win32 diff --git a/backends/platform/sdl/win32/win32_wrapper.h b/backends/platform/sdl/win32/win32_wrapper.h index 8b6411b155b..97a57b73f85 100644 --- a/backends/platform/sdl/win32/win32_wrapper.h +++ b/backends/platform/sdl/win32/win32_wrapper.h @@ -31,6 +31,29 @@ HRESULT SHGetFolderPathFunc(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, // Helper functions namespace Win32 { +/** + * Gets the full path to the ScummVM application data directory + * in the user's profile and creates it if it doesn't exist. + * + * @param profileDirectory MAX_PATH sized output array + * + * @return True if the user's profile directory was found, false if + * it was not. + * + * @note if the user's profile directory is found but the "ScummVM" + * subdirectory can't be created then this function calls error(). + */ +bool getApplicationDataDirectory(TCHAR *profileDirectory); + +/** + * Gets the full path to the directory that the currently executing + * process resides in. + * + * @param processDirectory output array + * @param size size in characters of output array + */ +void getProcessDirectory(TCHAR *processDirectory, DWORD size); + /** * Checks if the current running Windows version is greater or equal to the specified version. * See: https://docs.microsoft.com/en-us/windows/desktop/sysinfo/operating-system-version @@ -115,6 +138,6 @@ char **getArgvUtf8(int *argc); void freeArgvUtf8(int argc, char **argv); #endif -} +} // End of namespace Win32 #endif diff --git a/backends/saves/windows/windows-saves.cpp b/backends/saves/windows/windows-saves.cpp index 4488f1db6f8..53aa12efc82 100644 --- a/backends/saves/windows/windows-saves.cpp +++ b/backends/saves/windows/windows-saves.cpp @@ -36,27 +36,25 @@ #include "backends/saves/windows/windows-saves.h" #include "backends/platform/sdl/win32/win32_wrapper.h" -WindowsSaveFileManager::WindowsSaveFileManager() { +WindowsSaveFileManager::WindowsSaveFileManager(bool isPortable) { TCHAR defaultSavepath[MAX_PATH]; - // Use the Application Data directory of the user profile. - if (SHGetFolderPathFunc(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, defaultSavepath) == S_OK) { - _tcscat(defaultSavepath, TEXT("\\ScummVM")); - if (!CreateDirectory(defaultSavepath, NULL)) { - if (GetLastError() != ERROR_ALREADY_EXISTS) - error("Cannot create ScummVM application data folder"); - } - - _tcscat(defaultSavepath, TEXT("\\Saved games")); - if (!CreateDirectory(defaultSavepath, NULL)) { - if (GetLastError() != ERROR_ALREADY_EXISTS) - error("Cannot create ScummVM Saved games folder"); - } - - ConfMan.registerDefault("savepath", Win32::tcharToString(defaultSavepath)); + if (isPortable) { + Win32::getProcessDirectory(defaultSavepath, MAX_PATH); } else { - warning("Unable to access application data directory"); + // Use the Application Data directory of the user profile + if (!Win32::getApplicationDataDirectory(defaultSavepath)) { + return; + } } + + _tcscat(defaultSavepath, TEXT("\\Saved games")); + if (!CreateDirectory(defaultSavepath, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + error("Cannot create ScummVM Saved games folder"); + } + + ConfMan.registerDefault("savepath", Win32::tcharToString(defaultSavepath)); } #endif diff --git a/backends/saves/windows/windows-saves.h b/backends/saves/windows/windows-saves.h index 66b5b034a8b..8b8315d9ba8 100644 --- a/backends/saves/windows/windows-saves.h +++ b/backends/saves/windows/windows-saves.h @@ -26,11 +26,11 @@ #include "backends/saves/default/default-saves.h" /** - * Provides a default savefile manager implementation for common platforms. + * Provides a savefile manager implementation for Windows. */ class WindowsSaveFileManager final : public DefaultSaveFileManager { public: - WindowsSaveFileManager(); + WindowsSaveFileManager(bool isPortable); }; #endif