[UI] Added modify profile UI
- Changed default icons size from 75x75 to 64x64 to match console icons size - Added png_utils - Removed all dialogs from xam_ui.cc into separate entities under xam/ui - Added option to return INT32 and WSTRING type settings to host - Added multiple enums related to user settings - Added code to handle changing user pfp - Fixed bug with system app being added to GPD played list - Changed logic in: XamUserGetUserFlagsFromXUID - Implemented: XamUserIsOnlineEnabled - Implemented: XamUserGetMembershipTier - Implemented: XamUserGetMembershipTierFromXUID - Implemented: XamUserGetUserTenure - Partially Implemented: XamUserGetSubscriptionType
This commit is contained in:
committed by
Radosław Gliński
parent
270e88a7a7
commit
e601b5ab87
78
src/xenia/kernel/xam/ui/create_profile_ui.cc
Normal file
78
src/xenia/kernel/xam/ui/create_profile_ui.cc
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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/kernel/xam/ui/create_profile_ui.h"
|
||||
#include "xenia/emulator.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
void CreateProfileUI::OnDraw(ImGuiIO& io) {
|
||||
if (!has_opened_) {
|
||||
ImGui::OpenPopup("Create Profile");
|
||||
has_opened_ = true;
|
||||
}
|
||||
|
||||
auto profile_manager =
|
||||
emulator_->kernel_state()->xam_state()->profile_manager();
|
||||
|
||||
bool dialog_open = true;
|
||||
if (!ImGui::BeginPopupModal("Create Profile", &dialog_open,
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
!ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)) {
|
||||
ImGui::SetKeyboardFocusHere(0);
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Gamertag:");
|
||||
ImGui::InputText("##Gamertag", gamertag_, sizeof(gamertag_));
|
||||
|
||||
const std::string gamertag_string = std::string(gamertag_);
|
||||
bool valid = profile_manager->IsGamertagValid(gamertag_string);
|
||||
|
||||
ImGui::BeginDisabled(!valid);
|
||||
if (ImGui::Button("Create")) {
|
||||
bool autologin = (profile_manager->GetAccountCount() == 0);
|
||||
if (profile_manager->CreateProfile(gamertag_string, autologin,
|
||||
migration_) &&
|
||||
migration_) {
|
||||
emulator_->DataMigration(0xB13EBABEBABEBABE);
|
||||
}
|
||||
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');
|
||||
dialog_open = false;
|
||||
}
|
||||
|
||||
if (!dialog_open) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
ImGui::EndPopup();
|
||||
return;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
44
src/xenia/kernel/xam/ui/create_profile_ui.h
Normal file
44
src/xenia/kernel/xam/ui/create_profile_ui.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_XAM_UI_CREATE_PROFILE_UI_H_
|
||||
#define XENIA_KERNEL_XAM_UI_CREATE_PROFILE_UI_H_
|
||||
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
class CreateProfileUI final : public XamDialog {
|
||||
public:
|
||||
CreateProfileUI(xe::ui::ImGuiDrawer* imgui_drawer, Emulator* emulator,
|
||||
bool with_migration = false)
|
||||
: XamDialog(imgui_drawer),
|
||||
emulator_(emulator),
|
||||
migration_(with_migration) {
|
||||
memset(gamertag_, 0, sizeof(gamertag_));
|
||||
}
|
||||
|
||||
private:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
bool has_opened_ = false;
|
||||
bool migration_ = false;
|
||||
char gamertag_[16] = "";
|
||||
Emulator* emulator_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
229
src/xenia/kernel/xam/ui/game_achievements_ui.cc
Normal file
229
src/xenia/kernel/xam/ui/game_achievements_ui.cc
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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/kernel/xam/ui/game_achievements_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
GameAchievementsUI::GameAchievementsUI(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
const ImVec2 drawing_position,
|
||||
const TitleInfo* title_info,
|
||||
const UserProfile* profile)
|
||||
: XamDialog(imgui_drawer),
|
||||
drawing_position_(drawing_position),
|
||||
title_info_(*title_info),
|
||||
profile_(profile),
|
||||
window_id_(GetWindowId()) {
|
||||
LoadAchievementsData();
|
||||
}
|
||||
|
||||
GameAchievementsUI::~GameAchievementsUI() {
|
||||
for (auto& entry : achievements_icons_) {
|
||||
entry.second.release();
|
||||
}
|
||||
}
|
||||
|
||||
bool GameAchievementsUI::LoadAchievementsData() {
|
||||
achievements_info_ =
|
||||
kernel_state()->xam_state()->achievement_manager()->GetTitleAchievements(
|
||||
profile_->xuid(), title_info_.id);
|
||||
|
||||
if (achievements_info_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xe::ui::IconsData data;
|
||||
|
||||
for (const Achievement& entry : achievements_info_) {
|
||||
const auto icon =
|
||||
kernel_state()->xam_state()->achievement_manager()->GetAchievementIcon(
|
||||
profile_->xuid(), title_info_.id, entry.achievement_id);
|
||||
|
||||
data.insert({entry.image_id, icon});
|
||||
}
|
||||
|
||||
achievements_icons_ = imgui_drawer()->LoadIcons(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GameAchievementsUI::GetAchievementTitle(
|
||||
const Achievement& achievement_entry) const {
|
||||
std::string title = "Secret trophy";
|
||||
|
||||
if (achievement_entry.IsUnlocked() || show_locked_info_ ||
|
||||
achievement_entry.flags &
|
||||
static_cast<uint32_t>(AchievementFlags::kShowUnachieved)) {
|
||||
title = xe::to_utf8(achievement_entry.achievement_name);
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
std::string GameAchievementsUI::GetAchievementDescription(
|
||||
const Achievement& achievement_entry) const {
|
||||
std::string description = "Hidden description";
|
||||
|
||||
if (achievement_entry.flags &
|
||||
static_cast<uint32_t>(AchievementFlags::kShowUnachieved)) {
|
||||
description = xe::to_utf8(achievement_entry.locked_description);
|
||||
}
|
||||
|
||||
if (achievement_entry.IsUnlocked() || show_locked_info_) {
|
||||
description = xe::to_utf8(achievement_entry.unlocked_description);
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
xe::ui::ImmediateTexture* GameAchievementsUI::GetIcon(
|
||||
const Achievement& achievement_entry) const {
|
||||
if (!achievement_entry.IsUnlocked() && !show_locked_info_) {
|
||||
return imgui_drawer()->GetLockedAchievementIcon();
|
||||
}
|
||||
|
||||
if (achievements_icons_.count(achievement_entry.image_id)) {
|
||||
return achievements_icons_.at(achievement_entry.image_id).get();
|
||||
}
|
||||
|
||||
if (achievement_entry.IsUnlocked()) {
|
||||
return nullptr;
|
||||
}
|
||||
return imgui_drawer()->GetLockedAchievementIcon();
|
||||
}
|
||||
|
||||
std::string GameAchievementsUI::GetUnlockedTime(
|
||||
const Achievement& achievement_entry) const {
|
||||
if (achievement_entry.IsUnlockedOnline()) {
|
||||
const auto unlock_time = chrono::WinSystemClock::to_local(
|
||||
achievement_entry.unlock_time.to_time_point());
|
||||
|
||||
return fmt::format(
|
||||
"Unlocked: {:%Y-%m-%d %H:%M}",
|
||||
std::chrono::system_clock::time_point(unlock_time.time_since_epoch()));
|
||||
}
|
||||
|
||||
if (achievement_entry.unlock_time.is_valid()) {
|
||||
const auto unlock_time = chrono::WinSystemClock::to_local(
|
||||
achievement_entry.unlock_time.to_time_point());
|
||||
|
||||
return fmt::format(
|
||||
"Unlocked: Offline ({:%Y-%m-%d %H:%M})",
|
||||
std::chrono::system_clock::time_point(unlock_time.time_since_epoch()));
|
||||
}
|
||||
return fmt::format("Unlocked: Offline");
|
||||
}
|
||||
|
||||
void GameAchievementsUI::DrawTitleAchievementInfo(
|
||||
ImGuiIO& io, const Achievement& achievement_entry) const {
|
||||
const auto start_drawing_pos = ImGui::GetCursorPos();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
|
||||
const auto icon = GetIcon(achievement_entry);
|
||||
if (icon) {
|
||||
ImGui::Image(reinterpret_cast<ImTextureID>(GetIcon(achievement_entry)),
|
||||
xe::ui::default_image_icon_size);
|
||||
} else {
|
||||
ImGui::Dummy(xe::ui::default_image_icon_size);
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::PushFont(imgui_drawer()->GetTitleFont());
|
||||
const auto primary_line_height = ImGui::GetTextLineHeight();
|
||||
ImGui::Text("%s", GetAchievementTitle(achievement_entry).c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushTextWrapPos(ImGui::GetMainViewport()->Size.x * 0.5f);
|
||||
ImGui::TextWrapped("%s",
|
||||
GetAchievementDescription(achievement_entry).c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
ImGui::SetCursorPosY(start_drawing_pos.y + xe::ui::default_image_icon_size.x -
|
||||
ImGui::GetTextLineHeight());
|
||||
|
||||
if (achievement_entry.IsUnlocked()) {
|
||||
ImGui::Text("%s", GetUnlockedTime(achievement_entry).c_str());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// TODO(Gliniak): There is no easy way to align text to middle, so I have to
|
||||
// do it manually.
|
||||
const float achievement_row_middle_alignment =
|
||||
((xe::ui::default_image_icon_size.x / 2.f) -
|
||||
ImGui::GetTextLineHeight() / 2.f) *
|
||||
0.85f;
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() +
|
||||
achievement_row_middle_alignment);
|
||||
ImGui::PushFont(imgui_drawer()->GetTitleFont());
|
||||
ImGui::TextUnformatted(
|
||||
fmt::format("{} G", achievement_entry.gamerscore).c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
void GameAchievementsUI::OnDraw(ImGuiIO& io) {
|
||||
ImGui::SetNextWindowPos(drawing_position_, ImGuiCond_FirstUseEver);
|
||||
|
||||
const auto xenia_window_size = ImGui::GetMainViewport()->Size;
|
||||
|
||||
ImGui::SetNextWindowSizeConstraints(
|
||||
ImVec2(xenia_window_size.x * 0.2f, xenia_window_size.y * 0.3f),
|
||||
ImVec2(xenia_window_size.x * 0.6f, xenia_window_size.y * 0.8f));
|
||||
ImGui::SetNextWindowBgAlpha(0.8f);
|
||||
|
||||
bool dialog_open = true;
|
||||
|
||||
std::string title_name = xe::to_utf8(title_info_.title_name);
|
||||
title_name.erase(std::remove(title_name.begin(), title_name.end(), '\0'),
|
||||
title_name.end());
|
||||
|
||||
const std::string window_name =
|
||||
fmt::format("{} Achievements###{}", title_name, window_id_);
|
||||
if (!ImGui::Begin(window_name.c_str(), &dialog_open,
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||
Close();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Show locked achievements information", &show_locked_info_);
|
||||
ImGui::Separator();
|
||||
|
||||
if (achievements_info_.empty()) {
|
||||
ImGui::TextUnformatted(fmt::format("No achievements data!").c_str());
|
||||
} else {
|
||||
if (ImGui::BeginTable("", 3, ImGuiTableFlags_BordersInnerH)) {
|
||||
for (const auto& entry : achievements_info_) {
|
||||
ImGui::TableNextRow(0, xe::ui::default_image_icon_size.y);
|
||||
DrawTitleAchievementInfo(io, entry);
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
if (!dialog_open) {
|
||||
Close();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
63
src/xenia/kernel/xam/ui/game_achievements_ui.h
Normal file
63
src/xenia/kernel/xam/ui/game_achievements_ui.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_XAM_UI_GAME_ACHIEVEMENTS_UI_H_
|
||||
#define XENIA_KERNEL_XAM_UI_GAME_ACHIEVEMENTS_UI_H_
|
||||
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
class GameAchievementsUI final : public XamDialog {
|
||||
public:
|
||||
GameAchievementsUI(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
const ImVec2 drawing_position, const TitleInfo* title_info,
|
||||
const UserProfile* profile);
|
||||
|
||||
private:
|
||||
~GameAchievementsUI();
|
||||
bool LoadAchievementsData();
|
||||
|
||||
std::string GetAchievementTitle(const Achievement& achievement_entry) const;
|
||||
|
||||
std::string GetAchievementDescription(
|
||||
const Achievement& achievement_entry) const;
|
||||
|
||||
xe::ui::ImmediateTexture* GetIcon(const Achievement& achievement_entry) const;
|
||||
|
||||
std::string GetUnlockedTime(const Achievement& achievement_entry) const;
|
||||
|
||||
void DrawTitleAchievementInfo(ImGuiIO& io,
|
||||
const Achievement& achievement_entry) const;
|
||||
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
private:
|
||||
bool show_locked_info_ = false;
|
||||
|
||||
uint64_t window_id_;
|
||||
const ImVec2 drawing_position_ = {};
|
||||
|
||||
const TitleInfo title_info_;
|
||||
const UserProfile* profile_;
|
||||
|
||||
std::vector<Achievement> achievements_info_;
|
||||
std::map<uint32_t, std::unique_ptr<xe::ui::ImmediateTexture>>
|
||||
achievements_icons_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
667
src/xenia/kernel/xam/ui/gamercard_ui.cc
Normal file
667
src/xenia/kernel/xam/ui/gamercard_ui.cc
Normal file
@@ -0,0 +1,667 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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/kernel/xam/ui/gamercard_ui.h"
|
||||
#include "xenia/base/png_utils.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
constexpr float leftSideTextObjectAlignment = 100.f;
|
||||
constexpr float rightSideTextObjectAlignment = 140.f;
|
||||
// Because all these ENUM->NAME conversions will be used only here there is no
|
||||
// reason to store them somewhere globally available.
|
||||
static constexpr const char* XLanguageName[] = {nullptr,
|
||||
"English",
|
||||
"Japanese",
|
||||
"German",
|
||||
"French",
|
||||
"Spanish",
|
||||
"Italian",
|
||||
"Korean",
|
||||
"Traditional Chinese",
|
||||
"Portuguese",
|
||||
"Simplified Chinese",
|
||||
"Polish",
|
||||
"Russian"};
|
||||
|
||||
static constexpr const char* XOnlineCountry[] = {nullptr,
|
||||
"United Arab Emirates",
|
||||
"Albania",
|
||||
"Armenia",
|
||||
"Argentina",
|
||||
"Austria",
|
||||
"Australia",
|
||||
"Azerbaijan",
|
||||
"Belgium",
|
||||
"Bulgaria",
|
||||
"Bahrain",
|
||||
"Brunei Darussalam",
|
||||
"Bolivia",
|
||||
"Brazil",
|
||||
"Belarus",
|
||||
"Belize",
|
||||
"Canada",
|
||||
nullptr,
|
||||
"Switzerland",
|
||||
"Chile",
|
||||
"China",
|
||||
"Colombia",
|
||||
"Costa Rica",
|
||||
"Czech Republic",
|
||||
"Germany",
|
||||
"Denmark",
|
||||
"Dominican Republic",
|
||||
"Algeria",
|
||||
"Ecuador",
|
||||
"Estonia",
|
||||
"Egypt",
|
||||
"Spain",
|
||||
"Finland",
|
||||
"Faroe Islands",
|
||||
"France",
|
||||
"Great Britain",
|
||||
"Georgia",
|
||||
"Greece",
|
||||
"Guatemala",
|
||||
"Hong Kong",
|
||||
"Honduras",
|
||||
"Croatia",
|
||||
"Hungary",
|
||||
"Indonesia",
|
||||
"Ireland",
|
||||
"Israel",
|
||||
"India",
|
||||
"Iraq",
|
||||
"Iran",
|
||||
"Iceland",
|
||||
"Italy",
|
||||
"Jamaica",
|
||||
"Jordan",
|
||||
"Japan",
|
||||
"Kenya",
|
||||
"Kyrgyzstan",
|
||||
"Korea",
|
||||
"Kuwait",
|
||||
"Kazakhstan",
|
||||
"Lebanon",
|
||||
"Liechtenstein",
|
||||
"Lithuania",
|
||||
"Luxembourg",
|
||||
"Latvia",
|
||||
"Libya",
|
||||
"Morocco",
|
||||
"Monaco",
|
||||
"Macedonia",
|
||||
"Mongolia",
|
||||
"Macau",
|
||||
"Maldives",
|
||||
"Mexico",
|
||||
"Malaysia",
|
||||
"Nicaragua",
|
||||
"Netherlands",
|
||||
"Norway",
|
||||
"New Zealand",
|
||||
"Oman",
|
||||
"Panama",
|
||||
"Peru",
|
||||
"Philippines",
|
||||
"Pakistan",
|
||||
"Poland",
|
||||
"Puerto Rico",
|
||||
"Portugal",
|
||||
"Paraguay",
|
||||
"Qatar",
|
||||
"Romania",
|
||||
"Russian Federation",
|
||||
"Saudi Arabia",
|
||||
"Sweden",
|
||||
"Singapore",
|
||||
"Slovenia",
|
||||
"Slovak Republic",
|
||||
nullptr,
|
||||
"El Salvador",
|
||||
"Syria",
|
||||
"Thailand",
|
||||
"Tunisia",
|
||||
"Turkey",
|
||||
"Trinidad And Tobago",
|
||||
"Taiwan",
|
||||
"Ukraine",
|
||||
"United States",
|
||||
"Uruguay",
|
||||
"Uzbekistan",
|
||||
"Venezuela",
|
||||
"Viet Nam",
|
||||
"Yemen",
|
||||
"South Africa",
|
||||
"Zimbabwe"};
|
||||
|
||||
static constexpr const char* AccountSubscription[] = {
|
||||
"None", nullptr, nullptr, "Silver", nullptr,
|
||||
nullptr, "Gold", nullptr, nullptr, "Family"};
|
||||
|
||||
static constexpr const char* XGamerzoneName[] = {"None", "Recreation", "Pro",
|
||||
"Family", "Underground"};
|
||||
|
||||
static constexpr const char* PreferredColorOptions[] = {
|
||||
"None", "Black", "White", "Yellow", "Orange", "Pink",
|
||||
"Red", "Purple", "Blue", "Green", "Brown", "Silver"};
|
||||
|
||||
static constexpr const char* ControllerVibrationOptions[] = {"Off", nullptr,
|
||||
nullptr, "On"};
|
||||
|
||||
static constexpr const char* ControlSensitivityOptions[] = {"Medium", "Low",
|
||||
"High"};
|
||||
|
||||
static constexpr const char* GamerDifficultyOptions[] = {"Normal", "Easy",
|
||||
"Hard"};
|
||||
|
||||
static constexpr const char* AutoAimOptions[] = {"Off", "On"};
|
||||
static constexpr const char* AutoCenterOptions[] = {"Off", "On"};
|
||||
static constexpr const char* MovementControlOptions[] = {"Left Thumbstick",
|
||||
"Right Thumbstick"};
|
||||
static constexpr const char* YAxisInversionOptions[] = {"Off", "On"};
|
||||
static constexpr const char* TransmissionOptions[] = {"Automatic", "Manual"};
|
||||
static constexpr const char* CameraLocationOptions[] = {"Behind", "In Front",
|
||||
"Inside"};
|
||||
static constexpr const char* BrakeControlOptions[] = {"Trigger", "Button"};
|
||||
static constexpr const char* AcceleratorControlOptions[] = {"Trigger",
|
||||
"Button"};
|
||||
|
||||
constexpr std::array<UserSettingId, 19> UserSettingsToLoad = {
|
||||
UserSettingId::XPROFILE_GAMER_TYPE,
|
||||
UserSettingId::XPROFILE_GAMER_YAXIS_INVERSION,
|
||||
UserSettingId::XPROFILE_OPTION_CONTROLLER_VIBRATION,
|
||||
UserSettingId::XPROFILE_GAMERCARD_ZONE,
|
||||
UserSettingId::XPROFILE_GAMERCARD_REGION,
|
||||
UserSettingId::XPROFILE_GAMER_DIFFICULTY,
|
||||
UserSettingId::XPROFILE_GAMER_CONTROL_SENSITIVITY,
|
||||
UserSettingId::XPROFILE_GAMER_PREFERRED_COLOR_FIRST,
|
||||
UserSettingId::XPROFILE_GAMER_PREFERRED_COLOR_SECOND,
|
||||
UserSettingId::XPROFILE_GAMER_ACTION_AUTO_AIM,
|
||||
UserSettingId::XPROFILE_GAMER_ACTION_AUTO_CENTER,
|
||||
UserSettingId::XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL,
|
||||
UserSettingId::XPROFILE_GAMER_RACE_TRANSMISSION,
|
||||
UserSettingId::XPROFILE_GAMER_RACE_CAMERA_LOCATION,
|
||||
UserSettingId::XPROFILE_GAMER_RACE_BRAKE_CONTROL,
|
||||
UserSettingId::XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL,
|
||||
UserSettingId::XPROFILE_GAMERCARD_USER_NAME,
|
||||
UserSettingId::XPROFILE_GAMERCARD_USER_BIO,
|
||||
UserSettingId::XPROFILE_GAMERCARD_MOTTO};
|
||||
|
||||
GamercardUI::GamercardUI(xe::ui::Window* window,
|
||||
xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
KernelState* kernel_state, uint64_t xuid)
|
||||
: XamDialog(imgui_drawer),
|
||||
window_(window),
|
||||
kernel_state_(kernel_state),
|
||||
xuid_(xuid),
|
||||
is_signed_in_(kernel_state->xam_state()->GetUserProfile(xuid) !=
|
||||
nullptr) {
|
||||
LoadGamercardInfo();
|
||||
}
|
||||
|
||||
void GamercardUI::LoadStringSetting(UserSettingId setting_id, char* buffer) {
|
||||
const auto entry = xe::to_utf8(std::get<std::u16string>(
|
||||
gamercardOriginalValues_.gpd_settings[setting_id]));
|
||||
|
||||
std::memcpy(buffer, entry.c_str(), entry.size());
|
||||
}
|
||||
|
||||
void GamercardUI::LoadGamercardInfo() {
|
||||
const auto account_data =
|
||||
kernel_state()->xam_state()->profile_manager()->GetAccount(xuid_);
|
||||
|
||||
std::memcpy(gamercardOriginalValues_.gamertag,
|
||||
account_data->GetGamertagString().c_str(),
|
||||
account_data->GetGamertagString().size());
|
||||
|
||||
gamercardOriginalValues_.country = account_data->GetCountry();
|
||||
gamercardOriginalValues_.language = account_data->GetLanguage();
|
||||
|
||||
gamercardOriginalValues_.is_live_enabled = account_data->IsLiveEnabled();
|
||||
|
||||
const std::string online_xuid_hex =
|
||||
string_util::to_hex_string(account_data->GetOnlineXUID());
|
||||
|
||||
std::memcpy(gamercardOriginalValues_.online_xuid, online_xuid_hex.c_str(),
|
||||
online_xuid_hex.size());
|
||||
|
||||
std::memcpy(gamercardOriginalValues_.online_domain,
|
||||
account_data->GetOnlineDomain().data(),
|
||||
account_data->GetOnlineDomain().size());
|
||||
|
||||
gamercardOriginalValues_.account_subscription_tier =
|
||||
account_data->GetSubscriptionTier();
|
||||
|
||||
if (is_signed_in_) {
|
||||
// GPD settings to load
|
||||
for (const auto setting_id : UserSettingsToLoad) {
|
||||
LoadSetting(setting_id);
|
||||
}
|
||||
|
||||
const auto gamer_icon =
|
||||
kernel_state()->xam_state()->GetUserProfile(xuid_)->GetProfileIcon(
|
||||
XTileType::kGamerTile);
|
||||
|
||||
gamercardOriginalValues_.profile_icon.assign(gamer_icon.begin(),
|
||||
gamer_icon.end());
|
||||
|
||||
gamercardOriginalValues_.icon_texture =
|
||||
imgui_drawer()->LoadImGuiIcon(gamer_icon).release();
|
||||
|
||||
LoadStringSetting(UserSettingId::XPROFILE_GAMERCARD_USER_NAME,
|
||||
gamercardOriginalValues_.gamer_name);
|
||||
|
||||
LoadStringSetting(UserSettingId::XPROFILE_GAMERCARD_MOTTO,
|
||||
gamercardOriginalValues_.gamer_motto);
|
||||
|
||||
LoadStringSetting(UserSettingId::XPROFILE_GAMERCARD_USER_BIO,
|
||||
gamercardOriginalValues_.gamer_bio);
|
||||
}
|
||||
|
||||
gamercardValues_ = gamercardOriginalValues_;
|
||||
}
|
||||
|
||||
void GamercardUI::LoadSetting(UserSettingId setting_id) {
|
||||
const auto setting = kernel_state()->xam_state()->user_tracker()->GetSetting(
|
||||
kernel_state()->xam_state()->GetUserProfile(xuid_), kDashboardID,
|
||||
static_cast<uint32_t>(setting_id));
|
||||
|
||||
if (!setting) {
|
||||
return;
|
||||
}
|
||||
|
||||
gamercardOriginalValues_.gpd_settings[setting_id] =
|
||||
setting.value().get_host_data();
|
||||
}
|
||||
|
||||
void GamercardUI::DrawInputTextBox(
|
||||
std::string label, char* buffer, size_t buffer_size, float alignment,
|
||||
std::function<bool(std::span<char>)> on_input_change) {
|
||||
ImGui::Text("%s", label.c_str());
|
||||
ImGui::SameLine(alignment);
|
||||
|
||||
const ImVec2 input_field_pos = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImGui::InputText(fmt::format("###{}", label).c_str(), buffer, buffer_size);
|
||||
|
||||
if (on_input_change) {
|
||||
if (!on_input_change({buffer, buffer_size})) {
|
||||
const ImVec2 item_size = ImGui::GetItemRectSize();
|
||||
const ImVec2 end_pos = ImVec2(input_field_pos.x + item_size.x,
|
||||
input_field_pos.y + item_size.y);
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
draw_list->AddRect(input_field_pos, end_pos, IM_COL32(255, 0, 0, 200),
|
||||
0.0f, 0, 2.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamercardUI::DrawSettingComboBox(UserSettingId setting_id,
|
||||
std::string label,
|
||||
const char* const items[], int item_count,
|
||||
float alignment) {
|
||||
ImGui::Text("%s:", label.c_str());
|
||||
ImGui::SameLine(alignment);
|
||||
|
||||
if (gamercardValues_.gpd_settings.contains(setting_id)) {
|
||||
auto entry = &std::get<int32_t>(gamercardValues_.gpd_settings[setting_id]);
|
||||
|
||||
ImGui::Combo(fmt::format("###{}", label).c_str(),
|
||||
reinterpret_cast<int*>(entry), items, item_count);
|
||||
} else {
|
||||
ImGui::BeginDisabled();
|
||||
int empty = 0;
|
||||
ImGui::Combo(fmt::format("###{}", label).c_str(), &empty, items, 0);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
void GamercardUI::SelectNewIcon() {
|
||||
std::filesystem::path path;
|
||||
|
||||
auto file_picker = xe::ui::FilePicker::Create();
|
||||
file_picker->set_mode(xe::ui::FilePicker::Mode::kOpen);
|
||||
file_picker->set_type(xe::ui::FilePicker::Type::kFile);
|
||||
file_picker->set_multi_selection(false);
|
||||
file_picker->set_title("Select PNG Image");
|
||||
file_picker->set_extensions({{"PNG Image", "*.png"}});
|
||||
|
||||
if (file_picker->Show(window_)) {
|
||||
auto selected_files = file_picker->selected_files();
|
||||
if (!selected_files.empty()) {
|
||||
path = selected_files[0];
|
||||
}
|
||||
|
||||
if (IsFilePngImage(path)) {
|
||||
const auto res = GetImageResolution(path);
|
||||
|
||||
if (res == kernel::xam::kProfileIconSizeSmall ||
|
||||
res == kernel::xam::kProfileIconSize) {
|
||||
gamercardValues_.profile_icon = ReadPngFromFile(path);
|
||||
gamercardValues_.icon_texture =
|
||||
imgui_drawer()
|
||||
->LoadImGuiIcon(gamercardValues_.profile_icon)
|
||||
.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamercardUI::DrawBaseSettings(ImGuiIO& io) {
|
||||
ImGui::SeparatorText("Profile Settings");
|
||||
|
||||
DrawInputTextBox(
|
||||
"Gamertag:", gamercardValues_.gamertag,
|
||||
std::size(gamercardValues_.gamertag), leftSideTextObjectAlignment,
|
||||
[](std::span<const char> data) {
|
||||
return ProfileManager::IsGamertagValid(std::string(data.data()));
|
||||
});
|
||||
|
||||
ImGui::BeginDisabled(!is_signed_in_);
|
||||
ImGui::BeginDisabled(kernel_state_->title_id());
|
||||
if (ImGui::ImageButton(
|
||||
"###ProfileIcon",
|
||||
reinterpret_cast<ImTextureID>(gamercardValues_.icon_texture),
|
||||
xe::ui::default_image_icon_size)) {
|
||||
SelectNewIcon();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) {
|
||||
if (kernel_state_->title_id()) {
|
||||
ImGui::SetTooltip("Icon change is disabled when title is running.");
|
||||
} else {
|
||||
ImGui::SetTooltip(
|
||||
"Provide a PNG image with a resolution of 64x64. Icon will refresh "
|
||||
"after relog.");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Gamer Name:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::InputText("###GamerName", gamercardValues_.gamer_name,
|
||||
std::size(gamercardValues_.gamer_name),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::Text("Gamer Motto:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::InputText("###GamerMotto", gamercardValues_.gamer_motto,
|
||||
std::size(gamercardValues_.gamer_motto),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::Text("Gamer Bio:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::InputTextMultiline("###GamerBio", gamercardValues_.gamer_bio,
|
||||
std::size(gamercardValues_.gamer_bio), ImVec2(),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Text("Language:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::Combo("###Language",
|
||||
reinterpret_cast<int*>(&gamercardValues_.language),
|
||||
XLanguageName, std::size(XLanguageName));
|
||||
|
||||
ImGui::Text("Country:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::Combo("###Country", reinterpret_cast<int*>(&gamercardValues_.country),
|
||||
XOnlineCountry, std::size(XOnlineCountry));
|
||||
}
|
||||
|
||||
void GamercardUI::DrawOnlineSettings(ImGuiIO& io) {
|
||||
ImGui::SeparatorText("Online Profile Settings");
|
||||
|
||||
if (ImGui::Checkbox("Live Enabled", &gamercardValues_.is_live_enabled)) {
|
||||
// TODO: Add checks to decide if online XUID generation is required
|
||||
}
|
||||
|
||||
ImGui::Text("Online XUID:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::InputText("###OnlineXUID", gamercardValues_.online_xuid,
|
||||
std::size(gamercardValues_.online_xuid),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::Text("Online Domain:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::InputText("###OnlineDomain", gamercardValues_.online_domain,
|
||||
std::size(gamercardValues_.online_domain),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::BeginDisabled(!gamercardValues_.is_live_enabled);
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMERCARD_ZONE, "Gamer Zone",
|
||||
XGamerzoneName, std::size(XGamerzoneName),
|
||||
leftSideTextObjectAlignment);
|
||||
|
||||
ImGui::Text("Subscription Tier:");
|
||||
ImGui::SameLine(leftSideTextObjectAlignment);
|
||||
ImGui::Combo(
|
||||
"###Subscription",
|
||||
reinterpret_cast<int*>(&gamercardValues_.account_subscription_tier),
|
||||
AccountSubscription, std::size(AccountSubscription));
|
||||
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void GamercardUI::DrawGpdSettings(ImGuiIO& io) {
|
||||
ImGui::SeparatorText("Game Settings");
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_DIFFICULTY, "Difficulty",
|
||||
GamerDifficultyOptions, std::size(GamerDifficultyOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_OPTION_CONTROLLER_VIBRATION,
|
||||
"Controller Vibration", ControllerVibrationOptions,
|
||||
std::size(ControllerVibrationOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_CONTROL_SENSITIVITY,
|
||||
"Control Sensitivity", ControlSensitivityOptions,
|
||||
std::size(ControlSensitivityOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_PREFERRED_COLOR_FIRST,
|
||||
"Favorite Color (First)", PreferredColorOptions,
|
||||
std::size(PreferredColorOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_PREFERRED_COLOR_SECOND,
|
||||
"Favorite Color (Second)", PreferredColorOptions,
|
||||
std::size(PreferredColorOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
ImGui::SeparatorText("Action Games Settings");
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_YAXIS_INVERSION,
|
||||
"Y-axis Inversion", YAxisInversionOptions,
|
||||
std::size(YAxisInversionOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_ACTION_AUTO_AIM, "Auto Aim",
|
||||
AutoAimOptions, std::size(AutoAimOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_ACTION_AUTO_CENTER,
|
||||
"Auto Center", AutoCenterOptions,
|
||||
std::size(AutoCenterOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL,
|
||||
"Movement Control", MovementControlOptions,
|
||||
std::size(MovementControlOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
ImGui::SeparatorText("Racing Games Settings");
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_RACE_TRANSMISSION,
|
||||
"Transmission", TransmissionOptions,
|
||||
std::size(TransmissionOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_RACE_CAMERA_LOCATION,
|
||||
"Camera Location", CameraLocationOptions,
|
||||
std::size(CameraLocationOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_RACE_BRAKE_CONTROL,
|
||||
"Brake Control", BrakeControlOptions,
|
||||
std::size(BrakeControlOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
|
||||
DrawSettingComboBox(UserSettingId::XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL,
|
||||
"Accelerator Control", AcceleratorControlOptions,
|
||||
std::size(AcceleratorControlOptions),
|
||||
rightSideTextObjectAlignment);
|
||||
}
|
||||
|
||||
void GamercardUI::OnDraw(ImGuiIO& io) {
|
||||
if (!has_opened_) {
|
||||
ImGui::OpenPopup(fmt::format("{}'s Gamercard",
|
||||
std::string(gamercardOriginalValues_.gamertag))
|
||||
.c_str());
|
||||
has_opened_ = true;
|
||||
}
|
||||
|
||||
bool dialog_open = true;
|
||||
if (!ImGui::BeginPopupModal(
|
||||
fmt::format("{}'s Gamercard",
|
||||
std::string(gamercardOriginalValues_.gamertag))
|
||||
.c_str(),
|
||||
&dialog_open,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("###GamercardTable", 2)) {
|
||||
ImGui::TableSetupColumn("###Label", ImGuiTableColumnFlags_WidthFixed,
|
||||
350.0f);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
DrawBaseSettings(io);
|
||||
DrawOnlineSettings(io);
|
||||
ImGui::TableNextColumn();
|
||||
DrawGpdSettings(io);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
const bool is_valid_gamertag =
|
||||
ProfileManager::IsGamertagValid(std::string(gamercardValues_.gamertag));
|
||||
|
||||
ImGui::BeginDisabled(!is_valid_gamertag);
|
||||
if (ImGui::Button("Save")) {
|
||||
SaveProfileIcon();
|
||||
SaveSettings();
|
||||
SaveAccountData();
|
||||
dialog_open = false;
|
||||
}
|
||||
if (!is_valid_gamertag) {
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
|
||||
ImGui::SetTooltip("Saving disabled! Invalid gamertag provided.");
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel")) {
|
||||
dialog_open = false;
|
||||
}
|
||||
|
||||
if (!dialog_open) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
ImGui::EndPopup();
|
||||
return;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
void GamercardUI::SaveAccountData() {
|
||||
const auto account_original =
|
||||
*kernel_state()->xam_state()->profile_manager()->GetAccount(xuid_);
|
||||
auto account = account_original;
|
||||
|
||||
account.SetCountry(gamercardValues_.country);
|
||||
account.SetLanguage(gamercardValues_.language);
|
||||
account.SetSubscriptionTier(gamercardValues_.account_subscription_tier);
|
||||
account.ToggleLiveFlag(gamercardValues_.is_live_enabled);
|
||||
|
||||
std::u16string gamertag =
|
||||
xe::to_utf16(std::string(gamercardValues_.gamertag));
|
||||
string_util::copy_truncating(account.gamertag, gamertag,
|
||||
std::size(account.gamertag));
|
||||
|
||||
if (std::memcmp(&account, &account_original, sizeof(X_XAMACCOUNTINFO)) != 0) {
|
||||
if (!is_signed_in_) {
|
||||
kernel_state()->xam_state()->profile_manager()->MountProfile(xuid_);
|
||||
}
|
||||
|
||||
kernel_state()->xam_state()->profile_manager()->UpdateAccount(xuid_,
|
||||
&account);
|
||||
|
||||
if (!is_signed_in_) {
|
||||
kernel_state()->xam_state()->profile_manager()->DismountProfile(xuid_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamercardUI::SaveProfileIcon() {
|
||||
if (gamercardValues_.profile_icon == gamercardOriginalValues_.profile_icon) {
|
||||
return;
|
||||
}
|
||||
|
||||
kernel_state()->xam_state()->user_tracker()->UpdateUserIcon(
|
||||
xuid_, {gamercardValues_.profile_icon.data(),
|
||||
gamercardValues_.profile_icon.size()});
|
||||
}
|
||||
|
||||
void GamercardUI::SaveSettings() {
|
||||
// First check all GPD embedded settings.
|
||||
for (const auto& [id, setting] : gamercardValues_.gpd_settings) {
|
||||
// No change was made to the setting?
|
||||
if (gamercardOriginalValues_.gpd_settings[id] == setting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UserSetting updated_setting(id, setting);
|
||||
kernel_state()->xam_state()->user_tracker()->UpsertSetting(
|
||||
xuid_, kDashboardID, &updated_setting);
|
||||
}
|
||||
|
||||
const uint32_t user_index = kernel_state()
|
||||
->xam_state()
|
||||
->profile_manager()
|
||||
->GetUserIndexAssignedToProfile(xuid_);
|
||||
|
||||
if (user_index != XUserIndexAny) {
|
||||
// TODO: Fix it to update only changed player
|
||||
kernel_state_->BroadcastNotification(
|
||||
kXNotificationSystemProfileSettingChanged, 0xF);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
91
src/xenia/kernel/xam/ui/gamercard_ui.h
Normal file
91
src/xenia/kernel/xam/ui/gamercard_ui.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_XAM_UI_GAMERCARD_UI_H_
|
||||
#define XENIA_KERNEL_XAM_UI_GAMERCARD_UI_H_
|
||||
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
struct GamercardSettings {
|
||||
// Account settings
|
||||
char gamertag[16];
|
||||
xe::XOnlineCountry country;
|
||||
xe::XLanguage language;
|
||||
bool is_live_enabled;
|
||||
char online_xuid[0x11];
|
||||
char online_domain[0x14];
|
||||
X_XAMACCOUNTINFO::AccountSubscriptionTier account_subscription_tier;
|
||||
|
||||
// GPD settings
|
||||
std::map<UserSettingId, UserDataTypes> gpd_settings;
|
||||
|
||||
// GPD string buffers
|
||||
char gamer_name[0x104];
|
||||
char gamer_motto[0x2C];
|
||||
char gamer_bio[kMaxUserDataSize];
|
||||
|
||||
// Other
|
||||
std::vector<uint8_t> profile_icon;
|
||||
// Immediate Texture?
|
||||
xe::ui::ImmediateTexture* icon_texture;
|
||||
};
|
||||
|
||||
class GamercardUI final : public XamDialog {
|
||||
public:
|
||||
GamercardUI(xe::ui::Window* window, xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
KernelState* kernel_state, uint64_t xuid);
|
||||
|
||||
private:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
void DrawBaseSettings(ImGuiIO& io);
|
||||
void DrawOnlineSettings(ImGuiIO& io);
|
||||
void DrawGpdSettings(ImGuiIO& io);
|
||||
|
||||
void LoadGamercardInfo();
|
||||
void LoadSetting(UserSettingId setting_id);
|
||||
void LoadStringSetting(UserSettingId setting_id, char* buffer);
|
||||
void SaveSettings();
|
||||
void SaveAccountData();
|
||||
void SaveProfileIcon();
|
||||
|
||||
void DrawSettingComboBox(UserSettingId setting_id, std::string label,
|
||||
const char* const items[], int item_count,
|
||||
float alignment);
|
||||
|
||||
void DrawInputTextBox(
|
||||
std::string label, char* buffer, size_t buffer_size, float alignment,
|
||||
std::function<bool(std::span<char>)> on_input_change = {});
|
||||
|
||||
void SelectNewIcon();
|
||||
|
||||
const uint64_t xuid_ = 0;
|
||||
const bool is_signed_in_ = false;
|
||||
const bool is_valid_gamertag_ = true;
|
||||
|
||||
bool has_opened_ = false;
|
||||
KernelState* kernel_state_;
|
||||
xe::ui::Window* window_;
|
||||
|
||||
// We're storing OG and current values to compare at the end and send what was
|
||||
// changed.
|
||||
GamercardSettings gamercardOriginalValues_ = {};
|
||||
GamercardSettings gamercardValues_ = {};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
101
src/xenia/kernel/xam/ui/passcode_ui.cc
Normal file
101
src/xenia/kernel/xam/ui/passcode_ui.cc
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/kernel/xam/ui/passcode_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
ProfilePasscodeUI::ProfilePasscodeUI(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
std::string_view title,
|
||||
std::string_view description,
|
||||
MESSAGEBOX_RESULT* result_ptr)
|
||||
: XamDialog(imgui_drawer),
|
||||
title_(title),
|
||||
description_(description),
|
||||
result_ptr_(result_ptr) {
|
||||
std::memset(result_ptr, 0, sizeof(MESSAGEBOX_RESULT));
|
||||
|
||||
if (title_.empty()) {
|
||||
title_ = "Enter Pass Code";
|
||||
}
|
||||
|
||||
if (description_.empty()) {
|
||||
description_ = "Enter your Xbox LIVE pass code.";
|
||||
}
|
||||
}
|
||||
|
||||
void ProfilePasscodeUI::DrawPasscodeField(uint8_t key_id) {
|
||||
const std::string label = fmt::format("##Key {}", key_id);
|
||||
|
||||
if (ImGui::BeginCombo(label.c_str(), labelled_keys_[key_indexes_[key_id]])) {
|
||||
for (uint8_t key_index = 0; key_index < keys_map_.size(); key_index++) {
|
||||
bool is_selected = key_id == key_index;
|
||||
|
||||
if (ImGui::Selectable(labelled_keys_[key_index], is_selected)) {
|
||||
key_indexes_[key_id] = key_index;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
void ProfilePasscodeUI::OnDraw(ImGuiIO& io) {
|
||||
if (!has_opened_) {
|
||||
ImGui::OpenPopup(title_.c_str());
|
||||
has_opened_ = true;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal(title_.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
if (description_.size()) {
|
||||
ImGui::Text("%s", description_.c_str());
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < passcode_length; i++) {
|
||||
DrawPasscodeField(i);
|
||||
// result_ptr_->Passcode[i] =
|
||||
// keys_map_.at(labelled_keys_[key_indexes_[i]]);
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
// We write each key on close to prevent simultaneous dialogs.
|
||||
if (ImGui::Button("Sign In")) {
|
||||
for (uint8_t i = 0; i < passcode_length; i++) {
|
||||
result_ptr_->Passcode[i] =
|
||||
keys_map_.at(labelled_keys_[key_indexes_[i]]);
|
||||
}
|
||||
|
||||
selected_signed_in_ = true;
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel")) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
66
src/xenia/kernel/xam/ui/passcode_ui.h
Normal file
66
src/xenia/kernel/xam/ui/passcode_ui.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_XAM_UI_PASSCODE_UI_H_
|
||||
#define XENIA_KERNEL_XAM_UI_PASSCODE_UI_H_
|
||||
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
class ProfilePasscodeUI final : public XamDialog {
|
||||
public:
|
||||
ProfilePasscodeUI(xe::ui::ImGuiDrawer* imgui_drawer, std::string_view title,
|
||||
std::string_view description,
|
||||
MESSAGEBOX_RESULT* result_ptr);
|
||||
|
||||
void DrawPasscodeField(uint8_t key_id);
|
||||
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
virtual ~ProfilePasscodeUI() {}
|
||||
|
||||
bool SelectedSignedIn() const { return selected_signed_in_; }
|
||||
|
||||
private:
|
||||
const char* labelled_keys_[11] = {"None", "X", "Y", "RB", "LB", "LT",
|
||||
"RT", "Up", "Down", "Left", "Right"};
|
||||
|
||||
const std::map<std::string, uint16_t> keys_map_ = {
|
||||
{"None", 0},
|
||||
{"X", X_BUTTON_PASSCODE},
|
||||
{"Y", Y_BUTTON_PASSCODE},
|
||||
{"RB", RIGHT_BUMPER_PASSCODE},
|
||||
{"LB", LEFT_BUMPER_PASSCODE},
|
||||
{"LT", LEFT_TRIGGER_PASSCODE},
|
||||
{"RT", RIGHT_TRIGGER_PASSCODE},
|
||||
{"Up", DPAD_UP_PASSCODE},
|
||||
{"Down", DPAD_DOWN_PASSCODE},
|
||||
{"Left", DPAD_LEFT_PASSCODE},
|
||||
{"Right", DPAD_RIGHT_PASSCODE}};
|
||||
|
||||
bool has_opened_ = false;
|
||||
bool selected_signed_in_ = false;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
|
||||
static constexpr uint8_t passcode_length = sizeof(X_XAMACCOUNTINFO::passcode);
|
||||
int key_indexes_[passcode_length] = {0, 0, 0, 0};
|
||||
MESSAGEBOX_RESULT* result_ptr_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
247
src/xenia/kernel/xam/ui/signin_ui.cc
Normal file
247
src/xenia/kernel/xam/ui/signin_ui.cc
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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/kernel/xam/ui/signin_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
SigninUI::SigninUI(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
ProfileManager* profile_manager, uint32_t last_used_slot,
|
||||
uint32_t users_needed)
|
||||
: XamDialog(imgui_drawer),
|
||||
profile_manager_(profile_manager),
|
||||
last_user_(last_used_slot),
|
||||
users_needed_(users_needed),
|
||||
title_("Sign In") {}
|
||||
|
||||
void SigninUI::OnDraw(ImGuiIO& io) {
|
||||
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)) {
|
||||
for (uint32_t i = 0; i < users_needed_; i++) {
|
||||
ImGui::BeginGroup();
|
||||
|
||||
std::vector<const char*> combo_items;
|
||||
int items_count = 0;
|
||||
int current_item = 0;
|
||||
|
||||
// Fill slot list.
|
||||
std::vector<uint8_t> 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<int>(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<int>(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<uint64_t> 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<int>(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<int>(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];
|
||||
const auto account = profile_manager_->GetAccount(xuid);
|
||||
|
||||
if (slot == 0xFF || xuid == 0 || !account) {
|
||||
float ypos = ImGui::GetCursorPosY();
|
||||
ImGui::SetCursorPosY(ypos + ImGui::GetTextLineHeight() * 5);
|
||||
} else {
|
||||
xeDrawProfileContent(imgui_drawer(), xuid, slot, account, nullptr, {},
|
||||
{}, 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<uint8_t, uint64_t> 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 SigninUI::ReloadProfiles(bool first_draw) {
|
||||
auto profile_manager = kernel_state()->xam_state()->profile_manager();
|
||||
auto profiles = profile_manager->GetAccounts();
|
||||
|
||||
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] != XUserIndexAny && slot == chosen_slots_[j]) {
|
||||
chosen_xuids_[j] = xuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
53
src/xenia/kernel/xam/ui/signin_ui.h
Normal file
53
src/xenia/kernel/xam/ui/signin_ui.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_XAM_UI_SIGNIN_UI_H_
|
||||
#define XENIA_KERNEL_XAM_UI_SIGNIN_UI_H_
|
||||
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
class SigninUI final : public XamDialog {
|
||||
public:
|
||||
SigninUI(xe::ui::ImGuiDrawer* imgui_drawer, ProfileManager* profile_manager,
|
||||
uint32_t last_used_slot, uint32_t users_needed);
|
||||
|
||||
private:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
void ReloadProfiles(bool first_draw);
|
||||
|
||||
const std::map<uint8_t, std::string> slot_data_ = {
|
||||
{0, "Slot 0"}, {1, "Slot 1"}, {2, "Slot 2"}, {3, "Slot 3"}};
|
||||
|
||||
ProfileManager* profile_manager_ = nullptr;
|
||||
|
||||
bool has_opened_ = false;
|
||||
std::string title_;
|
||||
uint32_t users_needed_ = 1;
|
||||
uint32_t last_user_ = 0;
|
||||
|
||||
std::vector<std::pair<uint64_t, std::string>> profile_data_;
|
||||
uint8_t chosen_slots_[XUserMaxUserCount] = {};
|
||||
uint64_t chosen_xuids_[XUserMaxUserCount] = {};
|
||||
|
||||
bool creating_profile_ = false;
|
||||
char gamertag_[16] = "";
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
229
src/xenia/kernel/xam/ui/title_info_ui.cc
Normal file
229
src/xenia/kernel/xam/ui/title_info_ui.cc
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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/kernel/xam/ui/title_info_ui.h"
|
||||
#include "xenia/kernel/xam/ui/game_achievements_ui.h"
|
||||
|
||||
#include "xenia/base/system.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
TitleListUI::TitleListUI(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
const ImVec2 drawing_position,
|
||||
const UserProfile* profile)
|
||||
: XamDialog(imgui_drawer),
|
||||
drawing_position_(drawing_position),
|
||||
profile_(profile),
|
||||
profile_manager_(kernel_state()->xam_state()->profile_manager()),
|
||||
dialog_name_(
|
||||
fmt::format("{}'s Games List###{}", profile->name(), GetWindowId())) {
|
||||
LoadProfileTitleList(imgui_drawer, profile);
|
||||
}
|
||||
|
||||
TitleListUI::~TitleListUI() {
|
||||
for (auto& entry : title_icon) {
|
||||
entry.second.release();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleListUI::LoadProfileTitleList(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
const UserProfile* profile) {
|
||||
info_.clear();
|
||||
|
||||
xe::ui::IconsData data;
|
||||
|
||||
info_ = kernel_state()->xam_state()->user_tracker()->GetPlayedTitles(
|
||||
profile->xuid());
|
||||
for (const auto& title_info : info_) {
|
||||
if (!title_info.icon.empty()) {
|
||||
data[title_info.id] = title_info.icon;
|
||||
}
|
||||
}
|
||||
|
||||
title_icon = imgui_drawer->LoadIcons(data);
|
||||
}
|
||||
|
||||
void TitleListUI::DrawTitleEntry(ImGuiIO& io, TitleInfo& entry) {
|
||||
const auto start_position = ImGui::GetCursorPos();
|
||||
const ImVec2 next_window_position =
|
||||
ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowSize().x + 20.f,
|
||||
ImGui::GetWindowPos().y);
|
||||
|
||||
// First Column
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
|
||||
if (title_icon.count(entry.id)) {
|
||||
ImGui::Image(reinterpret_cast<ImTextureID>(title_icon.at(entry.id).get()),
|
||||
xe::ui::default_image_icon_size);
|
||||
} else {
|
||||
ImGui::Dummy(xe::ui::default_image_icon_size);
|
||||
}
|
||||
|
||||
// Second Column
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushFont(imgui_drawer()->GetTitleFont());
|
||||
ImGui::TextUnformatted(xe::to_utf8(entry.title_name).c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::TextUnformatted(
|
||||
fmt::format("{}/{} Achievements unlocked ({} Gamerscore)",
|
||||
entry.unlocked_achievements_count, entry.achievements_count,
|
||||
entry.title_earned_gamerscore)
|
||||
.c_str());
|
||||
|
||||
ImGui::SetCursorPosY(start_position.y + xe::ui::default_image_icon_size.y -
|
||||
ImGui::GetTextLineHeight());
|
||||
|
||||
if (entry.WasTitlePlayed()) {
|
||||
ImGui::TextUnformatted(
|
||||
fmt::format("Last played: {:%Y-%m-%d %H:%M}",
|
||||
std::chrono::system_clock::time_point(
|
||||
entry.last_played.time_since_epoch()))
|
||||
.c_str());
|
||||
} else {
|
||||
ImGui::TextUnformatted("Last played: Unknown");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
const ImVec2 end_draw_position =
|
||||
ImVec2(ImGui::GetCursorPos().x - start_position.x,
|
||||
ImGui::GetCursorPos().y - start_position.y);
|
||||
|
||||
ImGui::SetCursorPos(start_position);
|
||||
|
||||
if (ImGui::Selectable(fmt::format("##{:08X}Selectable", entry.id).c_str(),
|
||||
selected_title_ == entry.id,
|
||||
ImGuiSelectableFlags_SpanAllColumns,
|
||||
end_draw_position)) {
|
||||
selected_title_ = entry.id;
|
||||
new GameAchievementsUI(imgui_drawer(), next_window_position, &entry,
|
||||
profile_);
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextItem(
|
||||
fmt::format("Title Menu {:08X}", entry.id).c_str())) {
|
||||
selected_title_ = entry.id;
|
||||
if (ImGui::MenuItem("Refresh title stats", nullptr, nullptr, true)) {
|
||||
kernel_state()->xam_state()->user_tracker()->RefreshTitleSummary(
|
||||
profile_->xuid(), entry.id);
|
||||
|
||||
const auto title_info =
|
||||
kernel_state()->xam_state()->user_tracker()->GetUserTitleInfo(
|
||||
profile_->xuid(), entry.id);
|
||||
|
||||
if (title_info) {
|
||||
entry = title_info.value();
|
||||
}
|
||||
}
|
||||
|
||||
const auto savefile_path = profile_manager_->GetProfileContentPath(
|
||||
profile_->xuid(), entry.id, XContentType::kSavedGame);
|
||||
|
||||
const auto dlc_path = profile_manager_->GetProfileContentPath(
|
||||
0, entry.id, XContentType::kMarketplaceContent);
|
||||
|
||||
const auto tu_path = profile_manager_->GetProfileContentPath(
|
||||
0, entry.id, XContentType::kInstaller);
|
||||
|
||||
if (ImGui::MenuItem("Open savefile directory", nullptr, nullptr,
|
||||
std::filesystem::exists(savefile_path))) {
|
||||
std::thread path_open(LaunchFileExplorer, savefile_path);
|
||||
path_open.detach();
|
||||
}
|
||||
if (ImGui::MenuItem("Open DLC directory", nullptr, nullptr,
|
||||
std::filesystem::exists(dlc_path))) {
|
||||
std::thread path_open(LaunchFileExplorer, dlc_path);
|
||||
path_open.detach();
|
||||
}
|
||||
if (ImGui::MenuItem("Open Title Update directory", nullptr, nullptr,
|
||||
std::filesystem::exists(tu_path))) {
|
||||
std::thread path_open(LaunchFileExplorer, tu_path);
|
||||
path_open.detach();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleListUI::OnDraw(ImGuiIO& io) {
|
||||
ImGui::SetNextWindowPos(drawing_position_, ImGuiCond_FirstUseEver);
|
||||
const auto xenia_window_size = ImGui::GetMainViewport()->Size;
|
||||
|
||||
ImGui::SetNextWindowSizeConstraints(
|
||||
ImVec2(xenia_window_size.x * 0.05f, xenia_window_size.y * 0.05f),
|
||||
ImVec2(xenia_window_size.x * 0.4f, xenia_window_size.y * 0.5f));
|
||||
ImGui::SetNextWindowBgAlpha(0.8f);
|
||||
|
||||
bool dialog_open = true;
|
||||
if (!ImGui::Begin(dialog_name_.c_str(), &dialog_open,
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||
Close();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!info_.empty()) {
|
||||
if (info_.size() > 10) {
|
||||
ImGui::Text("Search: ");
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("##Search", title_name_filter_, title_name_filter_size);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("", 2, ImGuiTableFlags_BordersInnerH)) {
|
||||
ImGui::TableNextRow(0, xe::ui::default_image_icon_size.y);
|
||||
for (auto& entry : info_) {
|
||||
std::string filter(title_name_filter_);
|
||||
if (!filter.empty()) {
|
||||
bool contains_filter =
|
||||
utf8::lower_ascii(xe::to_utf8(entry.title_name))
|
||||
.find(utf8::lower_ascii(filter)) != std::string::npos;
|
||||
|
||||
if (!contains_filter) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
DrawTitleEntry(io, entry);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
} else {
|
||||
// Align text to the center
|
||||
std::string no_entries_message = "There are no titles, so far.";
|
||||
|
||||
ImGui::PushFont(imgui_drawer()->GetTitleFont());
|
||||
float windowWidth = ImGui::GetContentRegionAvail().x;
|
||||
ImVec2 textSize = ImGui::CalcTextSize(no_entries_message.c_str());
|
||||
float textOffsetX = (windowWidth - textSize.x) * 0.5f;
|
||||
if (textOffsetX > 0.0f) {
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + textOffsetX);
|
||||
}
|
||||
|
||||
ImGui::Text("%s", no_entries_message.c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (!dialog_open) {
|
||||
Close();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
53
src/xenia/kernel/xam/ui/title_info_ui.h
Normal file
53
src/xenia/kernel/xam/ui/title_info_ui.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_XAM_UI_TITLE_INFO_UI_H_
|
||||
#define XENIA_KERNEL_XAM_UI_TITLE_INFO_UI_H_
|
||||
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
namespace ui {
|
||||
|
||||
class TitleListUI final : public XamDialog {
|
||||
public:
|
||||
TitleListUI(xe::ui::ImGuiDrawer* imgui_drawer, const ImVec2 drawing_position,
|
||||
const UserProfile* profile);
|
||||
|
||||
private:
|
||||
~TitleListUI();
|
||||
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
void LoadProfileTitleList(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
const UserProfile* profile);
|
||||
void DrawTitleEntry(ImGuiIO& io, TitleInfo& entry);
|
||||
|
||||
static constexpr uint8_t title_name_filter_size = 15;
|
||||
|
||||
std::string dialog_name_ = "";
|
||||
char title_name_filter_[title_name_filter_size] = "";
|
||||
uint32_t selected_title_ = 0;
|
||||
const ImVec2 drawing_position_ = {};
|
||||
|
||||
const UserProfile* profile_;
|
||||
const ProfileManager* profile_manager_;
|
||||
|
||||
std::map<uint32_t, std::unique_ptr<xe::ui::ImmediateTexture>> title_icon;
|
||||
std::vector<TitleInfo> info_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user