diff --git a/launcher/launcher.cpp b/launcher/launcher.cpp index 7a8f00f..547776b 100644 --- a/launcher/launcher.cpp +++ b/launcher/launcher.cpp @@ -18,6 +18,7 @@ enum LauncherError : int { E_OS_ERROR = -1, E_APP_NOT_FOUND = -2, E_MODENGINE_NOT_FOUND = -3, + E_CREATE_PROCESS_FAILED = -4, }; struct LaunchTargetParams { @@ -51,46 +52,74 @@ static std::map exe_names { { "armoredcore6.exe", ARMORED_CORE_6 }, }; -std::wstring GetCurrentDirectory() +namespace platform { +std::wstring get_env_var(const std::wstring& name) { - wchar_t buffer[MAX_PATH]; - GetModuleFileNameW(NULL, buffer, MAX_PATH); - std::string::size_type pos = std::wstring(buffer).find_last_of(L"\\/"); + size_t buffer_size = GetEnvironmentVariableW(name.c_str(), nullptr, 0); + auto* buffer = new wchar_t[buffer_size + 1]; - return std::wstring(buffer).substr(0, pos); + std::wstring value; + + if (buffer_size > 0) { + size_t len = GetEnvironmentVariableW(name.c_str(), &buffer[0], buffer_size + 1); + value.append(buffer, len); + } + + delete buffer; + + return value; } -int main() +void set_env_var(const std::wstring& name, const std::wstring& value) { - auto logger = spdlog::stderr_color_mt("stderr"); + SetEnvironmentVariableW(name.c_str(), value.c_str()); +} + +fs::path get_launcher_directory() +{ + size_t buffer_size = GetModuleFileNameW(nullptr, nullptr, 0); + auto* buffer = new wchar_t[buffer_size + 1]; - wchar_t launcher_filename[MAX_PATH]; + fs::path path = fs::current_path(); - // This isn't always needed, but cli11 doesn't allow us to signal an error - // from the function that produces the default value for the modengine.dll path. - if (!GetModuleFileNameW(nullptr, launcher_filename, MAX_PATH)) { - return E_OS_ERROR; + if (buffer_size > 0) { + size_t len = GetModuleFileNameW(nullptr, buffer, buffer_size + 1); + fs::path launcher_path(std::wstring_view { buffer, len }); + + path = launcher_path.parent_path(); } + delete buffer; + + return path; +} + +} + +int main() +{ + auto logger = spdlog::stderr_color_mt("stderr"); + auto launcher_directory = platform::get_launcher_directory(); + + CLI::App app { "ModEngine Launcher" }; LaunchTarget target = AUTODETECT; auto target_option = app.add_option("-t,--launch-target", target, "Launch target") - ->transform(CLI::CheckedTransformer(launch_target_names, CLI::ignore_case)); + ->transform(CLI::CheckedTransformer(launch_target_names, CLI::ignore_case)); std::string target_path_string; auto target_path_option = app.add_option("-p,--game-path", target_path_string, "Path to game executable. Will autodetect if not specified.") - ->transform(CLI::ExistingFile); + ->transform(CLI::ExistingFile); std::string config_path_string; auto config_option = app.add_option("-c,--config", config_path_string, "ModEngine configuration file path") - ->transform(CLI::ExistingFile); + ->transform(CLI::ExistingFile); bool suspend = false; app.add_option("-s,--suspend", suspend, "Start the game in a suspended state"); - auto launcher_path = fs::path(launcher_filename); - auto modengine_dll_path = launcher_path.parent_path() / L"modengine2" / L"bin" / L"modengine2.dll"; + auto modengine_dll_path = launcher_directory / L"modengine2" / L"bin" / L"modengine2.dll"; app.add_option("--modengine-dll", modengine_dll_path, "ModEngine DLL file path (modengine2.dll)"); @@ -103,8 +132,7 @@ int main() std::optional app_path = std::nullopt; // First if the game path was specified, use that along with the specified target - if (!target_path_option->empty()) - { + if (!target_path_option->empty()) { app_path = absolute(CLI::to_path(target_path_string)).parent_path().parent_path(); if (target == AUTODETECT) { logger->error("Game target must be specified when supplying a manual path"); @@ -115,7 +143,7 @@ int main() // If the game target was not set, try to find a game exe in the current directory and infer from that if (target_option->empty()) { for (auto& name_kv : exe_names) { - auto exepath = launcher_path.parent_path() / name_kv.first; + auto exepath = launcher_directory / name_kv.first; if (fs::exists(exepath)) { target = name_kv.second; app_path = exepath.parent_path().parent_path(); // app_path is expected to be steam app path not exe path @@ -133,7 +161,7 @@ int main() // If a config wasn't specified, try to load the default one for the game if (config_option->empty()) { - auto default_config_path = launcher_path.parent_path() / launch_params.default_config; + auto default_config_path = launcher_directory / launch_params.default_config; if (!fs::exists(default_config_path)) { logger->error("Could not find default config file at {}", default_config_path.string()); } @@ -164,30 +192,34 @@ int main() auto kernel32 = LoadLibraryW(L"kernel32.dll"); auto create_process_addr = GetProcAddress(kernel32, "CreateProcessW"); - auto exec_path_env = std::getenv("PATH"); - auto exec_path = std::wstring(exec_path_env, exec_path_env + strlen(exec_path_env)); + auto exec_path = platform::get_env_var(L"PATH"); exec_path.append(L";"); exec_path.append(modengine_dll_path.parent_path().native()); auto config_path = CLI::to_path(config_path_string); if (config_path.is_relative()) { - const auto search_path = GetCurrentDirectory() / config_path; - config_path = absolute(search_path); + const auto search_path = launcher_directory / config_path; + config_path = fs::absolute(search_path); } // These are inherited by the game process we launch with Detours. - SetEnvironmentVariable(L"SteamAppId", launch_params.app_id.c_str()); - SetEnvironmentVariable(L"MODENGINE_CONFIG", config_path.c_str()); - SetEnvironmentVariable(L"PATH", exec_path.c_str()); + platform::set_env_var(L"SteamAppId", launch_params.app_id); + platform::set_env_var(L"MODENGINE_CONFIG", config_path.native()); + platform::set_env_var(L"PATH", exec_path); if (suspend || IsDebuggerPresent()) { - SetEnvironmentVariableW(L"MODENGINE_DEBUG_GAME", L"1"); + platform::set_env_var(L"MODENGINE_DEBUG_GAME", L"1"); } - wchar_t cmd[MAX_PATH] = {}; - wcscpy_s(cmd, app_cmd.c_str()); + std::wstring cmd_str = app_cmd.native(); + size_t cmd_len = cmd_str.length(); + + auto *cmd = new wchar_t[cmd_len + 1]; + cmd[cmd_len] = 0; - auto proc_flags = CREATE_NEW_PROCESS_GROUP; + wcscpy_s(cmd, cmd_len, cmd_str.c_str()); + + auto proc_flags = 0; bool success = DetourCreateProcessWithDllW( cmd, nullptr, @@ -202,12 +234,15 @@ int main() fs::absolute(modengine_dll_path).string().c_str(), reinterpret_cast(create_process_addr)); + auto status = E_OK; + if (!success) { logger->error("Couldn't create process: {:x}", GetLastError()); + status = E_CREATE_PROCESS_FAILED; } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - return E_OK; + return status; } \ No newline at end of file