[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:
parent
270e88a7a7
commit
e601b5ab87
|
@ -7,82 +7,24 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/app/profile_dialogs.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "xenia/app/emulator_window.h"
|
||||
#include "xenia/app/profile_dialogs.h"
|
||||
#include "xenia/base/png_utils.h"
|
||||
#include "xenia/base/system.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_ui.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
|
||||
#include "xenia/kernel/xam/ui/create_profile_ui.h"
|
||||
#include "xenia/kernel/xam/ui/gamercard_ui.h"
|
||||
#include "xenia/kernel/xam/ui/signin_ui.h"
|
||||
#include "xenia/kernel/xam/ui/title_info_ui.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();
|
||||
|
||||
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_window_->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();
|
||||
}
|
||||
|
||||
void NoProfileDialog::OnDraw(ImGuiIO& io) {
|
||||
auto profile_manager = emulator_window_->emulator()
|
||||
->kernel_state()
|
||||
|
@ -124,13 +66,13 @@ void NoProfileDialog::OnDraw(ImGuiIO& io) {
|
|||
|
||||
if (content_files.empty()) {
|
||||
if (ImGui::Button("Create Profile")) {
|
||||
new CreateProfileDialog(emulator_window_->imgui_drawer(),
|
||||
emulator_window_);
|
||||
new kernel::xam::ui::CreateProfileUI(emulator_window_->imgui_drawer(),
|
||||
emulator_window_->emulator());
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Button("Create profile & migrate data")) {
|
||||
new CreateProfileDialog(emulator_window_->imgui_drawer(),
|
||||
emulator_window_, true);
|
||||
new kernel::xam::ui::CreateProfileUI(emulator_window_->imgui_drawer(),
|
||||
emulator_window_->emulator(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,6 +91,57 @@ void NoProfileDialog::OnDraw(ImGuiIO& io) {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
void ProfileConfigDialog::LoadProfileIcon() {
|
||||
if (!emulator_window_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t user_index = 0; user_index < XUserMaxUserCount; user_index++) {
|
||||
const auto profile = emulator_window_->emulator()
|
||||
->kernel_state()
|
||||
->xam_state()
|
||||
->profile_manager()
|
||||
->GetProfile(user_index);
|
||||
|
||||
if (!profile) {
|
||||
continue;
|
||||
}
|
||||
LoadProfileIcon(profile->xuid());
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileConfigDialog::LoadProfileIcon(const uint64_t xuid) {
|
||||
if (!emulator_window_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto profile_manager = emulator_window_->emulator()
|
||||
->kernel_state()
|
||||
->xam_state()
|
||||
->profile_manager();
|
||||
if (!profile_manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto profile = profile_manager->GetProfile(xuid);
|
||||
|
||||
if (!profile) {
|
||||
if (profile_icon_.contains(xuid)) {
|
||||
profile_icon_[xuid].release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto profile_icon =
|
||||
profile->GetProfileIcon(kernel::xam::XTileType::kGamerTile);
|
||||
if (profile_icon.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
profile_icon_[xuid].release();
|
||||
profile_icon_[xuid] = imgui_drawer()->LoadImGuiIcon(profile_icon);
|
||||
}
|
||||
|
||||
void ProfileConfigDialog::OnDraw(ImGuiIO& io) {
|
||||
if (!emulator_window_->emulator() ||
|
||||
!emulator_window_->emulator()->kernel_state() ||
|
||||
|
@ -184,28 +177,120 @@ void ProfileConfigDialog::OnDraw(ImGuiIO& io) {
|
|||
ImGui::Separator();
|
||||
}
|
||||
|
||||
const ImVec2 next_window_position =
|
||||
ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowSize().x + 20.f,
|
||||
ImGui::GetWindowPos().y);
|
||||
|
||||
for (auto& [xuid, account] : *profiles) {
|
||||
ImGui::PushID(static_cast<int>(xuid));
|
||||
|
||||
const uint8_t user_index =
|
||||
profile_manager->GetUserIndexAssignedToProfile(xuid);
|
||||
|
||||
if (!kernel::xam::xeDrawProfileContent(imgui_drawer(), xuid, user_index,
|
||||
&account, &selected_xuid_)) {
|
||||
const auto profile_icon = profile_icon_.find(xuid) != profile_icon_.cend()
|
||||
? profile_icon_[xuid].get()
|
||||
: nullptr;
|
||||
|
||||
auto context_menu_fun = [=, this]() -> bool {
|
||||
if (ImGui::BeginPopupContextItem("Profile Menu")) {
|
||||
//*selected_xuid = xuid;
|
||||
if (user_index == XUserIndexAny) {
|
||||
if (ImGui::MenuItem("Login")) {
|
||||
profile_manager->Login(xuid);
|
||||
if (!profile_manager->GetProfile(xuid)
|
||||
->GetProfileIcon(kernel::xam::XTileType::kGamerTile)
|
||||
.empty()) {
|
||||
LoadProfileIcon(xuid);
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginMenu("Login to slot:")) {
|
||||
for (uint8_t i = 1; i <= XUserMaxUserCount; i++) {
|
||||
if (ImGui::MenuItem(fmt::format("slot {}", i).c_str())) {
|
||||
profile_manager->Login(xuid, i - 1);
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
} else {
|
||||
if (ImGui::MenuItem("Logout")) {
|
||||
profile_manager->Logout(user_index);
|
||||
LoadProfileIcon(xuid);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Modify")) {
|
||||
new kernel::xam::ui::GamercardUI(
|
||||
emulator_window_->window(), emulator_window_->imgui_drawer(),
|
||||
emulator_window_->emulator()->kernel_state(), xuid);
|
||||
}
|
||||
|
||||
const bool is_signedin = profile_manager->GetProfile(xuid) != nullptr;
|
||||
ImGui::BeginDisabled(!is_signedin);
|
||||
if (ImGui::MenuItem("Show Played Titles")) {
|
||||
new kernel::xam::ui::TitleListUI(
|
||||
emulator_window_->imgui_drawer(), next_window_position,
|
||||
profile_manager->GetProfile(user_index));
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
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();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!kernel::xam::xeDrawProfileContent(
|
||||
imgui_drawer(), xuid, user_index, &account, profile_icon,
|
||||
context_menu_fun, [=, this]() { LoadProfileIcon(xuid); },
|
||||
&selected_xuid_)) {
|
||||
ImGui::PopID();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Create Profile")) {
|
||||
new CreateProfileDialog(emulator_window_->imgui_drawer(), emulator_window_);
|
||||
new kernel::xam::ui::CreateProfileUI(emulator_window_->imgui_drawer(),
|
||||
emulator_window_->emulator());
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
|
|
@ -19,26 +19,6 @@ namespace app {
|
|||
|
||||
class EmulatorWindow;
|
||||
|
||||
class CreateProfileDialog final : public ui::ImGuiDialog {
|
||||
public:
|
||||
CreateProfileDialog(ui::ImGuiDrawer* imgui_drawer,
|
||||
EmulatorWindow* emulator_window,
|
||||
bool with_migration = false)
|
||||
: ui::ImGuiDialog(imgui_drawer),
|
||||
emulator_window_(emulator_window),
|
||||
migration_(with_migration) {
|
||||
memset(gamertag_, 0, sizeof(gamertag_));
|
||||
}
|
||||
|
||||
protected:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
bool has_opened_ = false;
|
||||
bool migration_ = false;
|
||||
char gamertag_[16] = "";
|
||||
EmulatorWindow* emulator_window_;
|
||||
};
|
||||
|
||||
class NoProfileDialog final : public ui::ImGuiDialog {
|
||||
public:
|
||||
NoProfileDialog(ui::ImGuiDrawer* imgui_drawer,
|
||||
|
@ -55,12 +35,19 @@ class ProfileConfigDialog final : public ui::ImGuiDialog {
|
|||
public:
|
||||
ProfileConfigDialog(ui::ImGuiDrawer* imgui_drawer,
|
||||
EmulatorWindow* emulator_window)
|
||||
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {}
|
||||
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {
|
||||
LoadProfileIcon();
|
||||
}
|
||||
|
||||
protected:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
private:
|
||||
void LoadProfileIcon();
|
||||
void LoadProfileIcon(const uint64_t xuid);
|
||||
|
||||
std::map<uint64_t, std::unique_ptr<ui::ImmediateTexture>> profile_icon_;
|
||||
|
||||
uint64_t selected_xuid_ = 0;
|
||||
EmulatorWindow* emulator_window_;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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/base/png_utils.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
|
||||
#include "third_party/stb/stb_image.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
bool IsFilePngImage(const std::filesystem::path& file_path) {
|
||||
FILE* file = xe::filesystem::OpenFile(file_path, "rb");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr uint8_t magic_size = 4;
|
||||
char magic[magic_size];
|
||||
if (fread(&magic, 1, magic_size, file) != magic_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
if (magic[1] != 'P' || magic[2] != 'N' || magic[3] != 'G') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<uint16_t, uint16_t> GetImageResolution(
|
||||
const std::filesystem::path& file_path) {
|
||||
FILE* file = xe::filesystem::OpenFile(file_path, "rb");
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int width, height, channels;
|
||||
if (!stbi_info_from_file(file, &width, &height, &channels)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ReadPngFromFile(const std::filesystem::path& file_path) {
|
||||
FILE* file = xe::filesystem::OpenFile(file_path, "rb");
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto file_size = std::filesystem::file_size(file_path);
|
||||
std::vector<uint8_t> data(file_size);
|
||||
fread(data.data(), 1, file_size, file);
|
||||
fclose(file);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace xe
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_BASE_PNG_UTILS_H_
|
||||
#define XENIA_BASE_PNG_UTILS_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xe {
|
||||
|
||||
bool IsFilePngImage(const std::filesystem::path& file_path);
|
||||
std::pair<uint16_t, uint16_t> GetImageResolution(
|
||||
const std::filesystem::path& file_path);
|
||||
|
||||
std::vector<uint8_t> ReadPngFromFile(const std::filesystem::path& file_path);
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_PNG_UTILS_H_
|
|
@ -10,9 +10,7 @@
|
|||
#include "xenia/kernel/xam/profile_manager.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/hid/input_system.h"
|
||||
|
@ -139,6 +137,14 @@ ProfileManager::ProfileManager(KernelState* kernel_state,
|
|||
|
||||
ProfileManager::~ProfileManager() {}
|
||||
|
||||
void ProfileManager::ReloadProfile(const uint64_t xuid) {
|
||||
if (accounts_.contains(xuid)) {
|
||||
accounts_.erase(xuid);
|
||||
}
|
||||
|
||||
LoadAccount(xuid);
|
||||
}
|
||||
|
||||
void ProfileManager::ReloadProfiles() { LoadAccounts(FindProfiles()); }
|
||||
|
||||
UserProfile* ProfileManager::GetProfile(const uint64_t xuid) const {
|
||||
|
@ -226,7 +232,7 @@ bool ProfileManager::LoadAccount(const uint64_t xuid) {
|
|||
// We need it only when we want to login into this account!
|
||||
DismountProfile(xuid);
|
||||
|
||||
accounts_.insert({xuid, tmp_acct});
|
||||
accounts_.insert_or_assign(xuid, tmp_acct);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -236,26 +242,6 @@ void ProfileManager::LoadAccounts(const std::vector<uint64_t> profiles_xuids) {
|
|||
}
|
||||
}
|
||||
|
||||
void ProfileManager::ModifyGamertag(const uint64_t xuid, std::string gamertag) {
|
||||
if (!accounts_.count(xuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
xe::X_XAMACCOUNTINFO* account = &accounts_[xuid];
|
||||
|
||||
std::u16string gamertag_u16 = xe::to_utf16(gamertag);
|
||||
|
||||
string_util::copy_truncating(account->gamertag, gamertag_u16,
|
||||
sizeof(account->gamertag));
|
||||
|
||||
if (!MountProfile(xuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateAccount(xuid, account);
|
||||
DismountProfile(xuid);
|
||||
}
|
||||
|
||||
bool ProfileManager::MountProfile(const uint64_t xuid, std::string mount_path) {
|
||||
std::filesystem::path profile_path = GetProfilePath(xuid);
|
||||
if (mount_path.empty()) {
|
||||
|
@ -282,7 +268,7 @@ bool ProfileManager::DismountProfile(const uint64_t xuid) {
|
|||
|
||||
void ProfileManager::Login(const uint64_t xuid, const uint8_t user_index,
|
||||
bool notify) {
|
||||
if (logged_profiles_.size() >= 4 && user_index >= XUserMaxUserCount) {
|
||||
if (logged_profiles_.size() >= XUserMaxUserCount) {
|
||||
XELOGE(
|
||||
"Cannot login account with XUID: {:016X} due to lack of free slots "
|
||||
"(Max 4 accounts at once)",
|
||||
|
@ -567,6 +553,9 @@ bool ProfileManager::UpdateAccount(const uint64_t xuid,
|
|||
std::span<uint8_t>(encrypted_data.data(), encrypted_data.size()), 0,
|
||||
&written_bytes);
|
||||
output_file->Destroy();
|
||||
|
||||
// Refresh the in-memory account data
|
||||
accounts_.insert_or_assign(xuid, *account);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,8 +68,6 @@ class ProfileManager {
|
|||
|
||||
bool DeleteProfile(const uint64_t xuid);
|
||||
|
||||
void ModifyGamertag(const uint64_t xuid, std::string gamertag);
|
||||
|
||||
bool MountProfile(const uint64_t xuid, std::string mount_path = "");
|
||||
bool DismountProfile(const uint64_t xuid);
|
||||
|
||||
|
@ -82,6 +80,7 @@ class ProfileManager {
|
|||
void LoadAccounts(const std::vector<uint64_t> profiles_xuids);
|
||||
|
||||
void ReloadProfiles();
|
||||
void ReloadProfile(const uint64_t xuid);
|
||||
|
||||
UserProfile* GetProfile(const uint64_t xuid) const;
|
||||
UserProfile* GetProfile(const uint8_t user_index) const;
|
||||
|
@ -101,13 +100,14 @@ class ProfileManager {
|
|||
const uint64_t xuid, const uint32_t title_id = -1,
|
||||
const XContentType content_type = XContentType::kInvalid) const;
|
||||
|
||||
bool UpdateAccount(const uint64_t xuid, const X_XAMACCOUNTINFO* account);
|
||||
|
||||
static bool IsGamertagValid(const std::string gamertag);
|
||||
|
||||
private:
|
||||
void UpdateConfig(const uint64_t xuid, const uint8_t slot);
|
||||
bool CreateAccount(const uint64_t xuid, const std::string gamertag);
|
||||
bool CreateAccount(const uint64_t xuid, const X_XAMACCOUNTINFO* account);
|
||||
bool UpdateAccount(const uint64_t xuid, const X_XAMACCOUNTINFO* account);
|
||||
|
||||
std::filesystem::path GetProfilePath(const uint64_t xuid) const;
|
||||
std::filesystem::path GetProfilePath(const std::string xuid) const;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -84,6 +84,29 @@ class UserData {
|
|||
return {extended_data_.data(), extended_data_.size()};
|
||||
}
|
||||
|
||||
UserDataTypes get_host_data() const {
|
||||
if (data_.type == X_USER_DATA_TYPE::INT32) {
|
||||
return data_.data.s32;
|
||||
}
|
||||
|
||||
if (data_.type == X_USER_DATA_TYPE::DATETIME) {
|
||||
return data_.data.s64;
|
||||
}
|
||||
|
||||
if (data_.type == X_USER_DATA_TYPE::WSTRING) {
|
||||
if (get_extended_data().empty()) {
|
||||
return std::u16string();
|
||||
}
|
||||
|
||||
const char16_t* str_begin =
|
||||
reinterpret_cast<const char16_t*>(get_extended_data().data());
|
||||
|
||||
return string_util::read_u16string_and_swap(str_begin);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_valid_type() const {
|
||||
return data_.type >= X_USER_DATA_TYPE::CONTEXT &&
|
||||
data_.type <= X_USER_DATA_TYPE::DATETIME;
|
||||
|
|
|
@ -93,7 +93,32 @@ void UserProfile::LoadProfileIcon(XTileType tile_type) {
|
|||
&written_bytes);
|
||||
file->Destroy();
|
||||
|
||||
profile_images_.insert({tile_type, data});
|
||||
profile_images_.insert_or_assign(tile_type, data);
|
||||
}
|
||||
|
||||
void UserProfile::WriteProfileIcon(XTileType tile_type,
|
||||
std::span<const uint8_t> icon_data) {
|
||||
const std::string path =
|
||||
fmt::format("User_{:016X}:\\{}", xuid_, kTileFileNames.at(tile_type));
|
||||
|
||||
vfs::File* file = nullptr;
|
||||
vfs::FileAction action;
|
||||
|
||||
const X_STATUS result = kernel_state()->file_system()->OpenFile(
|
||||
nullptr, path, vfs::FileDisposition::kOverwriteIf,
|
||||
vfs::FileAccess::kGenericAll, false, true, &file, &action);
|
||||
|
||||
if (result != X_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t written_bytes = 0;
|
||||
|
||||
file->WriteSync({icon_data.data(), icon_data.size()}, 0, &written_bytes);
|
||||
file->Destroy();
|
||||
|
||||
profile_images_.insert_or_assign(
|
||||
tile_type, std::vector<uint8_t>(icon_data.begin(), icon_data.end()));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> UserProfile::LoadGpd(const uint32_t title_id) {
|
||||
|
|
|
@ -46,6 +46,14 @@ struct X_USER_PROFILE_SETTING {
|
|||
};
|
||||
static_assert_size(X_USER_PROFILE_SETTING, 40);
|
||||
|
||||
enum class X_USER_PROFILE_GAMERCARD_ZONE_OPTIONS {
|
||||
GAMERCARD_ZONE_NONE,
|
||||
GAMERCARD_ZONE_RR,
|
||||
GAMERCARD_ZONE_PRO,
|
||||
GAMERCARD_ZONE_FAMILY,
|
||||
GAMERCARD_ZONE_UNDERGROUND
|
||||
};
|
||||
|
||||
enum class XTileType {
|
||||
kAchievement,
|
||||
kGameIcon,
|
||||
|
@ -75,6 +83,9 @@ static const std::map<XTileType, std::string> kTileFileNames = {
|
|||
{XTileType::kAvatarGamerTileSmall, "avtr_32.png"},
|
||||
};
|
||||
|
||||
static constexpr std::pair<uint16_t, uint16_t> kProfileIconSize = {64, 64};
|
||||
static constexpr std::pair<uint16_t, uint16_t> kProfileIconSizeSmall = {32, 32};
|
||||
|
||||
class UserProfile {
|
||||
public:
|
||||
UserProfile(uint64_t xuid, X_XAMACCOUNTINFO* account_info);
|
||||
|
@ -88,6 +99,7 @@ class UserProfile {
|
|||
uint32_t GetSubscriptionTier() const {
|
||||
return account_info_.GetSubscriptionTier();
|
||||
}
|
||||
bool IsLiveEnabled() const { return account_info_.IsLiveEnabled(); }
|
||||
|
||||
std::span<const uint8_t> GetProfileIcon(XTileType icon_type) {
|
||||
// Overwrite same types?
|
||||
|
@ -132,6 +144,8 @@ class UserProfile {
|
|||
|
||||
void LoadProfileGpds();
|
||||
void LoadProfileIcon(XTileType tile_type);
|
||||
void WriteProfileIcon(XTileType tile_type,
|
||||
std::span<const uint8_t> icon_data);
|
||||
std::vector<uint8_t> LoadGpd(const uint32_t title_id);
|
||||
bool WriteGpd(const uint32_t title_id);
|
||||
};
|
||||
|
|
|
@ -379,7 +379,7 @@ const static std::set<UserSettingId> title_writable_settings = {
|
|||
UserSettingId::XPROFILE_TITLE_SPECIFIC2,
|
||||
UserSettingId::XPROFILE_TITLE_SPECIFIC3};
|
||||
|
||||
enum PREFERRED_COLOR_OPTIONS : uint32_t {
|
||||
enum PREFERRED_COLOR_OPTIONS : uint8_t {
|
||||
PREFERRED_COLOR_NONE,
|
||||
PREFERRED_COLOR_BLACK,
|
||||
PREFERRED_COLOR_WHITE,
|
||||
|
@ -393,6 +393,57 @@ enum PREFERRED_COLOR_OPTIONS : uint32_t {
|
|||
PREFERRED_COLOR_BROWN,
|
||||
PREFERRED_COLOR_SILVER
|
||||
};
|
||||
enum CONTROLLER_VIBRATION_OPTIONS : uint8_t {
|
||||
CONTROLLER_VIBRATION_OFF = 0,
|
||||
CONTROLLER_VIBRATION_ON = 3
|
||||
};
|
||||
|
||||
enum CONTROL_SENSITIVITY_OPTIONS : uint8_t {
|
||||
CONTROL_SENSITIVITY_MEDIUM = 0,
|
||||
CONTROL_SENSITIVITY_LOW,
|
||||
CONTROL_SENSITIVITY_HIGH
|
||||
};
|
||||
|
||||
enum GAMER_DIFFICULTY_OPTIONS : uint8_t {
|
||||
GAMER_DIFFICULTY_NORMAL = 0,
|
||||
GAMER_DIFFICULTY_EASY,
|
||||
GAMER_DIFFICULTY_HARD
|
||||
};
|
||||
|
||||
enum AUTO_AIM_OPTIONS : uint8_t { AUTO_AIM_OFF = 0, AUTO_AIM_ON };
|
||||
|
||||
enum AUTO_CENTER_OPTIONS : uint8_t { AUTO_CENTER_OFF = 0, AUTO_CENTER_ON };
|
||||
|
||||
enum MOVEMENT_CONTROL_OPTIONS : uint8_t {
|
||||
MOVEMENT_CONTROL_L_THUMBSTICK = 0,
|
||||
MOVEMENT_CONTROL_R_THUMBSTICK
|
||||
};
|
||||
|
||||
enum YAXIS_INVERSION_OPTIONS : uint8_t {
|
||||
YAXIS_INVERSION_OFF = 0,
|
||||
YAXIS_INVERSION_ON
|
||||
};
|
||||
|
||||
enum TRANSMISSION_OPTIONS : uint8_t {
|
||||
TRANSMISSION_AUTO = 0,
|
||||
TRANSMISSION_MANUAL
|
||||
};
|
||||
|
||||
enum CAMERA_LOCATION_OPTIONS : uint8_t {
|
||||
CAMERA_LOCATION_BEHIND = 0,
|
||||
CAMERA_LOCATION_IN_FRONT,
|
||||
CAMERA_LOCATION_INSIDE
|
||||
};
|
||||
|
||||
enum BRAKE_CONTROL_OPTIONS : uint8_t {
|
||||
BRAKE_CONTROL_TRIGGER = 0,
|
||||
BRAKE_CONTROL_BUTTON
|
||||
};
|
||||
|
||||
enum ACCELERATOR_CONTROL_OPTIONS : uint8_t {
|
||||
ACCELERATOR_CONTROL_TRIGGER = 0,
|
||||
ACCELERATOR_CONTROL_BUTTON
|
||||
};
|
||||
|
||||
class UserSetting : public UserData {
|
||||
public:
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <sstream>
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "third_party/stb/stb_image.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/user_data.h"
|
||||
|
@ -158,6 +159,10 @@ void UserTracker::AddTitleToPlayedList(uint64_t xuid) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!spa_data_->include_in_profile() || spa_data_->is_system_app()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t title_id = spa_data_->title_id();
|
||||
auto title_gpd = user->games_gpd_.find(title_id);
|
||||
if (title_gpd == user->games_gpd_.end()) {
|
||||
|
@ -733,6 +738,35 @@ void UserTracker::UpsertSetting(uint64_t xuid, uint32_t title_id,
|
|||
FlushUserData(xuid);
|
||||
}
|
||||
|
||||
bool UserTracker::UpdateUserIcon(uint64_t xuid,
|
||||
std::span<const uint8_t> icon_data) {
|
||||
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int width, height, channels;
|
||||
if (!stbi_info_from_memory(icon_data.data(),
|
||||
static_cast<int>(icon_data.size()), &width,
|
||||
&height, &channels)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XTileType icon_type = XTileType::kGameIcon;
|
||||
|
||||
if (std::pair<uint16_t, uint16_t>(width, height) == kProfileIconSize) {
|
||||
icon_type = XTileType::kGamerTile;
|
||||
} else if (std::pair<uint16_t, uint16_t>(width, height) ==
|
||||
kProfileIconSizeSmall) {
|
||||
icon_type = XTileType::kGamerTileSmall;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
user->WriteProfileIcon(icon_type, icon_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::span<const uint8_t> UserTracker::GetIcon(uint64_t xuid, uint32_t title_id,
|
||||
XTileType tile_type,
|
||||
uint64_t tile_id) const {
|
||||
|
|
|
@ -75,6 +75,9 @@ class UserTracker {
|
|||
void UpsertSetting(uint64_t xuid, uint32_t title_id,
|
||||
const UserSetting* setting);
|
||||
|
||||
std::optional<UserSetting> GetSetting(UserProfile* user, uint32_t title_id,
|
||||
uint32_t setting_id) const;
|
||||
|
||||
bool GetUserSetting(uint64_t xuid, uint32_t title_id, uint32_t setting_id,
|
||||
X_USER_PROFILE_SETTING* setting_ptr,
|
||||
uint32_t& extended_data_address) const;
|
||||
|
@ -92,6 +95,8 @@ class UserTracker {
|
|||
uint32_t achievement_id) const;
|
||||
|
||||
// Images
|
||||
bool UpdateUserIcon(uint64_t xuid, std::span<const uint8_t> icon_data);
|
||||
|
||||
std::span<const uint8_t> GetIcon(uint64_t xuid, uint32_t title_id,
|
||||
XTileType tile_type, uint64_t tile_id) const;
|
||||
|
||||
|
@ -100,9 +105,6 @@ class UserTracker {
|
|||
|
||||
void UpdateSettingValue(uint64_t xuid, uint32_t title_id,
|
||||
UserSettingId setting_id, int32_t difference);
|
||||
|
||||
std::optional<UserSetting> GetSetting(UserProfile* user, uint32_t title_id,
|
||||
uint32_t setting_id) const;
|
||||
std::optional<UserSetting> GetGpdSetting(UserProfile* user, uint32_t title_id,
|
||||
uint32_t setting_id) const;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_XAM_UI_H_
|
||||
#define XENIA_KERNEL_XAM_XAM_UI_H_
|
||||
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
class XamDialog : public xe::ui::ImGuiDialog {
|
||||
public:
|
||||
void set_close_callback(std::function<void()> close_callback) {
|
||||
close_callback_ = close_callback;
|
||||
}
|
||||
|
||||
protected:
|
||||
XamDialog(xe::ui::ImGuiDrawer* imgui_drawer)
|
||||
: xe::ui::ImGuiDialog(imgui_drawer) {}
|
||||
|
||||
virtual ~XamDialog() {}
|
||||
void OnClose() override {
|
||||
if (close_callback_) {
|
||||
close_callback_();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> close_callback_ = nullptr;
|
||||
};
|
||||
|
||||
class MessageBoxDialog : public XamDialog {
|
||||
public:
|
||||
MessageBoxDialog(xe::ui::ImGuiDrawer* imgui_drawer, std::string& title,
|
||||
std::string& description, std::vector<std::string> buttons,
|
||||
uint32_t default_button)
|
||||
: XamDialog(imgui_drawer),
|
||||
title_(title),
|
||||
description_(description),
|
||||
buttons_(std::move(buttons)),
|
||||
default_button_(default_button),
|
||||
chosen_button_(default_button) {
|
||||
if (!title_.size()) {
|
||||
title_ = "Message Box";
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t chosen_button() const { return chosen_button_; }
|
||||
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
virtual ~MessageBoxDialog() {}
|
||||
|
||||
private:
|
||||
bool has_opened_ = false;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
std::vector<std::string> buttons_;
|
||||
uint32_t default_button_ = 0;
|
||||
uint32_t chosen_button_ = 0;
|
||||
};
|
||||
|
||||
class KeyboardInputDialog : public XamDialog {
|
||||
public:
|
||||
KeyboardInputDialog(xe::ui::ImGuiDrawer* imgui_drawer, std::string& title,
|
||||
std::string& description, std::string& default_text,
|
||||
size_t max_length)
|
||||
: XamDialog(imgui_drawer),
|
||||
title_(title),
|
||||
description_(description),
|
||||
default_text_(default_text),
|
||||
max_length_(max_length),
|
||||
text_buffer_() {
|
||||
if (!title_.size()) {
|
||||
if (!description_.size()) {
|
||||
title_ = "Keyboard Input";
|
||||
} else {
|
||||
title_ = description_;
|
||||
description_ = "";
|
||||
}
|
||||
}
|
||||
text_ = default_text;
|
||||
text_buffer_.resize(max_length);
|
||||
xe::string_util::copy_truncating(text_buffer_.data(), default_text_,
|
||||
text_buffer_.size());
|
||||
}
|
||||
virtual ~KeyboardInputDialog() {}
|
||||
|
||||
const std::string& text() const { return text_; }
|
||||
bool cancelled() const { return cancelled_; }
|
||||
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
private:
|
||||
bool has_opened_ = false;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
std::string default_text_;
|
||||
size_t max_length_ = 0;
|
||||
std::vector<char> text_buffer_;
|
||||
std::string text_ = "";
|
||||
bool cancelled_ = true;
|
||||
};
|
||||
|
||||
bool xeDrawProfileContent(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
const uint64_t xuid, const uint8_t user_index,
|
||||
const X_XAMACCOUNTINFO* account,
|
||||
const xe::ui::ImmediateTexture* profile_icon,
|
||||
std::function<bool()> context_menu,
|
||||
std::function<void()> on_profile_change,
|
||||
uint64_t* selected_xuid);
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
|
@ -461,8 +461,21 @@ dword_result_t XamUserContentRestrictionCheckAccess_entry(
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserContentRestrictionCheckAccess, kUserProfiles, kStub);
|
||||
|
||||
dword_result_t XamUserIsOnlineEnabled_entry(dword_t user_index) { return 1; }
|
||||
DECLARE_XAM_EXPORT1(XamUserIsOnlineEnabled, kUserProfiles, kStub);
|
||||
dword_result_t XamUserIsOnlineEnabled_entry(dword_t user_index) {
|
||||
if (user_index >= XUserMaxUserCount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return kernel_state()
|
||||
->xam_state()
|
||||
->GetUserProfile(user_index)
|
||||
->IsLiveEnabled();
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserIsOnlineEnabled, kUserProfiles, kImplemented);
|
||||
|
||||
dword_result_t XamUserGetMembershipTier_entry(dword_t user_index) {
|
||||
if (user_index >= XUserMaxUserCount) {
|
||||
|
@ -473,9 +486,23 @@ dword_result_t XamUserGetMembershipTier_entry(dword_t user_index) {
|
|||
return X_ERROR_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
return X_XAMACCOUNTINFO::AccountSubscriptionTier::kSubscriptionTierGold;
|
||||
return kernel_state()
|
||||
->xam_state()
|
||||
->GetUserProfile(user_index)
|
||||
->GetSubscriptionTier();
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserGetMembershipTier, kUserProfiles, kStub);
|
||||
DECLARE_XAM_EXPORT1(XamUserGetMembershipTier, kUserProfiles, kImplemented);
|
||||
|
||||
dword_result_t XamUserGetMembershipTierFromXUID_entry(qword_t xuid) {
|
||||
const auto profile = kernel_state()->xam_state()->GetUserProfile(xuid);
|
||||
if (!profile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return profile->GetSubscriptionTier();
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserGetMembershipTierFromXUID, kUserProfiles,
|
||||
kImplemented);
|
||||
|
||||
dword_result_t XamUserAreUsersFriends_entry(dword_t user_index, dword_t unk1,
|
||||
dword_t unk2, lpdword_t out_value,
|
||||
|
@ -868,15 +895,25 @@ dword_result_t XamUserIsUnsafeProgrammingAllowed_entry(dword_t user_index,
|
|||
DECLARE_XAM_EXPORT1(XamUserIsUnsafeProgrammingAllowed, kUserProfiles, kStub);
|
||||
|
||||
dword_result_t XamUserGetSubscriptionType_entry(dword_t user_index,
|
||||
dword_t unk2, dword_t unk3) {
|
||||
lpdword_t subscription_ptr,
|
||||
lpdword_t r5,
|
||||
dword_t overlapped_ptr) {
|
||||
if (user_index >= XUserMaxUserCount) {
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!unk2 || !unk3) {
|
||||
if (!subscription_ptr || !r5) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
|
||||
auto user = kernel_state()->xam_state()->GetUserProfile(user_index);
|
||||
if (!user) {
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*subscription_ptr = user->GetSubscriptionTier();
|
||||
*r5 = 0x0;
|
||||
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserGetSubscriptionType, kUserProfiles, kStub);
|
||||
|
@ -894,12 +931,11 @@ dword_result_t XamUserGetUserFlags_entry(dword_t user_index) {
|
|||
DECLARE_XAM_EXPORT1(XamUserGetUserFlags, kUserProfiles, kImplemented);
|
||||
|
||||
dword_result_t XamUserGetUserFlagsFromXUID_entry(qword_t xuid) {
|
||||
if (!kernel_state()->xam_state()->IsUserSignedIn(xuid)) {
|
||||
const auto& user_profile = kernel_state()->xam_state()->GetUserProfile(xuid);
|
||||
if (!user_profile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& user_profile = kernel_state()->xam_state()->GetUserProfile(xuid);
|
||||
|
||||
return user_profile->GetCachedFlags();
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserGetUserFlagsFromXUID, kUserProfiles, kImplemented);
|
||||
|
@ -961,6 +997,45 @@ dword_result_t XamUserCreateStatsEnumerator_entry(
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserCreateStatsEnumerator, kUserProfiles, kSketchy);
|
||||
|
||||
dword_result_t XamUserGetUserTenure_entry(dword_t user_index,
|
||||
lpdword_t tenure_level_ptr,
|
||||
lpdword_t milestone_ptr,
|
||||
lpqword_t milestone_date_ptr,
|
||||
dword_t overlap_ptr) {
|
||||
if (!kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
|
||||
const auto& user_profile =
|
||||
kernel_state()->xam_state()->GetUserProfile(user_index);
|
||||
|
||||
if (const auto setting =
|
||||
kernel_state()->xam_state()->user_tracker()->GetSetting(
|
||||
user_profile, kDashboardID,
|
||||
static_cast<uint32_t>(UserSettingId::XPROFILE_TENURE_LEVEL))) {
|
||||
*tenure_level_ptr = std::get<int32_t>(setting->get_host_data());
|
||||
}
|
||||
|
||||
if (const auto setting =
|
||||
kernel_state()->xam_state()->user_tracker()->GetSetting(
|
||||
user_profile, kDashboardID,
|
||||
static_cast<uint32_t>(
|
||||
UserSettingId::XPROFILE_TENURE_MILESTONE))) {
|
||||
*milestone_ptr = std::get<int32_t>(setting->get_host_data());
|
||||
}
|
||||
|
||||
if (const auto setting =
|
||||
kernel_state()->xam_state()->user_tracker()->GetSetting(
|
||||
user_profile, kDashboardID,
|
||||
static_cast<uint32_t>(
|
||||
UserSettingId::XPROFILE_TENURE_NEXT_MILESTONE_DATE))) {
|
||||
*milestone_date_ptr = std::get<int64_t>(setting->get_host_data());
|
||||
}
|
||||
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserGetUserTenure, kUserProfiles, kImplemented);
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
|
|
@ -38,6 +38,8 @@ enum class TitleType : uint32_t {
|
|||
kFull = 1,
|
||||
kDemo = 2,
|
||||
kDownload = 3,
|
||||
kUnknown = 4,
|
||||
kApp = 5
|
||||
};
|
||||
|
||||
enum class TitleFlags {
|
||||
|
|
|
@ -37,7 +37,7 @@ class Window;
|
|||
|
||||
using IconsData = std::map<uint32_t, std::span<const uint8_t>>;
|
||||
|
||||
constexpr ImVec2 default_image_icon_size = ImVec2(75.f, 75.f);
|
||||
constexpr ImVec2 default_image_icon_size = ImVec2(64.f, 64.f);
|
||||
|
||||
class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
public:
|
||||
|
|
154
src/xenia/xbox.h
154
src/xenia/xbox.h
|
@ -547,6 +547,117 @@ enum class XLanguage : uint32_t {
|
|||
kMaxLanguages = 13
|
||||
};
|
||||
|
||||
enum class XOnlineCountry : uint32_t {
|
||||
kUnitedArabEmirates = 1,
|
||||
kAlbania = 2,
|
||||
kArmenia = 3,
|
||||
kArgentina = 4,
|
||||
kAustria = 5,
|
||||
kAustralia = 6,
|
||||
kAzerbaijan = 7,
|
||||
kBelgium = 8,
|
||||
kBulgaria = 9,
|
||||
kBahrain = 10,
|
||||
kBruneiDarussalam = 11,
|
||||
kBolivia = 12,
|
||||
kBrazil = 13,
|
||||
kBelarus = 14,
|
||||
kBelize = 15,
|
||||
kCanada = 16,
|
||||
kSwitzerland = 18,
|
||||
kChile = 19,
|
||||
kChina = 20,
|
||||
kColombia = 21,
|
||||
kCostaRica = 22,
|
||||
kCzechRepublic = 23,
|
||||
kGermany = 24,
|
||||
kDenmark = 25,
|
||||
kDominicanRepublic = 26,
|
||||
kAlgeria = 27,
|
||||
kEcuador = 28,
|
||||
kEstonia = 29,
|
||||
kEgypt = 30,
|
||||
kSpain = 31,
|
||||
kFinland = 32,
|
||||
kFaroeIslands = 33,
|
||||
kFrance = 34,
|
||||
kGreatBritain = 35,
|
||||
kGeorgia = 36,
|
||||
kGreece = 37,
|
||||
kGuatemala = 38,
|
||||
kHongKong = 39,
|
||||
kHonduras = 40,
|
||||
kCroatia = 41,
|
||||
kHungary = 42,
|
||||
kIndonesia = 43,
|
||||
kIreland = 44,
|
||||
kIsrael = 45,
|
||||
kIndia = 46,
|
||||
kIraq = 47,
|
||||
kIran = 48,
|
||||
kIceland = 49,
|
||||
kItaly = 50,
|
||||
kJamaica = 51,
|
||||
kJordan = 52,
|
||||
kJapan = 53,
|
||||
kKenya = 54,
|
||||
kKyrgyzstan = 55,
|
||||
kKorea = 56,
|
||||
kKuwait = 57,
|
||||
kKazakhstan = 58,
|
||||
kLebanon = 59,
|
||||
kLiechtenstein = 60,
|
||||
kLithuania = 61,
|
||||
kLuxembourg = 62,
|
||||
kLatvia = 63,
|
||||
kLibya = 64,
|
||||
kMorocco = 65,
|
||||
kMonaco = 66,
|
||||
kMacedonia = 67,
|
||||
kMongolia = 68,
|
||||
kMacau = 69,
|
||||
kMaldives = 70,
|
||||
kMexico = 71,
|
||||
kMalaysia = 72,
|
||||
kNicaragua = 73,
|
||||
kNetherlands = 74,
|
||||
kNorway = 75,
|
||||
kNewZealand = 76,
|
||||
kOman = 77,
|
||||
kPanama = 78,
|
||||
kPeru = 79,
|
||||
kPhilippines = 80,
|
||||
kPakistan = 81,
|
||||
kPoland = 82,
|
||||
kPuertoRico = 83,
|
||||
kPortugal = 84,
|
||||
kParaguay = 85,
|
||||
kQatar = 86,
|
||||
kRomania = 87,
|
||||
kRussianFederation = 88,
|
||||
kSaudiArabia = 89,
|
||||
kSweden = 90,
|
||||
kSingapore = 91,
|
||||
kSlovenia = 92,
|
||||
kSlovakRepublic = 93,
|
||||
kElSalvador = 95,
|
||||
kSyria = 96,
|
||||
kThailand = 97,
|
||||
kTunisia = 98,
|
||||
kTurkey = 99,
|
||||
kTrinidadAndTobago = 100,
|
||||
kTaiwan = 101,
|
||||
kUkraine = 102,
|
||||
kUnitedStates = 103,
|
||||
kUruguay = 104,
|
||||
kUzbekistan = 105,
|
||||
kVenezuela = 106,
|
||||
kVietNam = 107,
|
||||
kYemen = 108,
|
||||
kSouthAfrica = 109,
|
||||
kZimbabwe = 110
|
||||
};
|
||||
|
||||
enum class XContentType : uint32_t {
|
||||
kInvalid = 0x00000000,
|
||||
kSavedGame = 0x00000001,
|
||||
|
@ -706,20 +817,26 @@ struct X_XAMACCOUNTINFO {
|
|||
char passport_password[0x20];
|
||||
char owner_passport_membername[0x72];
|
||||
|
||||
bool IsPasscodeEnabled() {
|
||||
bool IsPasscodeEnabled() const {
|
||||
return static_cast<bool>(reserved_flags &
|
||||
AccountReservedFlags::kPasswordProtected);
|
||||
}
|
||||
|
||||
bool IsLiveEnabled() {
|
||||
bool IsLiveEnabled() const {
|
||||
return static_cast<bool>(reserved_flags &
|
||||
AccountReservedFlags::kLiveEnabled);
|
||||
}
|
||||
|
||||
uint64_t GetOnlineXUID() const { return xuid_online; }
|
||||
|
||||
std::string_view GetOnlineDomain() const {
|
||||
return std::string_view(online_domain);
|
||||
}
|
||||
|
||||
uint32_t GetCachedFlags() const { return cached_user_flags; };
|
||||
|
||||
uint32_t GetCountry() const {
|
||||
return (cached_user_flags & kCountryMask) >> 8;
|
||||
XOnlineCountry GetCountry() const {
|
||||
return static_cast<XOnlineCountry>((cached_user_flags & kCountryMask) >> 8);
|
||||
}
|
||||
|
||||
AccountSubscriptionTier GetSubscriptionTier() const {
|
||||
|
@ -734,6 +851,35 @@ struct X_XAMACCOUNTINFO {
|
|||
std::string GetGamertagString() const {
|
||||
return xe::to_utf8(std::u16string(gamertag));
|
||||
}
|
||||
|
||||
void ToggleLiveFlag(bool is_live) {
|
||||
reserved_flags = reserved_flags & ~AccountReservedFlags::kLiveEnabled;
|
||||
|
||||
if (is_live) {
|
||||
reserved_flags = reserved_flags | AccountReservedFlags::kLiveEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
void SetCountry(XOnlineCountry country) {
|
||||
cached_user_flags = cached_user_flags & ~kCountryMask;
|
||||
cached_user_flags = cached_user_flags |
|
||||
(static_cast<uint32_t>(country) << 8) & kCountryMask;
|
||||
}
|
||||
|
||||
void SetLanguage(XLanguage language) {
|
||||
cached_user_flags = cached_user_flags & ~kLanguageMask;
|
||||
|
||||
cached_user_flags = cached_user_flags |
|
||||
(static_cast<uint32_t>(language) << 25) & kLanguageMask;
|
||||
}
|
||||
|
||||
void SetSubscriptionTier(AccountSubscriptionTier sub_tier) {
|
||||
cached_user_flags = cached_user_flags & ~kSubscriptionTierMask;
|
||||
|
||||
cached_user_flags =
|
||||
cached_user_flags |
|
||||
(static_cast<uint32_t>(sub_tier) << 20) & kSubscriptionTierMask;
|
||||
}
|
||||
};
|
||||
static_assert_size(X_XAMACCOUNTINFO, 0x17C);
|
||||
#pragma pack(pop)
|
||||
|
|
Loading…
Reference in New Issue