mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[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:
+125
-152
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user