From e177be305b93f7312dbd56f4b254e18393053511 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Tue, 3 Nov 2015 22:20:35 +0000 Subject: [PATCH 1/3] allow user data path to be specified by command line, closes #2182 Also changed default path under linux to be ~/.config/OpenRCT2. --- projects/openrct2.vcxproj | 1 + projects/openrct2.vcxproj.filters | 3 ++ src/cmdline.c | 6 ++++ src/openrct2.c | 2 ++ src/openrct2.h | 5 ++-- src/platform/linux.c | 41 ++++++++++++++++++++++++-- src/platform/platform.h | 1 + src/platform/windows.c | 48 ++++++++++++++++++++++++------- src/util/util.c | 3 ++ 9 files changed, 94 insertions(+), 16 deletions(-) diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index dd28db7755..8a16578e6f 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -82,6 +82,7 @@ + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index 5707164ac7..c08b462c63 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -543,6 +543,9 @@ Source\Ride + + Source\Platform + diff --git a/src/cmdline.c b/src/cmdline.c index 586d343f48..49f8c4adaa 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -73,6 +73,7 @@ int cmdline_run(const char **argv, int argc) // int version = 0, headless = 0, verbose = 0, width = 0, height = 0, port = 0; char *server = NULL; + char *userDataPath = NULL; argparse_option_t options[] = { OPT_HELP(), @@ -82,6 +83,7 @@ int cmdline_run(const char **argv, int argc) OPT_INTEGER('m', "mode", &sprite_mode, "the type of sprite conversion. 0 = default, 1 = simple closest pixel match, 2 = dithering"), OPT_STRING(0, "server", &server, "server to connect to"), OPT_INTEGER(0, "port", &port, "port"), + OPT_STRING(0, "user-data-path", &userDataPath, "path to the user data directory (containing config.ini)"), OPT_END() }; @@ -100,6 +102,10 @@ int cmdline_run(const char **argv, int argc) if (verbose) _log_levels[DIAGNOSTIC_LEVEL_VERBOSE] = 1; + if (userDataPath != NULL) { + safe_strncpy(gCustomUserDataPath, userDataPath, sizeof(gCustomUserDataPath)); + } + #ifndef DISABLE_NETWORK if (port != 0) { gNetworkStart = NETWORK_MODE_SERVER; diff --git a/src/openrct2.c b/src/openrct2.c index b1ccd332f2..d19fe73ecb 100644 --- a/src/openrct2.c +++ b/src/openrct2.c @@ -52,6 +52,7 @@ int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE; utf8 gOpenRCT2StartupActionPath[512] = { 0 }; utf8 gExePath[MAX_PATH]; +utf8 gCustomUserDataPath[MAX_PATH] = { 0 }; // This should probably be changed later and allow a custom selection of things to initialise like SDL_INIT bool gOpenRCT2Headless = false; @@ -187,6 +188,7 @@ bool openrct2_initialise() { utf8 userPath[MAX_PATH]; + platform_resolve_user_data_path(); platform_get_user_directory(userPath, NULL); if (!platform_ensure_directory_exists(userPath)) { log_fatal("Could not create user directory (do you have write access to your documents folder?)"); diff --git a/src/openrct2.h b/src/openrct2.h index 2d8ac7daf5..73fe12f0e8 100644 --- a/src/openrct2.h +++ b/src/openrct2.h @@ -31,8 +31,9 @@ enum { }; extern int gOpenRCT2StartupAction; -extern char gOpenRCT2StartupActionPath[512]; -extern char gExePath[MAX_PATH]; +extern utf8 gOpenRCT2StartupActionPath[512]; +extern utf8 gExePath[MAX_PATH]; +extern utf8 gCustomUserDataPath[MAX_PATH]; extern bool gOpenRCT2Headless; extern bool gOpenRCT2ShowChangelog; diff --git a/src/platform/linux.c b/src/platform/linux.c index 4c76595791..392819801b 100644 --- a/src/platform/linux.c +++ b/src/platform/linux.c @@ -39,6 +39,8 @@ // The name of the mutex used to prevent multiple instances of the game from running #define SINGLE_INSTANCE_MUTEX_NAME "RollerCoaster Tycoon 2_GSKMUTEX" +utf8 _userDataDirectoryPath[MAX_PATH] = { 0 }; + /** * The function that is called directly from the host application (rct2.exe)'s WinMain. This will be removed when OpenRCT2 can * be built as a stand alone application. @@ -554,8 +556,25 @@ wchar_t *regular_to_wchar(const char* src) return w_buffer; } -void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) +/** + * Default directory fallback is: + * - (command line argument) + * - $XDG_CONFIG_HOME/.config/OpenRCT2 + * - /home/[uid]/.config/OpenRCT2 + */ +void platform_resolve_user_data_path() { + if (gCustomUserDataPath[0] != 0) { + safe_strncpy(_userDataDirectoryPath, gCustomUserDataPath, sizeof(_userDataDirectoryPath)); + + // Ensure path ends with separator + int len = strlen(_userDataDirectoryPath); + if (_userDataDirectoryPath[len - 1] != separator[0]) { + strcat(_userDataDirectoryPath, separator); + } + return; + } + char buffer[MAX_PATH]; buffer[0] = '\0'; log_verbose("buffer = '%s'", buffer); @@ -567,16 +586,32 @@ void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) log_verbose("homedir was null, used getuid, now is = '%s'", homedir); if (homedir == NULL) { - log_error("Couldn't find user home directory"); + log_fatal("Couldn't find user data directory"); + exit(-1); return; } } char separator[2] = { platform_get_path_separator(), 0 }; strncat(buffer, homedir, MAX_PATH); strncat(buffer, separator, MAX_PATH); + strncat(buffer, ".config", MAX_PATH); + strncat(buffer, separator, MAX_PATH); strncat(buffer, "OpenRCT2", MAX_PATH); strncat(buffer, separator, MAX_PATH); - log_verbose("outPath + OpenRCT2 = '%s'", buffer); + log_verbose("OpenRCT2 user data directory = '%s'", buffer); + int len = strnlen(buffer, MAX_PATH); + wchar_t *w_buffer = regular_to_wchar(buffer); + w_buffer[len] = '\0'; + utf8 *path = widechar_to_utf8(w_buffer); + free(w_buffer); + safe_strncpy(_userDataDirectoryPath, path, MAX_PATH); + free(path); +} + +void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) +{ + char buffer[MAX_PATH]; + safe_strncpy(buffer, _userDataDirectoryPath, sizeof(buffer)); if (subDirectory != NULL && subDirectory[0] != 0) { log_verbose("adding subDirectory '%s'", subDirectory); strcat(buffer, subDirectory); diff --git a/src/platform/platform.h b/src/platform/platform.h index 75e568da43..fcd94a06b9 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -138,6 +138,7 @@ void platform_show_cursor(); void platform_get_cursor_position(int *x, int *y); void platform_set_cursor_position(int x, int y); unsigned int platform_get_ticks(); +void platform_resolve_user_data_path(); void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory); void platform_show_messagebox(utf8 *message); int platform_open_common_file_dialog(int type, utf8 *title, utf8 *filename, utf8 *filterPattern, utf8 *filterName); diff --git a/src/platform/windows.c b/src/platform/windows.c index c3c4434660..6e60ebed66 100644 --- a/src/platform/windows.c +++ b/src/platform/windows.c @@ -37,6 +37,8 @@ // The name of the mutex used to prevent multiple instances of the game from running #define SINGLE_INSTANCE_MUTEX_NAME "RollerCoaster Tycoon 2_GSKMUTEX" +utf8 _userDataDirectoryPath[MAX_PATH] = { 0 }; + LPSTR *CommandLineToArgvA(LPSTR lpCmdLine, int *argc); /** @@ -380,25 +382,49 @@ bool platform_file_delete(const utf8 *path) return success == TRUE; } -void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) +/** + * Default directory fallback is: + * - (command line argument) + * - C:\Users\%USERNAME%\OpenRCT2 (as from SHGetFolderPathW) + */ +void platform_resolve_user_data_path() { wchar_t wOutPath[MAX_PATH]; - char separator[2] = { platform_get_path_separator(), 0 }; + const char separator[2] = { platform_get_path_separator(), 0 }; + + if (gCustomUserDataPath[0] != 0) { + safe_strncpy(_userDataDirectoryPath, gCustomUserDataPath, sizeof(_userDataDirectoryPath)); + + // Ensure path ends with separator + int len = strlen(_userDataDirectoryPath); + if (_userDataDirectoryPath[len - 1] != separator[0]) { + strcat(_userDataDirectoryPath, separator); + } + return; + } if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, wOutPath))) { utf8 *outPathTemp = widechar_to_utf8(wOutPath); - safe_strncpy(outPath, outPathTemp, MAX_PATH); + safe_strncpy(_userDataDirectoryPath, outPathTemp, sizeof(_userDataDirectoryPath)); free(outPathTemp); - strcat(outPath, separator); - strcat(outPath, "OpenRCT2"); - strcat(outPath, separator); - if (subDirectory != NULL && subDirectory[0] != 0) { - strcat(outPath, subDirectory); - strcat(outPath, separator); - } + strcat(_userDataDirectoryPath, separator); + strcat(_userDataDirectoryPath, "OpenRCT2"); + strcat(_userDataDirectoryPath, separator); } else { - outPath[0] = 0; + log_fatal("Unable to resolve user data path."); + exit(-1); + } +} + +void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) +{ + const char separator[2] = { platform_get_path_separator(), 0 }; + + strcpy(outPath, _userDataDirectoryPath); + if (subDirectory != NULL && subDirectory[0] != 0) { + strcat(outPath, subDirectory); + strcat(outPath, separator); } } diff --git a/src/util/util.c b/src/util/util.c index 923c08fa29..a1c93d7ebe 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -183,6 +183,9 @@ int strcicmp(char const *a, char const *b) char *safe_strncpy(char * destination, const char * source, size_t size) { + assert(destination != NULL); + assert(source != NULL); + if (size == 0) { return destination; From 0b7544dbce035a97c716937dbc2130e522111b30 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Tue, 3 Nov 2015 23:18:10 +0000 Subject: [PATCH 2/3] fix error in linux.c and double .config/.config --- src/platform/linux.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/platform/linux.c b/src/platform/linux.c index 392819801b..beea676cdf 100644 --- a/src/platform/linux.c +++ b/src/platform/linux.c @@ -575,6 +575,7 @@ void platform_resolve_user_data_path() return; } + const char separator[2] = { platform_get_path_separator(), 0 }; char buffer[MAX_PATH]; buffer[0] = '\0'; log_verbose("buffer = '%s'", buffer); @@ -590,11 +591,15 @@ void platform_resolve_user_data_path() exit(-1); return; } + + strncat(buffer, homedir, MAX_PATH); + strncat(buffer, separator, MAX_PATH); + strncat(buffer, ".config", MAX_PATH); + } + else + { + strncat(buffer, homedir, MAX_PATH); } - char separator[2] = { platform_get_path_separator(), 0 }; - strncat(buffer, homedir, MAX_PATH); - strncat(buffer, separator, MAX_PATH); - strncat(buffer, ".config", MAX_PATH); strncat(buffer, separator, MAX_PATH); strncat(buffer, "OpenRCT2", MAX_PATH); strncat(buffer, separator, MAX_PATH); @@ -610,6 +615,7 @@ void platform_resolve_user_data_path() void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) { + const char separator[2] = { platform_get_path_separator(), 0 }; char buffer[MAX_PATH]; safe_strncpy(buffer, _userDataDirectoryPath, sizeof(buffer)); if (subDirectory != NULL && subDirectory[0] != 0) { From 70ef8d6777b0ad4d0527df018d96f76d9e9a8d7d Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Thu, 5 Nov 2015 21:36:24 +0000 Subject: [PATCH 3/3] add unicode support for windows command line arguments and resolve relative paths to absolute --- src/platform/linux.c | 5 +- src/platform/windows.c | 130 +++++++++++++---------------------------- 2 files changed, 42 insertions(+), 93 deletions(-) diff --git a/src/platform/linux.c b/src/platform/linux.c index beea676cdf..5117966ece 100644 --- a/src/platform/linux.c +++ b/src/platform/linux.c @@ -564,8 +564,10 @@ wchar_t *regular_to_wchar(const char* src) */ void platform_resolve_user_data_path() { + const char separator[2] = { platform_get_path_separator(), 0 }; + if (gCustomUserDataPath[0] != 0) { - safe_strncpy(_userDataDirectoryPath, gCustomUserDataPath, sizeof(_userDataDirectoryPath)); + realpath(gCustomUserDataPath, _userDataDirectoryPath); // Ensure path ends with separator int len = strlen(_userDataDirectoryPath); @@ -575,7 +577,6 @@ void platform_resolve_user_data_path() return; } - const char separator[2] = { platform_get_path_separator(), 0 }; char buffer[MAX_PATH]; buffer[0] = '\0'; log_verbose("buffer = '%s'", buffer); diff --git a/src/platform/windows.c b/src/platform/windows.c index 6e60ebed66..17f96bb738 100644 --- a/src/platform/windows.c +++ b/src/platform/windows.c @@ -39,7 +39,7 @@ utf8 _userDataDirectoryPath[MAX_PATH] = { 0 }; -LPSTR *CommandLineToArgvA(LPSTR lpCmdLine, int *argc); +utf8 **windows_get_command_line_args(int *outNumArgs); /** * Windows entry point to OpenRCT2 without a console window. @@ -77,18 +77,43 @@ __declspec(dllexport) int StartOpenRCT(HINSTANCE hInstance, HINSTANCE hPrevInsta RCT2_GLOBAL(RCT2_ADDRESS_HINSTANCE, HINSTANCE) = hInstance; RCT2_GLOBAL(RCT2_ADDRESS_CMDLINE, LPSTR) = lpCmdLine; - // Get command line arguments in standard form - argv = CommandLineToArgvA(lpCmdLine, &argc); + // argv = CommandLineToArgvA(lpCmdLine, &argc); + argv = (char**)windows_get_command_line_args(&argc); runGame = cmdline_run((const char **)argv, argc); - GlobalFree(argv); - if (runGame) + // Free argv + for (int i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); + + if (runGame) { openrct2_launch(); + } exit(gExitCode); return gExitCode; } +utf8 **windows_get_command_line_args(int *outNumArgs) +{ + int argc; + + // Get command line arguments as widechar + LPWSTR commandLine = GetCommandLineW(); + LPWSTR *argvW = CommandLineToArgvW(commandLine, &argc); + + // Convert to UTF-8 + utf8 **argvUtf8 = (utf8**)malloc(argc * sizeof(utf8*)); + for (int i = 0; i < argc; i++) { + argvUtf8[i] = widechar_to_utf8(argvW[i]); + } + LocalFree(argvW); + + *outNumArgs = argc; + return argvUtf8; +} + void platform_get_date(rct2_date *out_date) { assert(out_date != NULL); @@ -393,7 +418,15 @@ void platform_resolve_user_data_path() const char separator[2] = { platform_get_path_separator(), 0 }; if (gCustomUserDataPath[0] != 0) { - safe_strncpy(_userDataDirectoryPath, gCustomUserDataPath, sizeof(_userDataDirectoryPath)); + wchar_t *customUserDataPathW = utf8_to_widechar(gCustomUserDataPath); + if (GetFullPathNameW(customUserDataPathW, countof(wOutPath), wOutPath, NULL) == 0) { + log_fatal("Unable to resolve path '%s'.", gCustomUserDataPath); + exit(-1); + } + utf8 *outPathTemp = widechar_to_utf8(wOutPath); + safe_strncpy(_userDataDirectoryPath, outPathTemp, sizeof(_userDataDirectoryPath)); + free(outPathTemp); + free(customUserDataPathW); // Ensure path ends with separator int len = strlen(_userDataDirectoryPath); @@ -630,91 +663,6 @@ HWND windows_get_window_handle() return result; } -/** - * http://alter.org.ua/en/docs/win/args/ - */ -PCHAR *CommandLineToArgvA(PCHAR CmdLine, int *_argc) -{ - PCHAR* argv; - PCHAR _argv; - ULONG len; - ULONG argc; - CHAR a; - ULONG i, j; - - BOOLEAN in_QM; - BOOLEAN in_TEXT; - BOOLEAN in_SPACE; - - len = strlen(CmdLine); - i = ((len + 2) / 2)*sizeof(PVOID) + sizeof(PVOID); - - argv = (PCHAR*)GlobalAlloc(GMEM_FIXED, - i + (len + 2)*sizeof(CHAR) + 1); - - _argv = (PCHAR)(((PUCHAR)argv) + i); - - // Add in virtual 1st command line argument, process path, for arg_parse's sake. - argv[0] = 0; - argc = 1; - argv[argc] = _argv; - in_QM = FALSE; - in_TEXT = FALSE; - in_SPACE = TRUE; - i = 0; - j = 0; - - while (a = CmdLine[i]) { - if (in_QM) { - if (a == '\"') { - in_QM = FALSE; - } else { - _argv[j] = a; - j++; - } - } else { - switch (a) { - case '\"': - in_QM = TRUE; - in_TEXT = TRUE; - if (in_SPACE) { - argv[argc] = _argv + j; - argc++; - } - in_SPACE = FALSE; - break; - case ' ': - case '\t': - case '\n': - case '\r': - if (in_TEXT) { - _argv[j] = '\0'; - j++; - } - in_TEXT = FALSE; - in_SPACE = TRUE; - break; - default: - in_TEXT = TRUE; - if (in_SPACE) { - argv[argc] = _argv + j; - argc++; - } - _argv[j] = a; - j++; - in_SPACE = FALSE; - break; - } - } - i++; - } - _argv[j] = '\0'; - argv[argc] = NULL; - - (*_argc) = argc; - return argv; -} - uint16 platform_get_locale_language() { CHAR langCode[4];