diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index a39f7c91f..5a25518fe 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -95,9 +95,6 @@ class EmulatorWindow { void ToggleProfilesConfigDialog(); void SetHotkeysState(bool enabled) { disable_hotkeys_ = !enabled; } - // We need to store it somewhere so there will be no situation when there are - // multiple instances opened. - std::unique_ptr profile_creation_dialog_; // Types of button functions for hotkeys. enum class ButtonFunctions { diff --git a/src/xenia/app/profile_dialogs.cc b/src/xenia/app/profile_dialogs.cc index c38f259cd..2d623d906 100644 --- a/src/xenia/app/profile_dialogs.cc +++ b/src/xenia/app/profile_dialogs.cc @@ -11,29 +11,36 @@ #include #include "xenia/app/emulator_window.h" #include "xenia/base/system.h" +#include "xenia/kernel/util/shim_utils.h" namespace xe { +namespace kernel { +namespace xam { +extern bool xeDrawProfileContent(ui::ImGuiDrawer* imgui_drawer, + const uint64_t xuid, const uint8_t user_index, + const X_XAMACCOUNTINFO* account, + uint64_t* selected_xuid); +} +} // namespace kernel namespace app { void CreateProfileDialog::OnDraw(ImGuiIO& io) { + if (!has_opened_) { + ImGui::OpenPopup("Create Profile"); + has_opened_ = true; + } + auto profile_manager = emulator_window_->emulator() ->kernel_state() ->xam_state() ->profile_manager(); - const auto window_position = - ImVec2(GetIO().DisplaySize.x * 0.40f, GetIO().DisplaySize.y * 0.44f); - - ImGui::SetNextWindowPos(window_position, ImGuiCond_FirstUseEver); - ImGui::SetNextWindowBgAlpha(1.0f); - bool dialog_open = true; - if (!ImGui::Begin("Create Profile", &dialog_open, - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_HorizontalScrollbar)) { - ImGui::End(); - emulator_window_->profile_creation_dialog_.reset(); + if (!ImGui::BeginPopupModal("Create Profile", &dialog_open, + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_HorizontalScrollbar)) { + Close(); return; } @@ -43,33 +50,37 @@ void CreateProfileDialog::OnDraw(ImGuiIO& io) { } ImGui::TextUnformatted("Gamertag:"); - ImGui::InputText("##Gamertag", gamertag, sizeof(gamertag)); + ImGui::InputText("##Gamertag", gamertag_, sizeof(gamertag_)); - const std::string gamertag_string = std::string(gamertag); + const std::string gamertag_string = std::string(gamertag_); + bool valid = profile_manager->IsGamertagValid(gamertag_string); - if (profile_manager->IsGamertagValid(gamertag_string)) { - if (ImGui::Button("Create")) { - if (profile_manager->CreateProfile(gamertag_string, migration) && - migration) { - emulator_window_->emulator()->DataMigration(0xB13EBABEBABEBABE); - } - std::fill(std::begin(gamertag), std::end(gamertag), '\0'); - dialog_open = false; + ImGui::BeginDisabled(!valid); + if (ImGui::Button("Create")) { + bool autologin = (profile_manager->GetProfilesCount() == 0); + if (profile_manager->CreateProfile(gamertag_string, autologin, + migration_) && + migration_) { + emulator_window_->emulator()->DataMigration(0xB13EBABEBABEBABE); } - ImGui::SameLine(); + std::fill(std::begin(gamertag_), std::end(gamertag_), '\0'); + dialog_open = false; } + ImGui::EndDisabled(); + ImGui::SameLine(); if (ImGui::Button("Cancel")) { - std::fill(std::begin(gamertag), std::end(gamertag), '\0'); + std::fill(std::begin(gamertag_), std::end(gamertag_), '\0'); dialog_open = false; } if (!dialog_open) { - ImGui::End(); - emulator_window_->profile_creation_dialog_.reset(); + ImGui::CloseCurrentPopup(); + Close(); + ImGui::EndPopup(); return; } - ImGui::End(); + ImGui::EndPopup(); } void NoProfileDialog::OnDraw(ImGuiIO& io) { @@ -112,18 +123,14 @@ void NoProfileDialog::OnDraw(ImGuiIO& io) { emulator_window_->emulator()->content_root()); if (content_files.empty()) { - if (ImGui::Button("Create Profile") && - !emulator_window_->profile_creation_dialog_) { - emulator_window_->profile_creation_dialog_ = - std::make_unique( - emulator_window_->imgui_drawer(), emulator_window_); + if (ImGui::Button("Create Profile")) { + new CreateProfileDialog(emulator_window_->imgui_drawer(), + emulator_window_); } } else { - if (ImGui::Button("Create profile & migrate data") && - !emulator_window_->profile_creation_dialog_) { - emulator_window_->profile_creation_dialog_ = - std::make_unique( - emulator_window_->imgui_drawer(), emulator_window_, true); + if (ImGui::Button("Create profile & migrate data")) { + new CreateProfileDialog(emulator_window_->imgui_drawer(), + emulator_window_, true); } } @@ -183,7 +190,8 @@ void ProfileConfigDialog::OnDraw(ImGuiIO& io) { const uint8_t user_index = profile_manager->GetUserIndexAssignedToProfile(xuid); - if (!DrawProfileContent(xuid, user_index, &account)) { + if (!kernel::xam::xeDrawProfileContent(imgui_drawer(), xuid, user_index, + &account, &selected_xuid_)) { ImGui::PopID(); ImGui::End(); return; @@ -196,11 +204,8 @@ void ProfileConfigDialog::OnDraw(ImGuiIO& io) { ImGui::Spacing(); - if (ImGui::Button("Create Profile") && - !emulator_window_->profile_creation_dialog_) { - emulator_window_->profile_creation_dialog_ = - std::make_unique(emulator_window_->imgui_drawer(), - emulator_window_); + if (ImGui::Button("Create Profile")) { + new CreateProfileDialog(emulator_window_->imgui_drawer(), emulator_window_); } ImGui::End(); @@ -211,131 +216,5 @@ void ProfileConfigDialog::OnDraw(ImGuiIO& io) { } } -bool ProfileConfigDialog::DrawProfileContent(const uint64_t xuid, - const uint8_t user_index, - const X_XAMACCOUNTINFO* account) { - auto profile_manager = emulator_window_->emulator() - ->kernel_state() - ->xam_state() - ->profile_manager(); - - const float default_image_size = 75.0f; - auto position = ImGui::GetCursorPos(); - const float selectable_height = - ImGui::GetTextLineHeight() * - 5; // 3 is for amount of lines of text behind image/object. - const auto font = emulator_window_->imgui_drawer()->GetIO().Fonts->Fonts[0]; - - const auto text_size = font->CalcTextSizeA( - font->FontSize, FLT_MAX, -1.0f, - fmt::format("XUID: {:016X}\n", 0xB13EBABEBABEBABE).c_str()); - - const auto image_scale = selectable_height / default_image_size; - const auto image_size = ImVec2(default_image_size * image_scale, - default_image_size * image_scale); - // This includes 10% to include empty spaces between border and elements. - auto selectable_region_size = - ImVec2((image_size.x + text_size.x) * 1.10f, selectable_height); - - if (ImGui::Selectable("##Selectable", selected_xuid_ == xuid, - ImGuiSelectableFlags_SpanAllColumns, - selectable_region_size)) { - selected_xuid_ = xuid; - } - - if (ImGui::BeginPopupContextItem("Profile Menu")) { - if (user_index == static_cast(-1)) { - if (ImGui::MenuItem("Login")) { - profile_manager->Login(xuid); - } - - if (ImGui::BeginMenu("Login to slot:")) { - for (uint8_t i = 0; i < XUserMaxUserCount; i++) { - if (ImGui::MenuItem(fmt::format("slot {}", i).c_str())) { - profile_manager->Login(xuid, i); - } - } - ImGui::EndMenu(); - } - } else { - if (ImGui::MenuItem("Logout")) { - profile_manager->Logout(user_index); - } - } - - ImGui::MenuItem("Modify (unsupported)"); - ImGui::MenuItem("Show Achievements (unsupported)"); - - if (ImGui::MenuItem("Show Content Directory")) { - const auto path = profile_manager->GetProfileContentPath( - xuid, emulator_window_->emulator()->kernel_state()->title_id()); - - if (!std::filesystem::exists(path)) { - std::filesystem::create_directories(path); - } - - std::thread path_open(LaunchFileExplorer, path); - path_open.detach(); - } - - if (!emulator_window_->emulator()->is_title_open()) { - ImGui::Separator(); - if (ImGui::BeginMenu("Delete Profile")) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted( - fmt::format("You're about to delete profile: {} (XUID: {:016X}). " - "This will remove all data assigned to this profile " - "including savefiles. Are you sure?", - account->GetGamertagString(), xuid) - .c_str()); - ImGui::EndTooltip(); - - if (ImGui::MenuItem("Yes, delete it!")) { - profile_manager->DeleteProfile(xuid); - ImGui::EndMenu(); - ImGui::EndPopup(); - return false; - } - - ImGui::EndMenu(); - } - } - ImGui::EndPopup(); - } - - ImGui::SameLine(); - ImGui::SetCursorPos(position); - - // In the future it can be replaced with profile icon. - ImGui::Image(user_index < XUserMaxUserCount - ? imgui_drawer()->GetNotificationIcon(user_index) - : nullptr, - ImVec2(default_image_size * image_scale, - default_image_size * image_scale)); - - ImGui::SameLine(); - position = ImGui::GetCursorPos(); - ImGui::TextUnformatted( - fmt::format("User: {}\n", account->GetGamertagString()).c_str()); - - ImGui::SameLine(); - ImGui::SetCursorPos(position); - ImGui::SetCursorPosY(position.y + ImGui::GetTextLineHeight()); - ImGui::TextUnformatted(fmt::format("XUID: {:016X}\n", xuid).c_str()); - - ImGui::SameLine(); - ImGui::SetCursorPos(position); - ImGui::SetCursorPosY(position.y + 2 * ImGui::GetTextLineHeight()); - - if (user_index != static_cast(-1)) { - ImGui::TextUnformatted( - fmt::format("Assigned to slot: {}\n", user_index + 1).c_str()); - } else { - ImGui::TextUnformatted(fmt::format("Profile is not signed in").c_str()); - } - - return true; -} - } // namespace app } // namespace xe \ No newline at end of file diff --git a/src/xenia/app/profile_dialogs.h b/src/xenia/app/profile_dialogs.h index a8a9708b3..d9f4b3c0c 100644 --- a/src/xenia/app/profile_dialogs.h +++ b/src/xenia/app/profile_dialogs.h @@ -26,15 +26,16 @@ class CreateProfileDialog final : public ui::ImGuiDialog { bool with_migration = false) : ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window), - migration(with_migration) { - memset(gamertag, 0, sizeof(gamertag)); + migration_(with_migration) { + memset(gamertag_, 0, sizeof(gamertag_)); } protected: void OnDraw(ImGuiIO& io) override; - bool migration = false; - char gamertag[16] = ""; + bool has_opened_ = false; + bool migration_ = false; + char gamertag_[16] = ""; EmulatorWindow* emulator_window_; }; @@ -54,19 +55,13 @@ class ProfileConfigDialog final : public ui::ImGuiDialog { public: ProfileConfigDialog(ui::ImGuiDrawer* imgui_drawer, EmulatorWindow* emulator_window) - : ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) { - memset(gamertag, 0, sizeof(gamertag)); - } + : ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {} protected: void OnDraw(ImGuiIO& io) override; private: - bool DrawProfileContent(const uint64_t xuid, const uint8_t user_index, - const X_XAMACCOUNTINFO* account); - uint64_t selected_xuid_ = 0; - char gamertag[16] = ""; EmulatorWindow* emulator_window_; }; diff --git a/src/xenia/kernel/xam/profile_manager.cc b/src/xenia/kernel/xam/profile_manager.cc index 98ab18a12..35399e1f3 100644 --- a/src/xenia/kernel/xam/profile_manager.cc +++ b/src/xenia/kernel/xam/profile_manager.cc @@ -254,7 +254,8 @@ bool ProfileManager::DismountProfile(const uint64_t xuid) { fmt::format("{:016X}", xuid) + ':'); } -void ProfileManager::Login(const uint64_t xuid, const uint8_t user_index) { +void ProfileManager::Login(const uint64_t xuid, const uint8_t user_index, + bool notify) { if (logged_profiles_.size() >= 4 && user_index >= XUserMaxUserCount) { XELOGE( "Cannot login account with XUID: {:016X} due to lack of free slots " @@ -293,23 +294,39 @@ void ProfileManager::Login(const uint64_t xuid, const uint8_t user_index) { logged_profiles_[assigned_user_slot] = std::make_unique(xuid, &profile); - kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, - GetUsedUserSlots().to_ulong()); + if (notify) { + kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, + GetUsedUserSlots().to_ulong()); + } UpdateConfig(xuid, assigned_user_slot); } -void ProfileManager::Logout(const uint8_t user_index) { +void ProfileManager::Logout(const uint8_t user_index, bool notify) { auto profile = logged_profiles_.find(user_index); if (profile == logged_profiles_.cend()) { return; } DismountProfile(profile->second->xuid()); logged_profiles_.erase(profile); - kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, - GetUsedUserSlots().to_ulong()); + if (notify) { + kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, + GetUsedUserSlots().to_ulong()); + } UpdateConfig(0, user_index); } +void ProfileManager::LoginMultiple( + const std::map& profiles) { + int slots_mask = 0; + for (auto [slot, xuid] : profiles) { + Login(xuid, slot, false); + slots_mask |= (1 << slot); + } + + kernel_state_->BroadcastNotification(kXNotificationIDSystemSignInChanged, + slots_mask); +} + std::vector ProfileManager::FindProfiles() const { // Info: Profile directory name is also it's offline xuid std::vector profiles_xuids; @@ -410,7 +427,7 @@ std::filesystem::path ProfileManager::GetProfilePath( fmt::format("{:08X}", XContentType::kProfile) / xuid; } -bool ProfileManager::CreateProfile(const std::string gamertag, +bool ProfileManager::CreateProfile(const std::string gamertag, bool autologin, bool default_xuid) { const auto xuid = !default_xuid ? GenerateXuid() : 0xB13EBABEBABEBABE; @@ -423,7 +440,7 @@ bool ProfileManager::CreateProfile(const std::string gamertag, } const bool is_account_created = CreateAccount(xuid, gamertag); - if (is_account_created) { + if (is_account_created && autologin) { Login(xuid); } return is_account_created; diff --git a/src/xenia/kernel/xam/profile_manager.h b/src/xenia/kernel/xam/profile_manager.h index cb326b3a8..76f5a86f1 100644 --- a/src/xenia/kernel/xam/profile_manager.h +++ b/src/xenia/kernel/xam/profile_manager.h @@ -79,15 +79,18 @@ class ProfileManager { ~ProfileManager(); - bool CreateProfile(const std::string gamertag, bool default_xuid = false); + bool CreateProfile(const std::string gamertag, bool autologin, + bool default_xuid = false); // bool CreateProfile(const X_XAMACCOUNTINFO* account_info); bool DeleteProfile(const uint64_t xuid); bool MountProfile(const uint64_t xuid); bool DismountProfile(const uint64_t xuid); - void Login(const uint64_t xuid, const uint8_t user_index = -1); - void Logout(const uint8_t user_index); + void Login(const uint64_t xuid, const uint8_t user_index = -1, + bool notify = true); + void Logout(const uint8_t user_index, bool notify = true); + void LoginMultiple(const std::map& profiles); bool LoadAccount(const uint64_t xuid); void LoadAccounts(const std::vector profiles_xuids); diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index 4b57ad1e1..5175cea02 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -10,7 +10,9 @@ #include "third_party/imgui/imgui.h" #include "xenia/base/logging.h" #include "xenia/base/string_util.h" +#include "xenia/base/system.h" #include "xenia/emulator.h" +#include "xenia/hid/input_system.h" #include "xenia/kernel/kernel_flags.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" @@ -819,38 +821,415 @@ dword_result_t XamShowMarketplaceDownloadItemsUI_entry( } DECLARE_XAM_EXPORT1(XamShowMarketplaceDownloadItemsUI, kUI, kSketchy); -dword_result_t XamShowSigninUI_entry(dword_t users_needed, dword_t unk_mask) { - // XN_SYS_UI (on) - kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, 1); - // Mask values vary. Probably matching user types? Local/remote? - // Games seem to sit and loop until we trigger this notification: +bool xeDrawProfileContent(ui::ImGuiDrawer* imgui_drawer, const uint64_t xuid, + const uint8_t user_index, + const X_XAMACCOUNTINFO* account, + uint64_t* selected_xuid) { + auto profile_manager = kernel_state()->xam_state()->profile_manager(); - auto run = [users_needed]() -> void { - uint32_t user_mask = 0; - uint32_t active_users = 0; + const float default_image_size = 75.0f; + auto position = ImGui::GetCursorPos(); + const float selectable_height = + ImGui::GetTextLineHeight() * + 5; // 3 is for amount of lines of text behind image/object. + const auto font = imgui_drawer->GetIO().Fonts->Fonts[0]; - for (uint32_t i = 0; i < XUserMaxUserCount; i++) { - if (kernel_state()->xam_state()->IsUserSignedIn(i)) { - user_mask |= (1 << i); - active_users++; - if (active_users >= users_needed) break; - } + const auto text_size = font->CalcTextSizeA( + font->FontSize, FLT_MAX, -1.0f, + fmt::format("XUID: {:016X}\n", 0xB13EBABEBABEBABE).c_str()); + + const auto image_scale = selectable_height / default_image_size; + const auto image_size = ImVec2(default_image_size * image_scale, + default_image_size * image_scale); + + if (xuid && selected_xuid) { + // This includes 10% to include empty spaces between border and elements. + auto selectable_region_size = + ImVec2((image_size.x + text_size.x) * 1.10f, selectable_height); + + if (ImGui::Selectable("##Selectable", *selected_xuid == xuid, + ImGuiSelectableFlags_SpanAllColumns, + selectable_region_size)) { + *selected_xuid = xuid; } - std::this_thread::sleep_for(std::chrono::milliseconds(150)); - // XN_SYS_SIGNINCHANGED (players) - kernel_state()->BroadcastNotification(kXNotificationIDSystemSignInChanged, - user_mask); - // XN_SYS_UI (off) - kernel_state()->BroadcastNotification(kXNotificationIDSystemUI, 0); - }; + if (ImGui::BeginPopupContextItem("Profile Menu")) { + if (user_index == static_cast(-1)) { + if (ImGui::MenuItem("Login")) { + profile_manager->Login(xuid); + } - std::thread thread(run); - thread.detach(); + if (ImGui::BeginMenu("Login to slot:")) { + for (uint8_t i = 0; i < XUserMaxUserCount; i++) { + if (ImGui::MenuItem(fmt::format("slot {}", i).c_str())) { + profile_manager->Login(xuid, i); + } + } + ImGui::EndMenu(); + } + } else { + if (ImGui::MenuItem("Logout")) { + profile_manager->Logout(user_index); + } + } - return X_ERROR_SUCCESS; + ImGui::MenuItem("Modify (unsupported)"); + ImGui::MenuItem("Show Achievements (unsupported)"); + + if (ImGui::MenuItem("Show Content Directory")) { + const auto path = profile_manager->GetProfileContentPath( + xuid, kernel_state()->title_id()); + + if (!std::filesystem::exists(path)) { + std::filesystem::create_directories(path); + } + + std::thread path_open(LaunchFileExplorer, path); + path_open.detach(); + } + + if (!kernel_state()->emulator()->is_title_open()) { + ImGui::Separator(); + if (ImGui::BeginMenu("Delete Profile")) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted( + fmt::format("You're about to delete profile: {} (XUID: {:016X}). " + "This will remove all data assigned to this profile " + "including savefiles. Are you sure?", + account->GetGamertagString(), xuid) + .c_str()); + ImGui::EndTooltip(); + + if (ImGui::MenuItem("Yes, delete it!")) { + profile_manager->DeleteProfile(xuid); + ImGui::EndMenu(); + ImGui::EndPopup(); + return false; + } + + ImGui::EndMenu(); + } + } + ImGui::EndPopup(); + } + } + + ImGui::SameLine(); + ImGui::SetCursorPos(position); + + // In the future it can be replaced with profile icon. + ImGui::Image(user_index < XUserMaxUserCount + ? imgui_drawer->GetNotificationIcon(user_index) + : nullptr, + ImVec2(default_image_size * image_scale, + default_image_size * image_scale)); + + ImGui::SameLine(); + position = ImGui::GetCursorPos(); + ImGui::TextUnformatted( + fmt::format("User: {}\n", account->GetGamertagString()).c_str()); + + ImGui::SameLine(); + ImGui::SetCursorPos(position); + ImGui::SetCursorPosY(position.y + ImGui::GetTextLineHeight()); + ImGui::TextUnformatted(fmt::format("XUID: {:016X}\n", xuid).c_str()); + + ImGui::SameLine(); + ImGui::SetCursorPos(position); + ImGui::SetCursorPosY(position.y + 2 * ImGui::GetTextLineHeight()); + + if (user_index != static_cast(-1)) { + ImGui::TextUnformatted( + fmt::format("Assigned to slot: {}\n", user_index + 1).c_str()); + } else { + ImGui::TextUnformatted(fmt::format("Profile is not signed in").c_str()); + } + + return true; } -DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub); + +class SigninDialog : public XamDialog { + public: + SigninDialog(xe::ui::ImGuiDrawer* imgui_drawer, uint32_t users_needed) + : XamDialog(imgui_drawer), + users_needed_(users_needed), + title_("Sign In") { + last_user_ = kernel_state()->emulator()->input_system()->GetLastUsedSlot(); + + for (uint8_t slot = 0; slot < XUserMaxUserCount; slot++) { + std::string name = fmt::format("Slot {:d}", slot + 1); + slot_data_.push_back({slot, name}); + } + } + + virtual ~SigninDialog() {} + + void OnDraw(ImGuiIO& io) override { + bool first_draw = false; + if (!has_opened_) { + ImGui::OpenPopup(title_.c_str()); + has_opened_ = true; + first_draw = true; + ReloadProfiles(true); + } + if (ImGui::BeginPopupModal(title_.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + auto profile_manager = kernel_state()->xam_state()->profile_manager(); + auto profiles = profile_manager->GetProfiles(); + + for (uint32_t i = 0; i < users_needed_; i++) { + ImGui::BeginGroup(); + + std::vector combo_items; + int items_count = 0; + int current_item = 0; + + // Fill slot list. + std::vector slots; + slots.push_back(0xFF); + combo_items.push_back("---"); + for (auto& elem : slot_data_) { + // Select the slot or skip it if it's already used. + bool already_taken = false; + for (uint32_t j = 0; j < users_needed_; j++) { + if (chosen_slots_[j] == elem.first) { + if (i == j) { + current_item = static_cast(combo_items.size()); + } else { + already_taken = true; + } + break; + } + } + + if (already_taken) { + continue; + } + + slots.push_back(elem.first); + combo_items.push_back(elem.second.c_str()); + } + items_count = static_cast(combo_items.size()); + + ImGui::BeginDisabled(users_needed_ == 1); + ImGui::Combo(fmt::format("##Slot{:d}", i).c_str(), ¤t_item, + combo_items.data(), items_count); + chosen_slots_[i] = slots[current_item]; + ImGui::EndDisabled(); + ImGui::Spacing(); + + combo_items.clear(); + current_item = 0; + + // Fill profile list. + std::vector xuids; + xuids.push_back(0); + combo_items.push_back("---"); + if (chosen_slots_[i] != 0xFF) { + for (auto& elem : profile_data_) { + // Select the profile or skip it if it's already used. + bool already_taken = false; + for (uint32_t j = 0; j < users_needed_; j++) { + if (chosen_xuids_[j] == elem.first) { + if (i == j) { + current_item = static_cast(combo_items.size()); + } else { + already_taken = true; + } + break; + } + } + + if (already_taken) { + continue; + } + + xuids.push_back(elem.first); + combo_items.push_back(elem.second.c_str()); + } + } + items_count = static_cast(combo_items.size()); + + ImGui::BeginDisabled(chosen_slots_[i] == 0xFF); + ImGui::Combo(fmt::format("##Profile{:d}", i).c_str(), ¤t_item, + combo_items.data(), items_count); + chosen_xuids_[i] = xuids[current_item]; + ImGui::EndDisabled(); + ImGui::Spacing(); + + // Draw profile badge. + uint8_t slot = chosen_slots_[i]; + uint64_t xuid = chosen_xuids_[i]; + + if (slot == 0xFF || xuid == 0 || profiles->count(xuid) == 0) { + float ypos = ImGui::GetCursorPosY(); + ImGui::SetCursorPosY(ypos + ImGui::GetTextLineHeight() * 5); + } else { + const X_XAMACCOUNTINFO* account = &profiles->at(xuid); + xeDrawProfileContent(imgui_drawer(), xuid, slot, account, nullptr); + } + + ImGui::EndGroup(); + if (i != (users_needed_ - 1) && (i == 0 || i == 2)) { + ImGui::SameLine(); + } + } + + ImGui::Spacing(); + + if (ImGui::Button("Create Profile")) { + creating_profile_ = true; + ImGui::OpenPopup("Create Profile"); + first_draw = true; + } + ImGui::Spacing(); + + if (creating_profile_) { + if (ImGui::BeginPopupModal("Create Profile", nullptr, + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_HorizontalScrollbar)) { + if (first_draw) { + ImGui::SetKeyboardFocusHere(); + } + + ImGui::TextUnformatted("Gamertag:"); + ImGui::InputText("##Gamertag", gamertag_, sizeof(gamertag_)); + + const std::string gamertag_string = gamertag_; + bool valid = profile_manager->IsGamertagValid(gamertag_string); + + ImGui::BeginDisabled(!valid); + if (ImGui::Button("Create")) { + profile_manager->CreateProfile(gamertag_string, false); + std::fill(std::begin(gamertag_), std::end(gamertag_), '\0'); + ImGui::CloseCurrentPopup(); + creating_profile_ = false; + ReloadProfiles(false); + } + ImGui::EndDisabled(); + ImGui::SameLine(); + + if (ImGui::Button("Cancel")) { + std::fill(std::begin(gamertag_), std::end(gamertag_), '\0'); + ImGui::CloseCurrentPopup(); + creating_profile_ = false; + } + + ImGui::EndPopup(); + } else { + creating_profile_ = false; + } + } + + if (ImGui::Button("OK")) { + std::map profile_map; + for (uint32_t i = 0; i < users_needed_; i++) { + uint8_t slot = chosen_slots_[i]; + uint64_t xuid = chosen_xuids_[i]; + if (slot != 0xFF && xuid != 0) { + profile_map[slot] = xuid; + } + } + profile_manager->LoginMultiple(profile_map); + + ImGui::CloseCurrentPopup(); + Close(); + } + ImGui::SameLine(); + + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + Close(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::EndPopup(); + } else { + Close(); + } + } + + void ReloadProfiles(bool first_draw) { + auto profile_manager = kernel_state()->xam_state()->profile_manager(); + auto profiles = profile_manager->GetProfiles(); + + profile_data_.clear(); + for (auto& [xuid, account] : *profiles) { + profile_data_.push_back({xuid, account.GetGamertagString()}); + } + + if (first_draw) { + // If only one user is requested, request last used controller to sign in. + if (users_needed_ == 1) { + chosen_slots_[0] = last_user_; + } else { + for (uint32_t i = 0; i < users_needed_; i++) { + // TODO: Not sure about this, needs testing on real hardware. + chosen_slots_[i] = i; + } + } + + // Default profile selection to profile that is already signed in. + for (auto& elem : profile_data_) { + uint64_t xuid = elem.first; + uint8_t slot = profile_manager->GetUserIndexAssignedToProfile(xuid); + for (uint32_t j = 0; j < users_needed_; j++) { + if (chosen_slots_[j] != 0xFF && slot == chosen_slots_[j]) { + chosen_xuids_[j] = xuid; + } + } + } + } + } + + private: + bool has_opened_ = false; + std::string title_; + uint32_t users_needed_ = 1; + uint32_t last_user_ = 0; + + std::vector> slot_data_; + std::vector> profile_data_; + uint8_t chosen_slots_[XUserMaxUserCount] = {}; + uint64_t chosen_xuids_[XUserMaxUserCount] = {}; + + bool creating_profile_ = false; + char gamertag_[16] = ""; +}; + +dword_result_t XamShowSigninUI_entry(dword_t users_needed, dword_t unk_mask) { + // Mask values vary. Probably matching user types? Local/remote? + // Games seem to sit and loop until we trigger sign in notification. + if (users_needed != 1 && users_needed != 2 && users_needed != 4) { + return X_ERROR_INVALID_PARAMETER; + } + + if (cvars::headless) { + return xeXamDispatchHeadlessAsync([users_needed]() { + std::map xuids; + + for (uint32_t i = 0; i < XUserMaxUserCount; i++) { + UserProfile* profile = kernel_state()->xam_state()->GetUserProfile(i); + if (profile) { + xuids[i] = profile->xuid(); + if (xuids.size() >= users_needed) break; + } + } + + kernel_state()->xam_state()->profile_manager()->LoginMultiple(xuids); + }); + } + + auto close = [](SigninDialog* dialog) -> void {}; + + const Emulator* emulator = kernel_state()->emulator(); + ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); + return xeXamDispatchDialogAsync( + new SigninDialog(imgui_drawer, users_needed), close); +} +DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kImplemented); } // namespace xam } // namespace kernel