[App] Implement title-to-title launches without intermediate file

Removes the need to store launch_data.txt file and require a new app
start by directly spawning the child process with correct args and
exiting instead.
This commit is contained in:
Herman S.
2026-01-02 21:37:29 +09:00
parent f276316f72
commit b5496e9ea5
9 changed files with 233 additions and 355 deletions
+125 -152
View File
@@ -426,29 +426,119 @@ void EmulatorWindow::OnEmulatorInitialized() {
Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener");
}
// Register callback for launch data restart requests from the kernel
auto* qt_window = dynamic_cast<ui::QtWindow*>(window_.get());
if (qt_window) {
emulator_->set_on_launch_data_restart([this, qt_window]() {
// Show notification in UI thread
window_->app_context().CallInUIThread([this, qt_window]() {
auto* notification = new NotificationWidgetQt(
qt_window->qwindow(), "Title Restart Required",
"Title is restarting with new launch data.\n"
"Game will be loaded automatically.",
5000); // 5 second duration
notification->Show();
// Register callback for title-to-title launches from the kernel
emulator_->set_on_launch_new_title([this](const std::string& host_path,
const std::string& launch_module,
uint32_t launch_flags,
const std::string& launch_data) {
XELOGI(
"Launching new title: host_path={}, launch_module={}, flags={}, "
"data_len={}",
host_path, launch_module, launch_flags, launch_data.length());
// Schedule terminate and exit after notification duration
QTimer::singleShot(5000, [this]() {
if (emulator_->kernel_state()) {
emulator_->kernel_state()->TerminateTitle();
}
std::quick_exit(0);
});
});
});
}
std::filesystem::path executable_path = xe::filesystem::GetExecutablePath();
#if XE_PLATFORM_WIN32
auto exe_path_u16 = xe::path_to_utf16(executable_path);
std::u16string cmd_line = u"\"" + exe_path_u16 + u"\"";
if (!cvars::config.empty()) {
cmd_line += u" --config=\"" + xe::to_utf16(cvars::config) + u"\"";
}
// Append to log file instead of overwriting
cmd_line += u" --log_append=true";
if (!launch_module.empty()) {
cmd_line += u" --launch_module=\"" + xe::to_utf16(launch_module) + u"\"";
}
if (launch_flags != 0) {
cmd_line +=
u" --launch_flags=" + xe::to_utf16(fmt::format("{}", launch_flags));
}
if (!launch_data.empty()) {
cmd_line += u" --launch_data=" + xe::to_utf16(launch_data);
}
if (!host_path.empty()) {
cmd_line += u" \"" + xe::to_utf16(host_path) + u"\"";
}
STARTUPINFOW si = {};
si.cb = sizeof(si);
PROCESS_INFORMATION pi = {};
if (!CreateProcessW(nullptr,
const_cast<wchar_t*>(
reinterpret_cast<const wchar_t*>(cmd_line.c_str())),
nullptr, nullptr, FALSE, CREATE_NEW_CONSOLE, nullptr,
nullptr, &si, &pi)) {
XELOGE("Failed to launch new process: {}", GetLastError());
return;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
#else
pid_t pid = fork();
if (pid == 0) {
// Child process
if (cvars::use_mangohud) {
setenv("MANGOHUD", "1", 1);
}
std::vector<std::string> arg_storage;
std::vector<const char*> argv;
std::string gamemode_cmd;
if (cvars::use_gamemode) {
gamemode_cmd = "gamemoderun";
argv.push_back(gamemode_cmd.c_str());
}
arg_storage.push_back(executable_path.string());
argv.push_back(arg_storage.back().c_str());
if (!cvars::config.empty()) {
arg_storage.push_back("--config=" + cvars::config);
argv.push_back(arg_storage.back().c_str());
}
// Append to log file instead of overwriting
arg_storage.push_back("--log_append=true");
argv.push_back(arg_storage.back().c_str());
if (!launch_module.empty()) {
arg_storage.push_back("--launch_module=" + launch_module);
argv.push_back(arg_storage.back().c_str());
}
if (launch_flags != 0) {
arg_storage.push_back(fmt::format("--launch_flags={}", launch_flags));
argv.push_back(arg_storage.back().c_str());
}
if (!launch_data.empty()) {
arg_storage.push_back("--launch_data=" + launch_data);
argv.push_back(arg_storage.back().c_str());
}
if (!host_path.empty()) {
arg_storage.push_back(host_path);
argv.push_back(arg_storage.back().c_str());
}
argv.push_back(nullptr);
if (cvars::use_gamemode) {
execvp(gamemode_cmd.c_str(), const_cast<char**>(argv.data()));
} else {
execv(executable_path.c_str(), const_cast<char**>(argv.data()));
}
std::exit(1);
} else if (pid < 0) {
XELOGE("Failed to fork process");
return;
}
#endif
// Exit the current process
std::quick_exit(0);
});
// Register callback for disc swap to update title bar
emulator_->set_on_disc_swap([this](uint8_t new_disc_number) {
swapped_disc_number_ = new_disc_number;
app_context_.CallInUIThread([this]() { UpdateTitle(); });
});
}
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
@@ -1918,7 +2008,11 @@ void EmulatorWindow::UpdateTitle() {
auto executable_module = emulator()->kernel_state()->GetExecutableModule();
if (executable_module) {
if (executable_module->is_multi_disc_title()) {
sb.AppendFormat(" Disc {}", executable_module->disc_number());
// Use swapped disc number if set, otherwise use XEX header value
uint8_t disc_number = swapped_disc_number_ != 0
? swapped_disc_number_
: executable_module->disc_number();
sb.AppendFormat(" Disc {}", disc_number);
}
// Show XEX name if it's not default.xex
@@ -2411,63 +2505,13 @@ std::string EmulatorWindow::CanonicalizeFileExtension(
}
void EmulatorWindow::LaunchTitleInNewProcess(
const std::filesystem::path& path_to_file, bool for_launch_data) {
const std::filesystem::path& path_to_file) {
// Get the path to the current executable
std::filesystem::path executable_path = xe::filesystem::GetExecutablePath();
// Handle launch_data.txt if present
std::filesystem::path actual_path = path_to_file;
std::string launch_module_arg;
std::string launch_flags_arg;
std::string launch_data_arg;
if (for_launch_data) {
// Read launch_data.txt to get all launch parameters
std::filesystem::path file_path(kernel::xam::kXamModuleLoaderDataFileName);
std::ifstream file(file_path);
if (!file.is_open()) {
XELOGE("launch_data.txt not found");
return;
}
std::string host_path;
std::string launch_path;
std::string line;
while (std::getline(file, line)) {
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) {
continue;
}
std::string key = line.substr(0, eq_pos);
std::string value = line.substr(eq_pos + 1);
if (key == "host_path") {
host_path = value;
} else if (key == "launch_path") {
launch_path = value;
} else if (key == "launch_flags") {
launch_flags_arg = value;
} else if (key == "launch_data") {
launch_data_arg = value;
}
}
file.close();
// Delete launch_data.txt - we pass everything via command line
std::filesystem::remove(kernel::xam::kXamModuleLoaderDataFileName);
// Use the host_path as the target and launch_path as --launch_module
actual_path = host_path;
launch_module_arg = launch_path;
}
// Verify the file exists
if (!actual_path.empty() && !std::filesystem::exists(actual_path)) {
XELOGE("Cannot launch title - file not found: {}", actual_path.string());
if (!path_to_file.empty() && !std::filesystem::exists(path_to_file)) {
XELOGE("Cannot launch title - file not found: {}", path_to_file.string());
return;
}
@@ -2483,25 +2527,9 @@ void EmulatorWindow::LaunchTitleInNewProcess(
cmd_line += u" --config=\"" + xe::to_utf16(cvars::config) + u"\"";
}
// Add --launch_module if specified
if (!launch_module_arg.empty()) {
cmd_line +=
u" --launch_module=\"" + xe::to_utf16(launch_module_arg) + u"\"";
}
// Add --launch_flags if specified (for title-to-title launches)
if (!launch_flags_arg.empty()) {
cmd_line += u" --launch_flags=" + xe::to_utf16(launch_flags_arg);
}
// Add --launch_data if specified (for title-to-title launches)
if (!launch_data_arg.empty()) {
cmd_line += u" --launch_data=" + xe::to_utf16(launch_data_arg);
}
// Add the target game file
if (!actual_path.empty()) {
auto game_path_u16 = xe::path_to_utf16(actual_path);
if (!path_to_file.empty()) {
auto game_path_u16 = xe::path_to_utf16(path_to_file);
cmd_line += u" \"" + game_path_u16 + u"\"";
}
@@ -2560,31 +2588,10 @@ void EmulatorWindow::LaunchTitleInNewProcess(
argv.push_back(config_arg.c_str());
}
// Add --launch_module if specified
std::string launch_module_arg_str;
if (!launch_module_arg.empty()) {
launch_module_arg_str = "--launch_module=" + launch_module_arg;
argv.push_back(launch_module_arg_str.c_str());
}
// Add --launch_flags if specified (for title-to-title launches)
std::string launch_flags_arg_str;
if (!launch_flags_arg.empty()) {
launch_flags_arg_str = "--launch_flags=" + launch_flags_arg;
argv.push_back(launch_flags_arg_str.c_str());
}
// Add --launch_data if specified (for title-to-title launches)
std::string launch_data_arg_str;
if (!launch_data_arg.empty()) {
launch_data_arg_str = "--launch_data=" + launch_data_arg;
argv.push_back(launch_data_arg_str.c_str());
}
// Add the target game file
std::string target_arg;
if (!actual_path.empty()) {
target_arg = actual_path.string();
if (!path_to_file.empty()) {
target_arg = path_to_file.string();
argv.push_back(target_arg.c_str());
}
argv.push_back(nullptr);
@@ -2610,11 +2617,7 @@ void EmulatorWindow::LaunchTitleInNewProcess(
child_processes_.push_back(pid);
#endif
if (for_launch_data) {
XELOGI("Launched new process for launch_data.txt");
} else {
XELOGI("Launched title in new process: {}", path_to_file.string());
}
XELOGI("Launched title in new process: {}", path_to_file.string());
// Start periodic checking now that we have a child
ScheduleChildProcessCheck();
@@ -2705,36 +2708,6 @@ void EmulatorWindow::CheckChildProcessStatus() {
game_list_dialog_qt_->UpdateProfileButtonState();
XELOGI("Game list dialog refreshed");
}
// Check for launch_data.txt
FILE* file = xe::filesystem::OpenFile(
kernel::xam::kXamModuleLoaderDataFileName, "r");
if (file) {
fclose(file);
XELOGI(
"launch_data.txt exists - cleaning up old child and launching new "
"instance");
// Force kill any remaining child processes before launching new one
// (they should have exited but may be stuck)
#if XE_PLATFORM_WIN32
for (auto it = child_processes_.begin(); it != child_processes_.end();) {
TerminateProcess(*it, 0);
CloseHandle(*it);
it = child_processes_.erase(it);
}
#else
for (auto it = child_processes_.begin(); it != child_processes_.end();) {
XELOGI("Force killing stuck child process {}", *it);
kill(*it, SIGKILL);
int status;
waitpid(*it, &status, WNOHANG);
it = child_processes_.erase(it);
}
#endif
LaunchTitleInNewProcess(std::filesystem::path(), true);
}
}
had_child_last_check = has_child_now;
+3 -2
View File
@@ -84,8 +84,7 @@ class EmulatorWindow {
void OnEmulatorInitialized();
void LaunchTitleInNewProcess(const std::filesystem::path& path_to_file,
bool for_launch_data = false);
void LaunchTitleInNewProcess(const std::filesystem::path& path_to_file);
xe::X_STATUS RunTitle(const std::filesystem::path& path_to_file);
void UpdateTitle();
bool HasRunningChildProcess();
@@ -289,6 +288,8 @@ class EmulatorWindow {
std::string base_title_;
bool initializing_shader_storage_ = false;
// Disc number after disc swap (0 = use XEX header value)
uint8_t swapped_disc_number_ = 0;
QPointer<class PostProcessingDialogQt> postprocessing_dialog_qt_;
QPointer<class PerformanceTuningDialogQt> performance_tuning_dialog_qt_;
+16 -37
View File
@@ -589,16 +589,8 @@ bool EmulatorApp::OnInitialize() {
emulator_ =
std::make_unique<Emulator>("", storage_root, content_root, cache_root);
// Check if this is a game process (has target or launch_data.txt) or UI
// process
bool has_launch_data = false;
FILE* launch_data_file =
xe::filesystem::OpenFile(kernel::xam::kXamModuleLoaderDataFileName, "r");
if (launch_data_file) {
has_launch_data = true;
fclose(launch_data_file);
}
bool is_game_process = !cvars::target.empty() || has_launch_data;
// Check if this is a game process (has target) or UI process
bool is_game_process = !cvars::target.empty();
#if XE_PLATFORM_WIN32 && XE_ARCH_AMD64 == 1
// Apply ntdll rdrand patch for game process only
@@ -853,8 +845,7 @@ void EmulatorApp::EmulatorThread(bool is_game_process) {
return;
}
// Store the host path in loader_data for potential restart with
// launch_data.txt
// Store the host path in loader_data for title-to-title launches
auto xam_for_path =
emulator_->kernel_state()->GetKernelModule<kernel::xam::XamModule>(
"xam.xex");
@@ -872,32 +863,20 @@ void EmulatorApp::EmulatorThread(bool is_game_process) {
auto xam = emulator_->kernel_state()->GetKernelModule<kernel::xam::XamModule>(
"xam.xex");
if (xam) {
// Check if launch data was passed via command line (from UI process)
// This takes precedence over launch_data.txt
if (cvars::launch_flags != 0 || !cvars::launch_data.empty()) {
auto& loader_data = xam->loader_data();
loader_data.launch_data_present = true;
loader_data.launch_flags = cvars::launch_flags;
// Check if launch data was passed via command line (for title-to-title)
if (xam && (cvars::launch_flags != 0 || !cvars::launch_data.empty())) {
auto& loader_data = xam->loader_data();
loader_data.launch_data_present = true;
loader_data.launch_flags = cvars::launch_flags;
// Decode hex-encoded launch_data
if (!cvars::launch_data.empty()) {
loader_data.launch_data.clear();
const std::string& hex = cvars::launch_data;
for (size_t i = 0; i + 1 < hex.length(); i += 2) {
std::string byte_str = hex.substr(i, 2);
uint8_t byte =
static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
loader_data.launch_data.push_back(byte);
}
}
} else {
// Fall back to loading from file (legacy behavior)
xam->LoadLoaderData();
if (xam->loader_data().launch_data_present) {
const std::filesystem::path host_path = xam->loader_data().host_path;
emulator_->LaunchPath(host_path);
// Decode hex-encoded launch_data
if (!cvars::launch_data.empty()) {
loader_data.launch_data.clear();
const std::string& hex = cvars::launch_data;
for (size_t i = 0; i + 1 < hex.length(); i += 2) {
std::string byte_str = hex.substr(i, 2);
uint8_t byte = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
loader_data.launch_data.push_back(byte);
}
}
}
+8 -2
View File
@@ -47,6 +47,10 @@ DEFINE_bool(log_to_logcat, true, "Write log output to Android Logcat.",
"Logging");
#else
DEFINE_path(log_file, "", "Logs are written to the given file", "Logging");
DEFINE_transient_bool(log_append, false,
"Append to existing log file instead of overwriting. "
"Used for title-to-title launches.",
"Logging");
DEFINE_bool(log_to_stdout, true, "Write log output to stdout", "Logging");
DEFINE_bool(log_to_debugprint, false, "Dump the log to DebugPrint.", "Logging");
#endif // XE_PLATFORM_ANDROID
@@ -447,15 +451,17 @@ void InitializeLogging(const std::string_view app_name, bool is_game_process) {
// Only enable file logging for game processes, not the UI process
if (is_game_process) {
FILE* log_file = nullptr;
// Use append mode for title-to-title launches to preserve log history
const char* file_mode = cvars::log_append ? "at" : "wt";
if (cvars::log_file.empty()) {
// Default log file name for game process
std::string file_name = fmt::format("{}.log", app_name);
auto file_path = xe::filesystem::GetExecutableFolder() / file_name;
log_file = xe::filesystem::OpenFile(file_path, "wt");
log_file = xe::filesystem::OpenFile(file_path, file_mode);
} else {
// User specified log file - use as-is for game process
xe::filesystem::CreateParentFolder(cvars::log_file);
log_file = xe::filesystem::OpenFile(cvars::log_file, "wt");
log_file = xe::filesystem::OpenFile(cvars::log_file, file_mode);
}
logger_->AddLogSink(std::make_unique<FileLogSink>(log_file, true));
}
+19 -7
View File
@@ -326,13 +326,24 @@ class Emulator {
xe::Delegate<> on_terminate;
xe::Delegate<> on_exit;
// Called when XamLoaderLaunchTitle requests a restart with launch data.
// The UI layer should show an appropriate dialog and handle termination.
std::function<void()> on_launch_data_restart() const {
return on_launch_data_restart_;
// Called when XamLoaderLaunchTitle requests launching a new title.
// The callback should spawn a new process with the given parameters.
// Parameters: host_path, launch_module, launch_flags, launch_data (hex)
using LaunchNewTitleCallback = std::function<void(
const std::string&, const std::string&, uint32_t, const std::string&)>;
LaunchNewTitleCallback on_launch_new_title() const {
return on_launch_new_title_;
}
void set_on_launch_data_restart(std::function<void()> callback) {
on_launch_data_restart_ = std::move(callback);
void set_on_launch_new_title(LaunchNewTitleCallback callback) {
on_launch_new_title_ = std::move(callback);
}
// Called when XamSwapDisc successfully swaps to a new disc.
// Parameters: new_disc_number
using DiscSwapCallback = std::function<void(uint8_t)>;
DiscSwapCallback on_disc_swap() const { return on_disc_swap_; }
void set_on_disc_swap(DiscSwapCallback callback) {
on_disc_swap_ = std::move(callback);
}
private:
@@ -383,7 +394,8 @@ class Emulator {
bool restoring_;
threading::Fence restore_fence_; // Fired on restore finish.
std::function<void()> on_launch_data_restart_;
LaunchNewTitleCallback on_launch_new_title_;
DiscSwapCallback on_disc_swap_;
};
} // namespace xe
+19 -37
View File
@@ -651,8 +651,10 @@ dword_result_t XamSwapDisc_entry(
}
auto filesystem = kernel_state()->file_system();
auto mount_path = "\\Device\\LauncherData";
// Mount to Cdrom0 so the game: symlink points to the new disc
auto mount_path = "\\Device\\Cdrom0";
// Unmount the current disc
if (filesystem->ResolvePath(mount_path) != NULL) {
filesystem->UnregisterDevice(mount_path);
}
@@ -779,6 +781,12 @@ dword_result_t XamSwapDisc_entry(
xam->loader_data().host_path = xe::path_to_utf8(new_disc_path);
}
// Notify UI of disc swap for title bar update
auto on_disc_swap = kernel_state()->emulator()->on_disc_swap();
if (on_disc_swap) {
on_disc_swap(exec_info.disc_number);
}
// Success - break out of the loop
break;
}
@@ -847,24 +855,11 @@ dword_result_t XamContentLaunchImageFromFileInternal_entry(
entry, kernel_state()->emulator()->content_root(), progress, true);
}
auto xam = kernel_state()->GetKernelModule<XamModule>("xam.xex");
auto& loader_data = xam->loader_data();
loader_data.host_path = xe::path_to_utf8(host_path);
loader_data.launch_path = xex_name_;
xam->SaveLoaderData();
auto display_window = kernel_state()->emulator()->display_window();
auto imgui_drawer = kernel_state()->emulator()->imgui_drawer();
if (display_window && imgui_drawer) {
display_window->app_context().CallInUIThreadSynchronous([imgui_drawer]() {
xe::ui::ImGuiDialog::ShowMessageBox(
imgui_drawer, "Launching new title!",
"Launching new title. \nPlease close Xenia and launch it again. Game "
"should load automatically.");
});
auto on_launch_new_title = kernel_state()->emulator()->on_launch_new_title();
if (on_launch_new_title) {
XELOGI("XamContentLaunchImageFromFileInternal: spawning new title process");
on_launch_new_title(xe::path_to_utf8(host_path), xex_name_, 0, "");
// Callback calls quick_exit, so we don't reach here
}
kernel_state()->TerminateTitle();
@@ -903,24 +898,11 @@ dword_result_t XamContentLaunchImageInternal_entry(lpvoid_t content_data_ptr,
entry, kernel_state()->emulator()->content_root(), progress, true);
}
auto xam = kernel_state()->GetKernelModule<XamModule>("xam.xex");
auto& loader_data = xam->loader_data();
loader_data.host_path = xe::path_to_utf8(host_path);
loader_data.launch_path = xex_path.value();
xam->SaveLoaderData();
auto display_window = kernel_state()->emulator()->display_window();
auto imgui_drawer = kernel_state()->emulator()->imgui_drawer();
if (display_window && imgui_drawer) {
display_window->app_context().CallInUIThreadSynchronous([imgui_drawer]() {
xe::ui::ImGuiDialog::ShowMessageBox(
imgui_drawer, "Launching new title!",
"Launching new title. \nPlease close Xenia and launch it again. Game "
"should load automatically.");
});
auto on_launch_new_title = kernel_state()->emulator()->on_launch_new_title();
if (on_launch_new_title) {
XELOGI("XamContentLaunchImageInternal: spawning new title process");
on_launch_new_title(xe::path_to_utf8(host_path), xex_path.value(), 0, "");
// Callback calls quick_exit, so we don't reach here
}
kernel_state()->TerminateTitle();
+43 -14
View File
@@ -385,26 +385,55 @@ void XamLoaderLaunchTitle_entry(lpstring_t raw_name_ptr, dword_t flags) {
if (raw_name_ptr) {
auto path = raw_name_ptr.value();
if (path.empty()) {
// Empty path means exit to dashboard - don't save loader data
// Empty path means exit to dashboard
loader_data.launch_path = "game:\\default.xex";
} else {
// Non-empty path means launching another title - save loader data
loader_data.launch_path = xe::path_to_utf8(path);
// Non-empty path means launching another title
loader_data.launch_data_present = true;
xam->SaveLoaderData();
}
if (loader_data.launch_data_present) {
// Notify the UI that a restart with launch data is requested.
// The callback can show a notification, but we terminate immediately
// to prevent the game from calling this function repeatedly.
auto on_launch_data_restart =
kernel_state()->emulator()->on_launch_data_restart();
if (on_launch_data_restart) {
on_launch_data_restart();
// Normalize the paths
std::filesystem::path host_path = loader_data.host_path;
std::string launch_path = xe::path_to_utf8(path);
XELOGI("XamLoaderLaunchTitle: original host_path={}, launch_path={}",
loader_data.host_path, launch_path);
// Remove common guest path prefixes
auto remove_prefix = [&launch_path](std::string_view prefix) {
if (launch_path.compare(0, prefix.length(), prefix) == 0) {
launch_path = launch_path.substr(prefix.length());
}
};
remove_prefix("game:\\");
remove_prefix("d:\\");
// If host_path points to a .xex, combine with launch_path
if (host_path.extension() == ".xex") {
host_path.remove_filename();
host_path = host_path / launch_path;
launch_path = "";
}
// Terminate immediately when launch data is present
XELOGI("XamLoaderLaunchTitle: normalized host_path={}, launch_path={}",
xe::path_to_utf8(host_path), launch_path);
// Convert launch_data to hex string
std::string launch_data_hex;
for (uint8_t byte : loader_data.launch_data) {
launch_data_hex += fmt::format("{:02X}", byte);
}
// Call the callback to spawn the new process directly
auto on_launch_new_title =
kernel_state()->emulator()->on_launch_new_title();
if (on_launch_new_title) {
XELOGI("XamLoaderLaunchTitle: spawning new title process");
on_launch_new_title(xe::path_to_utf8(host_path), launch_path,
loader_data.launch_flags, launch_data_hex);
// Callback calls quick_exit, so we don't reach here
}
// Terminate if callback wasn't set
XELOGI("XamLoaderLaunchTitle: terminating to launch new title");
kernel_state()->TerminateTitle();
// This function does not return
-98
View File
@@ -67,104 +67,6 @@ void XamModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) {
XamModule::~XamModule() {}
void XamModule::LoadLoaderData() {
std::filesystem::path file_path(kXamModuleLoaderDataFileName);
std::ifstream file(file_path);
if (!file.is_open()) {
loader_data_.launch_data_present = false;
return;
}
loader_data_.launch_data_present = true;
std::string line;
while (std::getline(file, line)) {
// Find the '=' delimiter
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) {
continue; // Skip malformed lines
}
std::string key = line.substr(0, eq_pos);
std::string value = line.substr(eq_pos + 1);
if (key == "host_path") {
loader_data_.host_path = value;
} else if (key == "launch_path") {
loader_data_.launch_path = value;
} else if (key == "launch_flags") {
loader_data_.launch_flags = std::stoul(value);
} else if (key == "launch_data") {
// Convert hex string back to bytes
if (!value.empty()) {
loader_data_.launch_data.clear();
for (size_t i = 0; i < value.length(); i += 2) {
if (i + 1 < value.length()) {
std::string byte_str = value.substr(i, 2);
uint8_t byte =
static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
loader_data_.launch_data.push_back(byte);
}
}
}
}
}
file.close();
// We read launch data. Let's remove it till next request.
std::filesystem::remove(kXamModuleLoaderDataFileName);
}
void XamModule::SaveLoaderData() {
std::filesystem::path file_path(kXamModuleLoaderDataFileName);
std::ofstream file(file_path);
if (!file.is_open()) {
return;
}
std::filesystem::path host_path = loader_data_.host_path;
std::string launch_path = loader_data_.launch_path;
auto remove_prefix = [&launch_path](std::string_view prefix) {
if (launch_path.compare(0, prefix.length(), prefix) == 0) {
launch_path = launch_path.substr(prefix.length());
}
};
remove_prefix("game:\\");
remove_prefix("d:\\");
if (host_path.extension() == ".xex") {
host_path.remove_filename();
host_path = host_path / launch_path;
launch_path = "";
}
const std::string host_path_as_string = xe::path_to_utf8(host_path);
// Write text format: one field per line
file << "host_path=" << host_path_as_string << "\n";
file << "launch_path=" << launch_path << "\n";
file << "launch_flags=" << loader_data_.launch_flags << "\n";
file << "title_id=" << std::hex << kernel_state()->title_id() << "\n";
// Convert launch_data bytes to hex string
if (!loader_data_.launch_data.empty()) {
file << "launch_data=";
for (uint8_t byte : loader_data_.launch_data) {
file << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<unsigned>(byte);
}
file << "\n";
} else {
file << "launch_data=\n";
}
file.close();
}
} // namespace xam
} // namespace kernel
} // namespace xe
-6
View File
@@ -20,9 +20,6 @@ namespace xe {
namespace kernel {
namespace xam {
static constexpr std::string_view kXamModuleLoaderDataFileName =
"launch_data.txt";
class XamModule : public KernelModule {
public:
XamModule(Emulator* emulator, KernelState* kernel_state);
@@ -40,9 +37,6 @@ class XamModule : public KernelModule {
std::vector<uint8_t> launch_data;
};
void LoadLoaderData();
void SaveLoaderData();
const LoaderData& loader_data() const { return loader_data_; }
LoaderData& loader_data() { return loader_data_; }