diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 350e5c68e..26b6275a7 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -88,6 +88,7 @@ #include "xenia/gpu/command_processor.h" #include "xenia/gpu/graphics_system.h" #include "xenia/hid/input_system.h" +#include "xenia/kernel/user_module.h" #include "xenia/kernel/xam/profile_manager.h" #include "xenia/kernel/xam/xam_module.h" #include "xenia/kernel/xam/xam_state.h" @@ -1876,6 +1877,23 @@ void EmulatorWindow::UpdateTitle() { sb.Append(" v"); sb.Append(title_version); } + + // Show disc number for multi-disc titles and non-default XEX name + auto executable_module = emulator()->kernel_state()->GetExecutableModule(); + if (executable_module) { + if (executable_module->is_multi_disc_title()) { + sb.AppendFormat(" Disc {}", executable_module->disc_number()); + } + + // Show XEX name if it's not default.xex + auto module_name = executable_module->name(); + auto module_name_lower = xe::utf8::lower_ascii(module_name); + if (!module_name.empty() && module_name_lower != "default") { + sb.Append(" "); + sb.Append(module_name); + } + } + sb.Append("]"); auto title_name = emulator()->title_name(); @@ -2366,8 +2384,12 @@ void EmulatorWindow::LaunchTitleInNewProcess( std::filesystem::path actual_path = path_to_file; std::string launch_module_arg; + std::string launch_flags_arg; + std::string launch_data_arg; + uint32_t launch_title_id = title_id; + if (for_launch_data) { - // Read launch_data.txt to get the host_path and launch_path + // 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()) { @@ -2392,12 +2414,18 @@ void EmulatorWindow::LaunchTitleInNewProcess( 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; + } else if (key == "title_id") { + launch_title_id = std::stoul(value, nullptr, 16); } } file.close(); - // Delete launch_data.txt so new process doesn't try to process it + // 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 @@ -2413,8 +2441,8 @@ void EmulatorWindow::LaunchTitleInNewProcess( // Load per-game config overrides if title_id is provided std::vector game_config_args; - if (title_id != 0) { - auto title_id_str = fmt::format("{:08X}", title_id); + if (launch_title_id != 0) { + auto title_id_str = fmt::format("{:08X}", launch_title_id); game_config_args = config::LoadGameConfigAsArgs(title_id_str); } @@ -2436,6 +2464,16 @@ void EmulatorWindow::LaunchTitleInNewProcess( 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 per-game config overrides for (const auto& arg : game_config_args) { cmd_line += u" " + xe::to_utf16(arg); @@ -2509,6 +2547,20 @@ void EmulatorWindow::LaunchTitleInNewProcess( 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 per-game config overrides for (const auto& arg : game_config_args) { argv.push_back(arg.c_str()); diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index e494d1777..e2bfb96ce 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -138,6 +138,9 @@ DEFINE_bool(discord, true, "Enable Discord rich presence", "General"); DECLARE_bool(widescreen); +DECLARE_uint32(launch_flags); +DECLARE_string(launch_data); + #if XE_PLATFORM_WIN32 && XE_ARCH_AMD64 == 1 DEFINE_bool(enable_rdrand_ntdll_patch, false, "Hot-patches ntdll at the start of the process to not use rdrand " @@ -867,11 +870,32 @@ void EmulatorApp::EmulatorThread(bool is_game_process) { "xam.xex"); if (xam) { - xam->LoadLoaderData(); + // 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; - 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(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); + } } } diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index b2075ef5f..b642752d2 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -88,6 +88,16 @@ DEFINE_string( "module.", "General"); +DEFINE_CVar(launch_flags, 0, + "Launch flags passed from a title restart request. " + "Used internally for title-to-title launches.", + "General", true, uint32_t); + +DEFINE_CVar(launch_data, "", + "Hex-encoded launch data passed from a title restart request. " + "Used internally for title-to-title launches.", + "General", true, std::string); + DEFINE_bool(allow_game_relative_writes, false, "Not useful to non-developers. Allows code to write to paths " "relative to game://. Used for " diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 5b7ed575e..5406e17d0 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -772,6 +772,13 @@ dword_result_t XamSwapDisc_entry( } } + // Update the host_path in loader_data so title restarts use the new + // disc + auto xam = kernel_state()->GetKernelModule("xam.xex"); + if (xam) { + xam->loader_data().host_path = xe::path_to_utf8(new_disc_path); + } + // Success - break out of the loop break; } @@ -779,6 +786,13 @@ dword_result_t XamSwapDisc_entry( XELOGW( "XamSwapDisc: Mounted device is not an XContentContainerDevice, " "skipping validation"); + + // Update the host_path in loader_data so title restarts use the new disc + auto xam = kernel_state()->GetKernelModule("xam.xex"); + if (xam) { + xam->loader_data().host_path = xe::path_to_utf8(new_disc_path); + } + // For non-container devices, accept them (backward compatibility) break; } diff --git a/src/xenia/kernel/xam/xam_module.cc b/src/xenia/kernel/xam/xam_module.cc index f330a31c4..9493a7c9e 100644 --- a/src/xenia/kernel/xam/xam_module.cc +++ b/src/xenia/kernel/xam/xam_module.cc @@ -148,6 +148,7 @@ void XamModule::SaveLoaderData() { 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()) {