From 99361c54e514b8df1f3bd23f4a4c34baa787cdb0 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:16:31 +0900 Subject: [PATCH 1/4] [App] Ensure profile state is reloaded on child process exit. If the game was started logged out but the user logs in during the game we want that to be reflected in the main game list windows. --- src/xenia/app/emulator_window.cc | 18 +++++++++--- src/xenia/kernel/xam/profile_manager.cc | 38 ++++++++++++++++++------- src/xenia/ui/game_list_dialog_qt.h | 2 +- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index beacaa11f..7162c4f4b 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -2394,16 +2394,25 @@ void EmulatorWindow::CheckChildProcessStatus() { // Detect transition from having child to no child if (had_child_last_check && !has_child_now) { - XELOGI("Child process exited, remounting profiles and reloading GPDs"); + XELOGI("Child process exited, reloading config and profile state"); - // Remount VFS for all logged-in profiles to pick up file changes from game - // process + // Reload config from disk to pick up any profile login changes made by + // child process + config::ReloadConfig(); + XELOGI("Config reloaded from disk"); + + // Sync profile login state with the reloaded config if (emulator_ && emulator_->kernel_state() && emulator_->kernel_state()->xam_state()) { auto profile_manager = emulator_->kernel_state()->xam_state()->profile_manager(); if (profile_manager) { - // Remount VFS for each logged-in profile + // Sync profiles with config: logout current profiles and login based on + // config cvars + profile_manager->SyncProfilesWithConfig(); + XELOGI("Profile login state synced with config"); + + // Remount VFS for all newly logged-in profiles for (uint8_t i = 0; i < 4; i++) { auto profile = profile_manager->GetProfile(i); if (profile) { @@ -2427,6 +2436,7 @@ void EmulatorWindow::CheckChildProcessStatus() { // Reload the game list dialog if it's open if (game_list_dialog_qt_) { game_list_dialog_qt_->LoadGameList(); + game_list_dialog_qt_->UpdateProfileButtonState(); XELOGI("Game list dialog refreshed"); } diff --git a/src/xenia/kernel/xam/profile_manager.cc b/src/xenia/kernel/xam/profile_manager.cc index 1035a9351..427d381c8 100644 --- a/src/xenia/kernel/xam/profile_manager.cc +++ b/src/xenia/kernel/xam/profile_manager.cc @@ -162,30 +162,46 @@ void ProfileManager::SyncProfilesWithConfig() { LoadAccount(account_xuid); } - // Then logout all currently logged in profiles - std::vector slots_to_logout; - for (const auto& [slot, profile] : logged_profiles_) { - slots_to_logout.push_back(slot); - } - for (uint8_t slot : slots_to_logout) { - Logout(slot, false); - } - - // Now login profiles based on the current cvar values + // Build desired login state from current cvar values const std::string* profile_cvars[4] = { &cvars::logged_profile_slot_0_xuid, &cvars::logged_profile_slot_1_xuid, &cvars::logged_profile_slot_2_xuid, &cvars::logged_profile_slot_3_xuid}; + std::map desired_profiles; for (uint8_t slot = 0; slot < 4; slot++) { if (!profile_cvars[slot]->empty()) { uint64_t xuid = xe::string_util::from_string(*profile_cvars[slot], true); if (xuid != 0) { - Login(xuid, slot, false); + desired_profiles[slot] = xuid; } } } + // Logout profiles that shouldn't be logged in anymore + std::vector slots_to_logout; + for (const auto& [slot, profile] : logged_profiles_) { + auto desired_it = desired_profiles.find(slot); + if (desired_it == desired_profiles.end() || + desired_it->second != profile->xuid()) { + // This slot should be logged out (either empty or different profile) + slots_to_logout.push_back(slot); + } + } + for (uint8_t slot : slots_to_logout) { + Logout(slot, false); + } + + // Login profiles that aren't already logged in + for (const auto& [slot, xuid] : desired_profiles) { + auto current_profile = logged_profiles_.find(slot); + if (current_profile == logged_profiles_.end() || + current_profile->second->xuid() != xuid) { + // This slot needs to be logged in (either empty or different profile) + Login(xuid, slot, false); + } + } + // Send a single notification after all changes kernel_state_->BroadcastNotification(kXNotificationSystemSignInChanged, GetUsedUserSlots().to_ulong()); diff --git a/src/xenia/ui/game_list_dialog_qt.h b/src/xenia/ui/game_list_dialog_qt.h index c2d70f836..338d3f591 100644 --- a/src/xenia/ui/game_list_dialog_qt.h +++ b/src/xenia/ui/game_list_dialog_qt.h @@ -56,6 +56,7 @@ class GameListDialogQt : public QWidget { void LoadGameList(); void RefreshIcons(); + void UpdateProfileButtonState(); private slots: void OnFilterTextChanged(const QString& text); @@ -67,7 +68,6 @@ class GameListDialogQt : public QWidget { void OnProfileContextMenu(const QPoint& pos); void OnSelectionChanged(); void UpdatePlayButtonState(); - void UpdateProfileButtonState(); protected: bool eventFilter(QObject* obj, QEvent* event) override; From d6c4ea7e1184cefc9ae1152b9fac0d0b920543de Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:37:22 +0900 Subject: [PATCH 2/4] [UI] Add Saves browser to game list context menu --- src/xenia/ui/game_list_dialog_qt.cc | 68 ++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/xenia/ui/game_list_dialog_qt.cc b/src/xenia/ui/game_list_dialog_qt.cc index 00fc4623a..c085c2c0d 100644 --- a/src/xenia/ui/game_list_dialog_qt.cc +++ b/src/xenia/ui/game_list_dialog_qt.cc @@ -805,35 +805,55 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) { launch_action = context_menu.addAction("Open"); } - // Achievements option (enabled if user is logged in) - QAction* achievements_action = nullptr; + // Get the primary profile (same one shown in profile button) bool is_signedin = false; uint64_t xuid = 0; - // TODO: Handle multiple signed-in profiles better - // For now, just use the first logged-in profile we find - for (uint8_t user_index = 0; user_index < XUserMaxUserCount; user_index++) { - const auto profile = profile_manager->GetProfile(user_index); - if (profile) { - is_signedin = true; - xuid = profile->xuid(); - break; + // Use the profile from slot 0 as primary (matches profile button logic) + const auto primary_profile = + profile_manager->GetProfile(static_cast(0)); + if (primary_profile) { + is_signedin = true; + xuid = primary_profile->xuid(); + } + + // Saves folder option (enabled if user is logged in, we have a valid + // title_id, and saves exist) + QAction* saves_action = nullptr; + if (title_id != 0) { + saves_action = context_menu.addAction("Saves"); + if (!is_signedin) { + saves_action->setEnabled(false); + } else { + // Check if saves folder exists + auto saves_path = profile_manager->GetProfileContentPath( + xuid, title_id, XContentType::kSavedGame); + if (!std::filesystem::exists(saves_path)) { + saves_action->setEnabled(false); + } } } - if (is_signedin && title_id != 0) { + // Achievements option (enabled if user is logged in and we have a valid + // title_id) + QAction* achievements_action = nullptr; + if (title_id != 0) { achievements_action = context_menu.addAction("Achievements"); - connect(achievements_action, &QAction::triggered, [=, this]() { - // Get the title name from the game entry - QString title_name; - for (const auto& entry : game_entries_) { - if (entry.title_id == title_id) { - title_name = QString::fromStdString(entry.title_name); - break; + if (!is_signedin) { + achievements_action->setEnabled(false); + } else { + connect(achievements_action, &QAction::triggered, [=, this]() { + // Get the title name from the game entry + QString title_name; + for (const auto& entry : game_entries_) { + if (entry.title_id == title_id) { + title_name = QString::fromStdString(entry.title_name); + break; + } } - } - ShowAchievementsDialog(xuid, title_id, title_name); - }); + ShowAchievementsDialog(xuid, title_id, title_name); + }); + } } // Game config overrides option (enabled if we have a valid title_id) @@ -868,6 +888,12 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) { } } else if (selected == open_folder_action && open_folder_action) { OpenContainingFolder(path); + } else if (selected == saves_action && saves_action) { + // Open the saves folder for the primary profile + auto saves_path = profile_manager->GetProfileContentPath( + xuid, title_id, XContentType::kSavedGame); + std::thread path_open(LaunchFileExplorer, saves_path); + path_open.detach(); } else if (selected == achievements_action && achievements_action) { // Achievements dialog will open in a separate call } else if (selected == config_action && config_action) { From c0a73b18bc1c23b6e7482b17b112a6a30039ef61 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:45:04 +0900 Subject: [PATCH 3/4] [UI] Add DLC and Title Update entries to game list context menu --- src/xenia/ui/game_list_dialog_qt.cc | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/xenia/ui/game_list_dialog_qt.cc b/src/xenia/ui/game_list_dialog_qt.cc index c085c2c0d..28dbc5985 100644 --- a/src/xenia/ui/game_list_dialog_qt.cc +++ b/src/xenia/ui/game_list_dialog_qt.cc @@ -834,6 +834,31 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) { } } + // Title Updates folder option (enabled if we have a valid title_id and + // updates exist) + QAction* title_updates_action = nullptr; + if (title_id != 0) { + title_updates_action = context_menu.addAction("Title Updates"); + // Title updates use xuid=0 and content type kInstaller + auto tu_path = profile_manager->GetProfileContentPath( + 0, title_id, XContentType::kInstaller); + if (!std::filesystem::exists(tu_path)) { + title_updates_action->setEnabled(false); + } + } + + // DLC folder option (enabled if we have a valid title_id and DLC exists) + QAction* dlc_action = nullptr; + if (title_id != 0) { + dlc_action = context_menu.addAction("DLC"); + // DLC uses xuid=0 and content type kMarketplaceContent + auto dlc_path = profile_manager->GetProfileContentPath( + 0, title_id, XContentType::kMarketplaceContent); + if (!std::filesystem::exists(dlc_path)) { + dlc_action->setEnabled(false); + } + } + // Achievements option (enabled if user is logged in and we have a valid // title_id) QAction* achievements_action = nullptr; @@ -894,6 +919,18 @@ void GameListDialogQt::OnGameRightClicked(const QPoint& pos) { xuid, title_id, XContentType::kSavedGame); std::thread path_open(LaunchFileExplorer, saves_path); path_open.detach(); + } else if (selected == title_updates_action && title_updates_action) { + // Open the title updates folder + auto tu_path = profile_manager->GetProfileContentPath( + 0, title_id, XContentType::kInstaller); + std::thread path_open(LaunchFileExplorer, tu_path); + path_open.detach(); + } else if (selected == dlc_action && dlc_action) { + // Open the DLC folder + auto dlc_path = profile_manager->GetProfileContentPath( + 0, title_id, XContentType::kMarketplaceContent); + std::thread path_open(LaunchFileExplorer, dlc_path); + path_open.detach(); } else if (selected == achievements_action && achievements_action) { // Achievements dialog will open in a separate call } else if (selected == config_action && config_action) { From b24c9ac8ef22667b58c7a9d268c646afb28f07e1 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:14:54 +0900 Subject: [PATCH 4/4] [Build] Allow building with AVX support only on linux. --- premake5.lua | 8 ++------ src/xenia/base/memory.cc | 5 +++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/premake5.lua b/premake5.lua index e62277a2c..8f2ca3ff7 100644 --- a/premake5.lua +++ b/premake5.lua @@ -49,13 +49,9 @@ fatalwarnings("All") -- TODO(DrChat): Find a way to disable this on other architectures. if ARCH ~= "ppc64" then - filter({"architecture:x86_64", "platforms:Windows"}) - -- Use AVX instead of AVX2 on Windows to prevent MSVC from emitting BMI2 instructions - -- MSVC with /arch:AVX2 generates BMI2 unconditionally, breaking Sandy/Ivy Bridge CPUs + filter({"architecture:x86_64"}) + -- AVX2 requires Haswell (2013+) or newer vectorextensions("AVX") - filter({"architecture:x86_64", "platforms:not Windows"}) - -- On non-Windows (Linux/Clang), AVX2 is safe as it can be combined with -mno-bmi2 - vectorextensions("AVX2") filter({}) end diff --git a/src/xenia/base/memory.cc b/src/xenia/base/memory.cc index 09d899ba6..8d9cb3dc6 100644 --- a/src/xenia/base/memory.cc +++ b/src/xenia/base/memory.cc @@ -308,6 +308,11 @@ void copy_and_swap_32_aligned(void* dest_ptr, const void* src_ptr, } } +// Enable AVX2 for this function even when building with -mavx +// The function has runtime detection to only use AVX2 on supported CPUs +#if defined(__GNUC__) || defined(__clang__) +__attribute__((target("avx2"))) +#endif void copy_and_swap_32_unaligned(void* dest_ptr, const void* src_ptr, size_t count) { auto dest = reinterpret_cast(dest_ptr);