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 64e4e71579..60254b563c 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..5117966ece 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,27 @@ 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()
{
+ const char separator[2] = { platform_get_path_separator(), 0 };
+
+ if (gCustomUserDataPath[0] != 0) {
+ realpath(gCustomUserDataPath, _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 +588,37 @@ 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;
}
+
+ 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, "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)
+{
+ 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) {
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..17f96bb738 100644
--- a/src/platform/windows.c
+++ b/src/platform/windows.c
@@ -37,7 +37,9 @@
// The name of the mutex used to prevent multiple instances of the game from running
#define SINGLE_INSTANCE_MUTEX_NAME "RollerCoaster Tycoon 2_GSKMUTEX"
-LPSTR *CommandLineToArgvA(LPSTR lpCmdLine, int *argc);
+utf8 _userDataDirectoryPath[MAX_PATH] = { 0 };
+
+utf8 **windows_get_command_line_args(int *outNumArgs);
/**
* Windows entry point to OpenRCT2 without a console window.
@@ -75,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);
@@ -380,25 +407,57 @@ 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) {
+ 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);
+ 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);
}
}
@@ -604,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];
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;