mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[App] Fix auto-relaunching games with launch_data.bin
Move all the actual logic into the UI process and launch the new xex using --launch_module flag to the child process. The old handling for launch_data.bin is still in the game process for legacy reasons, in case that's started without a UI process for instance, but it's no longer used normally (as it's actually bugged though somewhat functional for some cases)
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include <QGridLayout>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "third_party/stb/stb_image_write.h"
|
||||
@@ -24,7 +25,6 @@
|
||||
#include "xenia/app/config_dialog_qt.h"
|
||||
#include "xenia/app/context_menu_widget_qt.h"
|
||||
#include "xenia/app/game_list_dialog_qt.h"
|
||||
#include "xenia/app/launch_data_restart_dialog_qt.h"
|
||||
#include "xenia/app/notification_widget_qt.h"
|
||||
#include "xenia/app/postprocessing_dialog_qt.h"
|
||||
#include "xenia/app/profile_dialog_qt.h"
|
||||
@@ -395,11 +395,22 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||
auto* qt_window = dynamic_cast<ui::QtWindow*>(window_.get());
|
||||
if (qt_window) {
|
||||
emulator_->set_on_launch_data_restart([this, qt_window]() {
|
||||
// Show dialog in UI thread
|
||||
// Show notification in UI thread
|
||||
window_->app_context().CallInUIThread([this, qt_window]() {
|
||||
auto* dialog = new app::LaunchDataRestartDialogQt(
|
||||
qt_window->qwindow(), window_.get(), emulator_->kernel_state());
|
||||
dialog->show();
|
||||
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();
|
||||
|
||||
// Schedule terminate and exit after notification duration
|
||||
QTimer::singleShot(5000, [this]() {
|
||||
if (emulator_->kernel_state()) {
|
||||
emulator_->kernel_state()->TerminateTitle();
|
||||
}
|
||||
std::quick_exit(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2123,9 +2134,46 @@ void EmulatorWindow::LaunchTitleInNewProcess(
|
||||
// Get the path to the current executable
|
||||
std::filesystem::path executable_path = xe::filesystem::GetExecutablePath();
|
||||
|
||||
// Verify the file exists (unless launching for launch_data)
|
||||
if (!for_launch_data && !std::filesystem::exists(path_to_file)) {
|
||||
XELOGE("Cannot launch title - file not found: {}", path_to_file.string());
|
||||
// Handle launch_data.bin if present
|
||||
std::filesystem::path actual_path = path_to_file;
|
||||
std::string launch_module_arg;
|
||||
|
||||
if (for_launch_data) {
|
||||
// Read launch_data.bin to get the host_path and launch_path
|
||||
FILE* file = xe::filesystem::OpenFile(
|
||||
kernel::xam::kXamModuleLoaderDataFileName, "rb");
|
||||
if (!file) {
|
||||
XELOGE("launch_data.bin not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read host_path (the disc/package path)
|
||||
uint16_t host_path_length = 0;
|
||||
fread(&host_path_length, sizeof(host_path_length), 1, file);
|
||||
std::string host_path;
|
||||
host_path.resize(host_path_length);
|
||||
fread(host_path.data(), host_path_length, 1, file);
|
||||
|
||||
// Read launch_path (the XEX to launch)
|
||||
uint16_t launch_path_length = 0;
|
||||
fread(&launch_path_length, sizeof(launch_path_length), 1, file);
|
||||
std::string launch_path;
|
||||
launch_path.resize(launch_path_length);
|
||||
fread(launch_path.data(), launch_path_length, 1, file);
|
||||
|
||||
fclose(file);
|
||||
|
||||
// Delete launch_data.bin so new process doesn't try to process it
|
||||
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());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2141,9 +2189,15 @@ void EmulatorWindow::LaunchTitleInNewProcess(
|
||||
cmd_line += u" --config=\"" + xe::to_utf16(cvars::config) + u"\"";
|
||||
}
|
||||
|
||||
// Add the target game file (unless launching for launch_data)
|
||||
if (!for_launch_data) {
|
||||
auto game_path_u16 = xe::path_to_utf16(path_to_file);
|
||||
// Add --launch_module if specified
|
||||
if (!launch_module_arg.empty()) {
|
||||
cmd_line +=
|
||||
u" --launch_module=\"" + xe::to_utf16(launch_module_arg) + u"\"";
|
||||
}
|
||||
|
||||
// Add the target game file
|
||||
if (!actual_path.empty()) {
|
||||
auto game_path_u16 = xe::path_to_utf16(actual_path);
|
||||
cmd_line += u" \"" + game_path_u16 + u"\"";
|
||||
}
|
||||
|
||||
@@ -2187,10 +2241,17 @@ void EmulatorWindow::LaunchTitleInNewProcess(
|
||||
argv.push_back(config_arg.c_str());
|
||||
}
|
||||
|
||||
// Add the target game file (unless launching for launch_data)
|
||||
// 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 the target game file
|
||||
std::string target_arg;
|
||||
if (!for_launch_data) {
|
||||
target_arg = path_to_file.string();
|
||||
if (!actual_path.empty()) {
|
||||
target_arg = actual_path.string();
|
||||
argv.push_back(target_arg.c_str());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/app/launch_data_restart_dialog_qt.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace app {
|
||||
|
||||
LaunchDataRestartDialogQt::LaunchDataRestartDialogQt(
|
||||
QWidget* parent, ui::Window* display_window,
|
||||
kernel::KernelState* kernel_state)
|
||||
: QDialog(parent),
|
||||
display_window_(display_window),
|
||||
kernel_state_(kernel_state) {
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
LaunchDataRestartDialogQt::~LaunchDataRestartDialogQt() {
|
||||
// Cleanup handled by Qt
|
||||
}
|
||||
|
||||
void LaunchDataRestartDialogQt::SetupUI() {
|
||||
setWindowTitle("Title Restart Required");
|
||||
setModal(true);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setMinimumWidth(400);
|
||||
|
||||
auto* main_layout = new QVBoxLayout(this);
|
||||
|
||||
// Message text
|
||||
auto* message_label = new QLabel(
|
||||
"Title is restarting with new launch data.\n"
|
||||
"Click OK to continue. Game will be loaded automatically.",
|
||||
this);
|
||||
message_label->setWordWrap(true);
|
||||
main_layout->addWidget(message_label);
|
||||
|
||||
main_layout->addSpacing(20);
|
||||
|
||||
// OK button
|
||||
auto* button_layout = new QHBoxLayout();
|
||||
button_layout->addStretch();
|
||||
|
||||
ok_button_ = new QPushButton("OK", this);
|
||||
ok_button_->setMinimumWidth(120);
|
||||
connect(ok_button_, &QPushButton::clicked, this,
|
||||
&LaunchDataRestartDialogQt::OnOKClicked);
|
||||
button_layout->addWidget(ok_button_);
|
||||
|
||||
button_layout->addStretch();
|
||||
main_layout->addLayout(button_layout);
|
||||
}
|
||||
|
||||
void LaunchDataRestartDialogQt::OnOKClicked() {
|
||||
// Terminate the title and quit after user clicks OK
|
||||
if (kernel_state_) {
|
||||
kernel_state_->TerminateTitle();
|
||||
}
|
||||
accept();
|
||||
|
||||
// Use quick_exit to avoid cleanup issues
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
} // namespace xe
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_APP_LAUNCH_DATA_RESTART_DIALOG_QT_H_
|
||||
#define XENIA_APP_LAUNCH_DATA_RESTART_DIALOG_QT_H_
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
class Window;
|
||||
} // namespace ui
|
||||
|
||||
namespace kernel {
|
||||
class KernelState;
|
||||
} // namespace kernel
|
||||
|
||||
namespace app {
|
||||
|
||||
class LaunchDataRestartDialogQt : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LaunchDataRestartDialogQt(QWidget* parent, ui::Window* display_window,
|
||||
kernel::KernelState* kernel_state);
|
||||
~LaunchDataRestartDialogQt() override;
|
||||
|
||||
private slots:
|
||||
void OnOKClicked();
|
||||
|
||||
private:
|
||||
void SetupUI();
|
||||
|
||||
ui::Window* display_window_;
|
||||
kernel::KernelState* kernel_state_;
|
||||
QPushButton* ok_button_;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_APP_LAUNCH_DATA_RESTART_DIALOG_QT_H_
|
||||
@@ -1,107 +0,0 @@
|
||||
/****************************************************************************
|
||||
** Meta object code from reading C++ file 'launch_data_restart_dialog_qt.h'
|
||||
**
|
||||
** Created by: The Qt Meta Object Compiler version 69 (Qt 6.9.2)
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost!
|
||||
*****************************************************************************/
|
||||
|
||||
#include <QtCore/qmetatype.h>
|
||||
#include "launch_data_restart_dialog_qt.h"
|
||||
|
||||
#include <QtCore/qtmochelpers.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QtCore/qxptype_traits.h>
|
||||
#if !defined(Q_MOC_OUTPUT_REVISION)
|
||||
#error \
|
||||
"The header file 'launch_data_restart_dialog_qt.h' doesn't include <QObject>."
|
||||
#elif Q_MOC_OUTPUT_REVISION != 69
|
||||
#error "This file was generated using the moc from 6.9.2. It"
|
||||
#error "cannot be used with the include files from this version of Qt."
|
||||
#error "(The moc has changed too much.)"
|
||||
#endif
|
||||
|
||||
#ifndef Q_CONSTINIT
|
||||
#define Q_CONSTINIT
|
||||
#endif
|
||||
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
QT_WARNING_DISABLE_GCC("-Wuseless-cast")
|
||||
namespace {
|
||||
struct qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t {};
|
||||
} // unnamed namespace
|
||||
|
||||
template <>
|
||||
constexpr inline auto
|
||||
xe::app::LaunchDataRestartDialogQt::qt_create_metaobjectdata<
|
||||
qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t>() {
|
||||
namespace QMC = QtMocConstants;
|
||||
QtMocHelpers::StringRefStorage qt_stringData{
|
||||
"xe::app::LaunchDataRestartDialogQt", "OnOKClicked", ""};
|
||||
|
||||
QtMocHelpers::UintData qt_methods{
|
||||
// Slot 'OnOKClicked'
|
||||
QtMocHelpers::SlotData<void()>(1, 2, QMC::AccessPrivate, QMetaType::Void),
|
||||
};
|
||||
QtMocHelpers::UintData qt_properties{};
|
||||
QtMocHelpers::UintData qt_enums{};
|
||||
return QtMocHelpers::metaObjectData<
|
||||
LaunchDataRestartDialogQt,
|
||||
qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t>(
|
||||
QMC::MetaObjectFlag{}, qt_stringData, qt_methods, qt_properties,
|
||||
qt_enums);
|
||||
}
|
||||
Q_CONSTINIT const QMetaObject xe::app::LaunchDataRestartDialogQt::staticMetaObject = { {
|
||||
QMetaObject::SuperData::link<QDialog::staticMetaObject>(),
|
||||
qt_staticMetaObjectStaticContent<qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t>.stringdata,
|
||||
qt_staticMetaObjectStaticContent<qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t>.data,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
qt_staticMetaObjectRelocatingContent<qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t>.metaTypes,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
void xe::app::LaunchDataRestartDialogQt::qt_static_metacall(
|
||||
QObject* _o, QMetaObject::Call _c, int _id, void** _a) {
|
||||
auto* _t = static_cast<LaunchDataRestartDialogQt*>(_o);
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
switch (_id) {
|
||||
case 0:
|
||||
_t->OnOKClicked();
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
(void)_a;
|
||||
}
|
||||
|
||||
const QMetaObject* xe::app::LaunchDataRestartDialogQt::metaObject() const {
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject()
|
||||
: &staticMetaObject;
|
||||
}
|
||||
|
||||
void* xe::app::LaunchDataRestartDialogQt::qt_metacast(const char* _clname) {
|
||||
if (!_clname) return nullptr;
|
||||
if (!strcmp(_clname, qt_staticMetaObjectStaticContent<qt_meta_tag_ZN2xe3app25LaunchDataRestartDialogQtE_t>.strings))
|
||||
return static_cast<void*>(this);
|
||||
return QDialog::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int xe::app::LaunchDataRestartDialogQt::qt_metacall(QMetaObject::Call _c,
|
||||
int _id, void** _a) {
|
||||
_id = QDialog::qt_metacall(_c, _id, _a);
|
||||
if (_id < 0) return _id;
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
if (_id < 1) qt_static_metacall(this, _c, _id, _a);
|
||||
_id -= 1;
|
||||
}
|
||||
if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
|
||||
if (_id < 1) *reinterpret_cast<QMetaType*>(_a[0]) = QMetaType();
|
||||
_id -= 1;
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
QT_WARNING_POP
|
||||
+57
-26
@@ -1434,42 +1434,73 @@ void Emulator::RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback) {
|
||||
game_config_load_callbacks_.erase(it);
|
||||
}
|
||||
|
||||
std::string Emulator::RemountAndResolveLaunchPath(
|
||||
const std::string& launch_path) {
|
||||
// Normalize path separators for the platform
|
||||
std::string normalized_path = launch_path;
|
||||
#if XE_PLATFORM_LINUX
|
||||
// Convert backslashes to forward slashes for consistent paths on Linux
|
||||
std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/');
|
||||
#endif
|
||||
|
||||
// Get the current game:\ symbolic link path
|
||||
std::string symbolic_link_path;
|
||||
if (!kernel_state_->file_system()->FindSymbolicLink(kDefaultGameSymbolicLink,
|
||||
symbolic_link_path)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::filesystem::path file_path = symbolic_link_path;
|
||||
|
||||
// Remove previous symbolic links.
|
||||
// Some titles can provide root within specific directory.
|
||||
kernel_state_->file_system()->UnregisterSymbolicLink(
|
||||
kDefaultPartitionSymbolicLink);
|
||||
kernel_state_->file_system()->UnregisterSymbolicLink(
|
||||
kDefaultGameSymbolicLink);
|
||||
|
||||
file_path /= std::filesystem::path(normalized_path);
|
||||
|
||||
// Re-register symbolic links to point to the parent directory of the module
|
||||
kernel_state_->file_system()->RegisterSymbolicLink(
|
||||
kDefaultPartitionSymbolicLink, xe::path_to_utf8(file_path.parent_path()));
|
||||
kernel_state_->file_system()->RegisterSymbolicLink(
|
||||
kDefaultGameSymbolicLink, xe::path_to_utf8(file_path.parent_path()));
|
||||
|
||||
return xe::path_to_utf8(file_path);
|
||||
}
|
||||
|
||||
std::string Emulator::FindLaunchModule() {
|
||||
std::string path("game:\\");
|
||||
|
||||
auto xam = kernel_state()->GetKernelModule<kernel::xam::XamModule>("xam.xex");
|
||||
|
||||
if (!xam->loader_data().launch_path.empty()) {
|
||||
std::string symbolic_link_path;
|
||||
if (kernel_state_->file_system()->FindSymbolicLink(kDefaultGameSymbolicLink,
|
||||
symbolic_link_path)) {
|
||||
std::filesystem::path file_path = symbolic_link_path;
|
||||
// Remove previous symbolic links.
|
||||
// Some titles can provide root within specific directory.
|
||||
kernel_state_->file_system()->UnregisterSymbolicLink(
|
||||
kDefaultPartitionSymbolicLink);
|
||||
kernel_state_->file_system()->UnregisterSymbolicLink(
|
||||
kDefaultGameSymbolicLink);
|
||||
|
||||
std::string launch_path = xam->loader_data().launch_path;
|
||||
#if XE_PLATFORM_LINUX
|
||||
// Convert backslashes to forward slashes for consistent paths on Linux
|
||||
std::replace(launch_path.begin(), launch_path.end(), '\\', '/');
|
||||
#endif
|
||||
file_path /= std::filesystem::path(launch_path);
|
||||
|
||||
kernel_state_->file_system()->RegisterSymbolicLink(
|
||||
kDefaultPartitionSymbolicLink,
|
||||
xe::path_to_utf8(file_path.parent_path()));
|
||||
kernel_state_->file_system()->RegisterSymbolicLink(
|
||||
kDefaultGameSymbolicLink, xe::path_to_utf8(file_path.parent_path()));
|
||||
|
||||
return xe::path_to_utf8(file_path);
|
||||
std::string result =
|
||||
RemountAndResolveLaunchPath(xam->loader_data().launch_path);
|
||||
if (!result.empty()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cvars::launch_module.empty()) {
|
||||
return path + cvars::launch_module;
|
||||
std::string launch_module = cvars::launch_module;
|
||||
#if XE_PLATFORM_LINUX
|
||||
// Convert backslashes to forward slashes for consistent paths on Linux
|
||||
std::replace(launch_module.begin(), launch_module.end(), '\\', '/');
|
||||
#endif
|
||||
|
||||
// If the module is in a subdirectory, remount game:\ to that subdirectory
|
||||
std::filesystem::path module_path(launch_module);
|
||||
if (module_path.has_parent_path() && module_path.parent_path() != ".") {
|
||||
std::string result = RemountAndResolveLaunchPath(launch_module);
|
||||
if (!result.empty()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// No subdirectory, just return as guest path
|
||||
return path + launch_module;
|
||||
}
|
||||
|
||||
return path + "default.xex";
|
||||
|
||||
@@ -367,6 +367,7 @@ class Emulator {
|
||||
void AddGameConfigLoadCallback(GameConfigLoadCallback* callback);
|
||||
void RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback);
|
||||
|
||||
std::string RemountAndResolveLaunchPath(const std::string& launch_path);
|
||||
std::string FindLaunchModule();
|
||||
|
||||
X_STATUS CompleteLaunch(const std::filesystem::path& path,
|
||||
|
||||
Reference in New Issue
Block a user