WIN32: Add Portable Mode

Adds support for a self-contained portable mode in which the
executable's directory is used for application files instead
of directories in the user's profile.

Portable mode is activated by placing a scummvm.ini file in
the executable's directory. The directory must be outside of
the system's Program Files directory to comply with UAC.
This commit is contained in:
sluicebox 2021-11-07 02:34:42 -06:00 committed by Lothar Serra Mari
parent 84872f6b8d
commit 3d6524c9f4
6 changed files with 167 additions and 64 deletions

View file

@ -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 {

View file

@ -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

View file

@ -28,8 +28,10 @@
#define _WIN32_IE 0x400
#endif
#include <shlobj.h>
#include <tchar.h>
#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

View file

@ -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

View file

@ -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

View file

@ -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