Skip to content
This repository has been archived by the owner on Jul 19, 2024. It is now read-only.

Fixes for ME2 launch failures in some scenarios #193

Merged
merged 5 commits into from
Mar 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 68 additions & 33 deletions launcher/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -51,46 +52,74 @@ static std::map<std::string, LaunchTarget> 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)");

Expand All @@ -103,8 +132,7 @@ int main()
std::optional<fs::path> 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");
Expand All @@ -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
Expand All @@ -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());
}
Expand Down Expand Up @@ -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,
Expand All @@ -202,12 +234,15 @@ int main()
fs::absolute(modengine_dll_path).string().c_str(),
reinterpret_cast<PDETOUR_CREATE_PROCESS_ROUTINEW>(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;
}
Loading