diff --git a/README.md b/README.md index e707725..05c7d33 100644 --- a/README.md +++ b/README.md @@ -133,10 +133,11 @@ All Doorstop arguments start with `--doorstop-` and always contain an argument. * `string` = any sequence of characters and numbers. Wrap into `"`s if the string contains spaces | Argument | Description | -| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| ------------------------------------------------- |------------------------------------------------------------------------------------------------------| | `--doorstop-enabled bool` | Enable or disable Doorstop. | | `--doorstop-redirect-output-log bool` | *Only on Windows*: If `true` Unity's output log is redirected to `\output_log.txt` | | `--doorstop-target-assembly string` | Path to the assembly to load and execute. | +| `--doorstop-boot-config-override string` | Overrides the boot.config file path. | | `--doorstop-mono-dll-search-path-override string` | Overrides default Mono DLL search path | | `--doorstop-mono-debug-enabled bool` | If true, Mono debugger server will be enabled | | `--doorstop-mono-debug-suspend bool` | Whether to suspend the game execution until the debugger is attached. | diff --git a/assets/nix/run.sh b/assets/nix/run.sh index ab24a52..b353448 100755 --- a/assets/nix/run.sh +++ b/assets/nix/run.sh @@ -25,6 +25,9 @@ enabled="1" # NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()` target_assembly="Doorstop.dll" +# Overrides the default boot.config file path +boot_config_override= + # If enabled, DOORSTOP_DISABLE env var value is ignored # USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS ignore_disable_switch="0" @@ -197,6 +200,10 @@ while :; do target_assembly="$2" shift ;; + --doorstop-boot-config-override) + boot_config_override="$2" + shift + ;; --doorstop-mono-dll-search-path-override) dll_search_path_override="$2" shift @@ -234,6 +241,7 @@ done # Move variables to environment export DOORSTOP_ENABLED="$enabled" export DOORSTOP_TARGET_ASSEMBLY="$target_assembly" +export DOORSTOP_BOOT_CONFIG_OVERRIDE="$boot_config_override" export DOORSTOP_IGNORE_DISABLED_ENV="$ignore_disable_switch" export DOORSTOP_MONO_DLL_SEARCH_PATH_OVERRIDE="$dll_search_path_override" export DOORSTOP_MONO_DEBUG_ENABLED="$debug_enable" diff --git a/assets/windows/doorstop_config.ini b/assets/windows/doorstop_config.ini index 4803878..2bc726b 100644 --- a/assets/windows/doorstop_config.ini +++ b/assets/windows/doorstop_config.ini @@ -11,6 +11,9 @@ target_assembly=Doorstop.dll # If true, Unity's output log is redirected to \output_log.txt redirect_output_log=false +# Overrides the default boot.config file path +boot_config_override= + # If enabled, DOORSTOP_DISABLE env var value is ignored # USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS ignore_disable_switch=false diff --git a/src/bootstrap.c b/src/bootstrap.c index 6c88701..65ba6d8 100644 --- a/src/bootstrap.c +++ b/src/bootstrap.c @@ -267,14 +267,14 @@ void il2cpp_doorstop_bootstrap() { strcat(app_paths_env, config.clr_corlib_dir); strcat(app_paths_env, PATH_SEP); strcat(app_paths_env, target_dir); - char *app_paths_env_n = narrow(app_paths_env); + const char *app_paths_env_n = narrow(app_paths_env); LOG("App path: %s", app_path); LOG("Target dir: %s", target_dir); LOG("Target name: %s", target_name); LOG("APP_PATHS: %s", app_paths_env); - char *props = "APP_PATHS"; + const char *props = "APP_PATHS"; setenv(TEXT("DOORSTOP_INITIALIZED"), TEXT("TRUE"), TRUE); setenv(TEXT("DOORSTOP_INVOKE_DLL_PATH"), config.target_assembly, TRUE); @@ -293,7 +293,8 @@ void il2cpp_doorstop_bootstrap() { void (*startup)() = NULL; result = coreclr.create_delegate(host, domain_id, target_name_n, - "Doorstop.Entrypoint", "Start", &startup); + "Doorstop.Entrypoint", "Start", + (void **)&startup); if (result != 0) { LOG("Failed to get entrypoint delegate: 0x%08x", result); return; diff --git a/src/config/common.c b/src/config/common.c index ac2c767..9632b8a 100644 --- a/src/config/common.c +++ b/src/config/common.c @@ -11,6 +11,7 @@ void cleanup_config() { } FREE_NON_NULL(config.target_assembly); + FREE_NON_NULL(config.boot_config_override); FREE_NON_NULL(config.mono_dll_search_path_override); FREE_NON_NULL(config.clr_corlib_dir); FREE_NON_NULL(config.clr_runtime_coreclr_path); @@ -27,6 +28,7 @@ void init_config_defaults() { config.mono_debug_suspend = FALSE; config.mono_debug_address = NULL; config.target_assembly = NULL; + config.boot_config_override = NULL; config.mono_dll_search_path_override = NULL; config.clr_corlib_dir = NULL; config.clr_runtime_coreclr_path = NULL; diff --git a/src/config/config.h b/src/config/config.h index cd963f4..0a0814b 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -35,6 +35,12 @@ typedef struct { */ char_t *target_assembly; + /** + * @brief Path to a custom boot.config file to use. If enabled, this file + * takes precedence over the default one in the Data folder. + */ + char_t *boot_config_override; + /** * @brief Path to use as the main DLL search path. If enabled, this folder * takes precedence over the default Managed folder. diff --git a/src/nix/config.c b/src/nix/config.c index afd8b8e..d977c25 100644 --- a/src/nix/config.c +++ b/src/nix/config.c @@ -10,7 +10,7 @@ void get_env_bool(const char_t *name, bool_t *target) { } } -void try_get_env(const char_t *name, const char_t *def, char_t **target) { +void try_get_env(const char_t *name, char_t *def, char_t **target) { char_t *value = getenv(name); if (value != NULL && strlen(value) > 0) { *target = strdup(value); @@ -37,6 +37,7 @@ void load_config() { try_get_env("DOORSTOP_MONO_DEBUG_ADDRESS", TEXT("127.0.0.1:10000"), &config.mono_debug_address); get_env_path("DOORSTOP_TARGET_ASSEMBLY", &config.target_assembly); + get_env_path("DOORSTOP_BOOT_CONFIG_OVERRIDE", &config.boot_config_override); try_get_env("DOORSTOP_MONO_DLL_SEARCH_PATH_OVERRIDE", TEXT(""), &config.mono_dll_search_path_override); get_env_path("DOORSTOP_CLR_RUNTIME_CORECLR_PATH", diff --git a/src/nix/entrypoint.c b/src/nix/entrypoint.c index 61ebde6..e9ee85e 100644 --- a/src/nix/entrypoint.c +++ b/src/nix/entrypoint.c @@ -59,6 +59,31 @@ int fclose_hook(FILE *stream) { return fclose(stream); } +char_t *default_boot_config_path = NULL; +#if !defined(__APPLE__) +FILE *fopen64_hook(char *filename, char *mode) { + char *actual_file_name = filename; + + if (strcmp(filename, default_boot_config_path) == 0) { + actual_file_name = config.boot_config_override; + LOG("Overriding boot.config to %s", actual_file_name); + } + + return fopen64(actual_file_name, mode); +} +#endif + +FILE *fopen_hook(char *filename, char *mode) { + char *actual_file_name = filename; + + if (strcmp(filename, default_boot_config_path) == 0) { + actual_file_name = config.boot_config_override; + LOG("Overriding boot.config to %s", actual_file_name); + } + + return fopen(actual_file_name, mode); +} + int dup2_hook(int od, int nd) { // Newer versions of Unity redirect stdout to player.log, we don't want // that @@ -80,7 +105,8 @@ __attribute__((constructor)) void doorstop_ctor() { void *unity_player = plthook_handle_by_name("UnityPlayer"); - if (unity_player && PLTHOOK_OPEN_BY_HANDLE_OR_ADDRESS(&hook, unity_player) == 0) { + if (unity_player && + PLTHOOK_OPEN_BY_HANDLE_OR_ADDRESS(&hook, unity_player) == 0) { LOG("Found UnityPlayer, hooking into it instead"); } else if (plthook_open(&hook, NULL) != 0) { LOG("Failed to open current process PLT! Cannot run Doorstop! " @@ -94,6 +120,31 @@ __attribute__((constructor)) void doorstop_ctor() { printf("Failed to hook dlsym, ignoring it. Error: %s\n", plthook_error()); + if (config.boot_config_override) { + if (file_exists(config.boot_config_override)) { + default_boot_config_path = calloc(MAX_PATH, sizeof(char_t)); + memset(default_boot_config_path, 0, MAX_PATH * sizeof(char_t)); + strcat(default_boot_config_path, get_working_dir()); + strcat(default_boot_config_path, TEXT("/")); + strcat(default_boot_config_path, + get_file_name(program_path(), FALSE)); + strcat(default_boot_config_path, TEXT("_Data/boot.config")); + +#if !defined(__APPLE__) + if (plthook_replace(hook, "fopen64", &fopen64_hook, NULL) != 0) + printf("Failed to hook fopen64, ignoring it. Error: %s\n", + plthook_error()); +#endif + if (plthook_replace(hook, "fopen", &fopen_hook, NULL) != 0) + printf("Failed to hook fopen, ignoring it. Error: %s\n", + plthook_error()); + } else { + LOG("The boot.config file won't be overriden because the provided " + "one does not exist: %s", + config.boot_config_override); + } + } + if (plthook_replace(hook, "fclose", &fclose_hook, NULL) != 0) printf("Failed to hook fclose, ignoring it. Error: %s\n", plthook_error()); diff --git a/src/util/util.h b/src/util/util.h index bce0a7f..b9387b3 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -24,7 +24,9 @@ typedef int bool_t; #define TRUE 1 #define FALSE 0 +#ifndef NULL #define NULL 0 +#endif #define TEXT(text) text diff --git a/src/windows/config.c b/src/windows/config.c index 493230d..6e90a44 100644 --- a/src/windows/config.c +++ b/src/windows/config.c @@ -71,6 +71,8 @@ static inline void init_config_file() { TEXT("false"), &config.redirect_output_log); load_path_file(config_path, TEXT("General"), TEXT("target_assembly"), DEFAULT_TARGET_ASSEMBLY, &config.target_assembly); + load_path_file(config_path, TEXT("General"), TEXT("boot_config_override"), + NULL, &config.boot_config_override); load_str_file(config_path, TEXT("UnityMono"), TEXT("dll_search_path_override"), TEXT(""), @@ -144,6 +146,8 @@ static inline void init_cmd_args() { config.redirect_output_log, load_bool_argv); PARSE_ARG(TEXT("--doorstop-target-assembly"), config.target_assembly, load_path_argv); + PARSE_ARG(TEXT("--doorstop-boot-config-override"), + config.boot_config_override, load_path_argv); PARSE_ARG(TEXT("--doorstop-mono-dll-search-path-override"), config.mono_dll_search_path_override, load_path_argv); diff --git a/src/windows/entrypoint.c b/src/windows/entrypoint.c index 12a6d98..7b542f4 100644 --- a/src/windows/entrypoint.c +++ b/src/windows/entrypoint.c @@ -41,6 +41,8 @@ bool_t fix_cwd() { #define LOG_FILE_CMD_END L"\\output_log.txt\"" #define LOG_FILE_CMD_END_LEN STR_LEN(LOG_FILE_CMD_END) +char_t *default_boot_config_path = NULL; + char_t *new_cmdline_args = NULL; char *new_cmdline_args_narrow = NULL; @@ -63,6 +65,70 @@ bool_t WINAPI close_handle_hook(void *handle) { return CloseHandle(handle); } +HANDLE WINAPI create_file_hook(LPCWSTR lpFileName, DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, + HANDLE hTemplateFile) { + LPCWSTR actual_file_name = lpFileName; + + char_t *normalised_path = calloc(strlen(lpFileName) + 1, sizeof(char_t)); + memset(normalised_path, 0, (strlen(lpFileName) + 1) * sizeof(char_t)); + strcpy(normalised_path, lpFileName); + for (size_t i = 0; i < strlen(normalised_path); i++) { + if (normalised_path[i] == L'/') { + normalised_path[i] = L'\\'; + } + } + + if (strcmpi(normalised_path, default_boot_config_path) == 0) { + actual_file_name = config.boot_config_override; + LOG("Overriding boot.config to %s", actual_file_name); + } + + free(normalised_path); + + return CreateFileW(actual_file_name, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); +} + +HANDLE WINAPI create_file_hook_narrow( + void *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { + void *actual_file_name = lpFileName; + + char_t *widened_filename = widen(lpFileName); + char_t *normalised_path = + calloc(strlen(widened_filename) + 1, sizeof(char_t)); + memset(normalised_path, 0, (strlen(widened_filename) + 1) * sizeof(char_t)); + strcpy(normalised_path, widened_filename); + free(widened_filename); + + for (size_t i = 0; i < strlen(normalised_path); i++) { + if (normalised_path[i] == L'/') { + normalised_path[i] = L'\\'; + } + } + + if (strcmpi(normalised_path, default_boot_config_path) == 0) { + char *narrowed_boot_config_override = + narrow(config.boot_config_override); + memcpy(actual_file_name, narrowed_boot_config_override, + strlen(config.boot_config_override)); + free(narrowed_boot_config_override); + LOG("Overriding boot.config to %s", actual_file_name); + } + + free(normalised_path); + + return CreateFileA(actual_file_name, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); +} + void capture_mono_path(void *handle) { char_t *result; get_module_path(handle, &result, NULL, 0); @@ -146,6 +212,25 @@ void inject(DoorstopPaths const *paths) { HOOK_SYS(target_module, GetProcAddress, get_proc_address_detour); HOOK_SYS(target_module, CloseHandle, close_handle_hook); + if (config.boot_config_override) { + if (file_exists(config.boot_config_override)) { + default_boot_config_path = calloc(MAX_PATH, sizeof(char_t)); + memset(default_boot_config_path, 0, MAX_PATH * sizeof(char_t)); + strcat(default_boot_config_path, get_working_dir()); + strcat(default_boot_config_path, TEXT("\\")); + strcat(default_boot_config_path, + get_file_name(program_path(), FALSE)); + strcat(default_boot_config_path, TEXT("_Data\\boot.config")); + + HOOK_SYS(target_module, CreateFileW, create_file_hook); + HOOK_SYS(target_module, CreateFileA, create_file_hook_narrow); + } else { + LOG("The boot.config file won't be overriden because the provided " + "one does not exist: %s", + config.boot_config_override); + } + } + HOOK_SYS(app_module, GetCommandLineW, get_command_line_hook); HOOK_SYS(app_module, GetCommandLineA, get_command_line_hook_narrow);