[App/Xam] Fix multi-disc issues

Properly handle launch flags and launch data, add disc/xex to title bar
Fixes Orange Box, Rage, etc
This commit is contained in:
Herman S.
2025-12-20 14:57:48 +09:00
parent 01cd752798
commit c50e07331d
5 changed files with 109 additions and 8 deletions
+56 -4
View File
@@ -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<std::string> 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());
+28 -4
View File
@@ -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<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);
}
}
}
+10
View File
@@ -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 "
+14
View File
@@ -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<XamModule>("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<XamModule>("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;
}
+1
View File
@@ -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()) {