862 lines
24 KiB
C++
862 lines
24 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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/emulator.h"
|
|
#include "xenia/kernel/xam/user_profile.h"
|
|
|
|
#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"
|
|
#include "xenia/kernel/xam/user_property.h"
|
|
#include "xenia/kernel/xam/user_settings.h"
|
|
#include "xenia/kernel/xam/user_tracker.h"
|
|
#include "xenia/kernel/xam/xdbf/gpd_info.h"
|
|
|
|
DECLARE_int32(user_language);
|
|
|
|
namespace xe {
|
|
namespace kernel {
|
|
namespace xam {
|
|
|
|
bool UserTracker::AddUser(uint64_t xuid) {
|
|
if (IsUserTracked(xuid)) {
|
|
XELOGW("{}: User is already on tracking list!", __func__);
|
|
return false;
|
|
}
|
|
|
|
tracked_xuids_.insert(xuid);
|
|
|
|
if (spa_data_) {
|
|
AddTitleToPlayedList(xuid);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UserTracker::RemoveUser(uint64_t xuid) {
|
|
if (!IsUserTracked(xuid)) {
|
|
XELOGW("{}: User is not on tracking list!", __func__);
|
|
return false;
|
|
}
|
|
|
|
tracked_xuids_.erase(xuid);
|
|
FlushUserData(xuid);
|
|
return true;
|
|
}
|
|
|
|
bool UserTracker::UnlockAchievement(uint64_t xuid, uint32_t achievement_id) {
|
|
if (!IsUserTracked(xuid)) {
|
|
XELOGW("{}: User is not on tracking list!", __func__);
|
|
return false;
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return false;
|
|
}
|
|
|
|
if (!spa_data_) {
|
|
XELOGW("{}: Missing title SPA.", __func__);
|
|
return false;
|
|
}
|
|
|
|
const auto spa_achievement = spa_data_->GetAchievement(achievement_id);
|
|
if (!spa_achievement) {
|
|
XELOGW("{}: Missing achievement data in SPA.", __func__);
|
|
return false;
|
|
}
|
|
|
|
// Update data in profile gpd.
|
|
auto title_info = user->dashboard_gpd_.GetTitleInfo(spa_data_->title_id());
|
|
if (!title_info) {
|
|
return false;
|
|
}
|
|
|
|
// Update title gpd
|
|
auto title_gpd = &user->games_gpd_[spa_data_->title_id()];
|
|
// Achievement is unlocked, so we need to add achievement icon
|
|
title_gpd->AddImage(spa_achievement->image_id,
|
|
spa_data_->GetIcon(spa_achievement->image_id));
|
|
|
|
auto gpd_achievement = title_gpd->GetAchievementEntry(spa_achievement->id);
|
|
if (!gpd_achievement) {
|
|
XELOGW(
|
|
"{}: Missing achievement data in title GPD. (User: {} Title: {:08X})",
|
|
__func__, user->name(), spa_data_->title_id());
|
|
return false;
|
|
}
|
|
|
|
title_info->achievements_unlocked++;
|
|
title_info->gamerscore_earned += spa_achievement->gamerscore;
|
|
|
|
const std::string achievement_name = spa_data_->GetStringTableEntry(
|
|
spa_data_->default_language(), spa_achievement->label_id);
|
|
|
|
XELOGI("Player: {} Unlocked Achievement: {}", user->name(),
|
|
achievement_name.c_str());
|
|
|
|
gpd_achievement->flags = gpd_achievement->flags |
|
|
static_cast<uint32_t>(AchievementFlags::kAchieved);
|
|
gpd_achievement->unlock_time = Clock::QueryGuestSystemTime();
|
|
|
|
UpdateSettingValue(xuid, kDashboardID, UserSettingId::XPROFILE_GAMERCARD_CRED,
|
|
gpd_achievement->gamerscore);
|
|
UpdateSettingValue(xuid, kDashboardID,
|
|
UserSettingId::XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED, 1);
|
|
UpdateSettingValue(xuid, spa_data_->title_id(),
|
|
UserSettingId::XPROFILE_GAMERCARD_TITLE_CRED_EARNED,
|
|
gpd_achievement->gamerscore);
|
|
UpdateSettingValue(
|
|
xuid, spa_data_->title_id(),
|
|
UserSettingId::XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED, 1);
|
|
|
|
FlushUserData(xuid);
|
|
return true;
|
|
}
|
|
|
|
void UserTracker::FlushUserData(const uint64_t xuid) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
user->WriteGpd(kDashboardID);
|
|
|
|
if (spa_data_) {
|
|
user->WriteGpd(spa_data_->title_id());
|
|
}
|
|
}
|
|
|
|
void UserTracker::AddTitleToPlayedList() {
|
|
if (!spa_data_) {
|
|
return;
|
|
}
|
|
|
|
for (const uint64_t xuid : tracked_xuids_) {
|
|
AddTitleToPlayedList(xuid);
|
|
}
|
|
}
|
|
|
|
void UserTracker::AddTitleToPlayedList(uint64_t xuid) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
if (!spa_data_) {
|
|
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()) {
|
|
user->games_gpd_.emplace(title_id, GpdInfoTitle(title_id));
|
|
UpdateTitleGpdFile();
|
|
}
|
|
|
|
if (!spa_data_->include_in_profile()) {
|
|
return;
|
|
}
|
|
|
|
const uint64_t current_time = Clock::QueryGuestSystemTime();
|
|
|
|
auto title_info = user->dashboard_gpd_.GetTitleInfo(title_id);
|
|
if (!title_info) {
|
|
user->dashboard_gpd_.AddNewTitle(spa_data_);
|
|
UpdateSettingValue(xuid, kDashboardID,
|
|
UserSettingId::XPROFILE_GAMERCARD_TITLES_PLAYED, 1);
|
|
title_info = user->dashboard_gpd_.GetTitleInfo(title_id);
|
|
}
|
|
// Normally we only need to update last booted time. Everything else is filled
|
|
// during creation time OR SPA UPDATE TIME!
|
|
title_info->last_played = current_time;
|
|
|
|
UpdateProfileGpd();
|
|
}
|
|
|
|
void UserTracker::RemoveTitleFromPlayedList(uint64_t xuid, uint32_t title_id) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
if (user->dashboard_gpd_.RemoveTitle(title_id)) {
|
|
UpdateSettingValue(xuid, kDashboardID,
|
|
UserSettingId::XPROFILE_GAMERCARD_TITLES_PLAYED, -1);
|
|
FlushUserData(xuid);
|
|
}
|
|
}
|
|
|
|
// Privates
|
|
bool UserTracker::IsUserTracked(uint64_t xuid) const {
|
|
return tracked_xuids_.find(xuid) != tracked_xuids_.cend();
|
|
}
|
|
|
|
std::optional<TitleInfo> UserTracker::GetUserTitleInfo(
|
|
uint64_t xuid, uint32_t title_id) const {
|
|
if (!IsUserTracked(xuid)) {
|
|
XELOGW("{}: User is not on tracking list!", __func__);
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto title_data = user->dashboard_gpd_.GetTitleInfo(title_id);
|
|
if (!title_data) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto game_gpd = user->games_gpd_.find(title_id);
|
|
if (game_gpd == user->games_gpd_.cend()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
TitleInfo info;
|
|
info.id = title_data->title_id;
|
|
info.achievements_count = title_data->achievements_count;
|
|
info.unlocked_achievements_count = title_data->achievements_unlocked;
|
|
info.gamerscore_amount = title_data->gamerscore_total;
|
|
info.title_earned_gamerscore = title_data->gamerscore_earned;
|
|
info.title_name = user->dashboard_gpd_.GetTitleName(title_id);
|
|
info.icon = game_gpd->second.GetImage(kXdbfIdTitle);
|
|
|
|
if (title_data->last_played.is_valid()) {
|
|
info.last_played = chrono::WinSystemClock::to_local(
|
|
title_data->last_played.to_time_point());
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
std::vector<TitleInfo> UserTracker::GetPlayedTitles(uint64_t xuid) const {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<TitleInfo> played_titles;
|
|
|
|
const auto titles_data = user->dashboard_gpd_.GetTitlesInfo();
|
|
for (const auto& title_data : titles_data) {
|
|
if (!title_data->include_in_enumerator()) {
|
|
continue;
|
|
}
|
|
|
|
TitleInfo info;
|
|
info.id = title_data->title_id;
|
|
info.achievements_count = title_data->achievements_count;
|
|
info.unlocked_achievements_count = title_data->achievements_unlocked;
|
|
info.gamerscore_amount = title_data->gamerscore_total;
|
|
info.title_earned_gamerscore = title_data->gamerscore_earned;
|
|
info.flags = title_data->flags;
|
|
info.all_avatar_awards = title_data->all_avatar_awards;
|
|
info.male_avatar_awards = title_data->male_avatar_awards;
|
|
info.female_avatar_awards = title_data->female_avatar_awards;
|
|
info.online_unlocked_achievements = title_data->online_achievement_count;
|
|
info.title_name = user->dashboard_gpd_.GetTitleName(title_data->title_id);
|
|
|
|
if (title_data->last_played.is_valid()) {
|
|
info.last_played = chrono::WinSystemClock::to_local(
|
|
title_data->last_played.to_time_point());
|
|
}
|
|
|
|
auto game_gpd = user->games_gpd_.find(title_data->title_id);
|
|
if (game_gpd != user->games_gpd_.cend()) {
|
|
info.icon = game_gpd->second.GetImage(kXdbfIdTitle);
|
|
}
|
|
|
|
played_titles.push_back(info);
|
|
}
|
|
|
|
std::sort(played_titles.begin(), played_titles.end(),
|
|
[](const TitleInfo& first, const TitleInfo& second) {
|
|
return first.last_played > second.last_played;
|
|
});
|
|
|
|
return played_titles;
|
|
}
|
|
|
|
void UserTracker::UpdateMissingAchievemntsIcons() {
|
|
for (auto& user_xuid : tracked_xuids_) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(user_xuid);
|
|
if (!user) {
|
|
continue;
|
|
}
|
|
|
|
auto game_gpd = user->games_gpd_.find(spa_data_->title_id());
|
|
if (game_gpd == user->games_gpd_.cend()) {
|
|
continue;
|
|
}
|
|
|
|
for (const auto& id : game_gpd->second.GetAchievementsIds()) {
|
|
const auto entry = game_gpd->second.GetAchievementEntry(id);
|
|
|
|
if (!entry) {
|
|
continue;
|
|
}
|
|
|
|
if (!entry->is_achievement_unlocked()) {
|
|
continue;
|
|
}
|
|
|
|
if (!game_gpd->second.GetImage(entry->image_id).empty()) {
|
|
continue;
|
|
}
|
|
|
|
game_gpd->second.AddImage(entry->image_id,
|
|
spa_data_->GetIcon(entry->image_id));
|
|
}
|
|
user->WriteGpd(spa_data_->title_id());
|
|
}
|
|
}
|
|
|
|
void UserTracker::UpdateSpaInfo(SpaInfo* spa_info) {
|
|
spa_data_ = spa_info;
|
|
|
|
if (!spa_data_) {
|
|
return;
|
|
}
|
|
|
|
UpdateProfileGpd();
|
|
UpdateTitleGpdFile();
|
|
UpdateMissingAchievemntsIcons();
|
|
}
|
|
|
|
void UserTracker::UpdateTitleGpdFile() {
|
|
for (auto& user_xuid : tracked_xuids_) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(user_xuid);
|
|
if (!user) {
|
|
continue;
|
|
}
|
|
|
|
auto game_gpd = user->games_gpd_.find(spa_data_->title_id());
|
|
if (game_gpd == user->games_gpd_.cend()) {
|
|
continue;
|
|
}
|
|
|
|
auto user_language = spa_data_->GetExistingLanguage(
|
|
static_cast<XLanguage>(cvars::user_language));
|
|
|
|
// First add achievements because of lowest ID
|
|
for (const auto& entry : spa_data_->GetAchievements()) {
|
|
AchievementDetails details(entry, spa_data_, user_language);
|
|
game_gpd->second.AddAchievement(&details);
|
|
}
|
|
|
|
// Then add game icon
|
|
game_gpd->second.AddImage(kXdbfIdTitle, spa_data_->title_icon());
|
|
|
|
// At the end add title name entry
|
|
game_gpd->second.AddString(kXdbfIdTitle,
|
|
xe::to_utf16(spa_data_->title_name()));
|
|
|
|
// Check if we have icon for every unlocked achievements.
|
|
FlushUserData(user_xuid);
|
|
}
|
|
}
|
|
|
|
void UserTracker::UpdateProfileGpd() {
|
|
for (auto& user_xuid : tracked_xuids_) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(user_xuid);
|
|
if (!user) {
|
|
continue;
|
|
}
|
|
|
|
auto title_data = user->dashboard_gpd_.GetTitleInfo(spa_data_->title_id());
|
|
if (!title_data) {
|
|
continue;
|
|
}
|
|
|
|
const uint32_t achievements_count = spa_data_->achievement_count();
|
|
// If achievements count doesn't match then obviously gamerscore won't match
|
|
// either
|
|
if (title_data->achievements_count < achievements_count) {
|
|
X_XDBF_GPD_TITLE_PLAYED title_updated_data = *title_data;
|
|
|
|
title_updated_data.achievements_count = achievements_count;
|
|
title_updated_data.gamerscore_total = spa_data_->total_gamerscore();
|
|
user->dashboard_gpd_.UpdateTitleInfo(spa_data_->title_id(),
|
|
&title_updated_data);
|
|
}
|
|
|
|
FlushUserData(user_xuid);
|
|
}
|
|
}
|
|
|
|
std::vector<Achievement> UserTracker::GetUserTitleAchievements(
|
|
uint64_t xuid, uint32_t title_id) const {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return {};
|
|
}
|
|
|
|
auto game_gpd = user->games_gpd_.find(title_id);
|
|
if (game_gpd == user->games_gpd_.cend()) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<Achievement> achievements;
|
|
|
|
for (const uint32_t id : game_gpd->second.GetAchievementsIds()) {
|
|
Achievement achievement(game_gpd->second.GetAchievementEntry(id));
|
|
|
|
achievement.achievement_name = game_gpd->second.GetAchievementTitle(id);
|
|
achievement.unlocked_description =
|
|
game_gpd->second.GetAchievementDescription(id);
|
|
achievement.locked_description =
|
|
game_gpd->second.GetAchievementUnachievedDescription(id);
|
|
|
|
achievements.push_back(std::move(achievement));
|
|
}
|
|
|
|
return achievements;
|
|
};
|
|
|
|
std::span<const uint8_t> UserTracker::GetAchievementIcon(
|
|
uint64_t xuid, uint32_t title_id, uint32_t achievement_id) const {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return {};
|
|
}
|
|
|
|
auto game_gpd = user->games_gpd_.find(title_id);
|
|
if (game_gpd == user->games_gpd_.cend()) {
|
|
return {};
|
|
}
|
|
|
|
const auto entry = game_gpd->second.GetAchievementEntry(achievement_id);
|
|
if (!entry) {
|
|
return {};
|
|
}
|
|
|
|
return GetIcon(xuid, title_id, XTileType::kAchievement, entry->image_id);
|
|
}
|
|
|
|
void UserTracker::AddProperty(const uint64_t xuid, const Property* property) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
const auto property_id = property->GetPropertyId();
|
|
|
|
if (property->IsContext()) {
|
|
const auto context_data = spa_data_->GetContext(property_id.value);
|
|
if (!context_data) {
|
|
return;
|
|
}
|
|
} else {
|
|
const auto property_data = spa_data_->GetProperty(property_id.value);
|
|
if (!property_data) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto entry = std::find_if(user->properties_.begin(), user->properties_.end(),
|
|
[property_id](const Property& property_data) {
|
|
return property_data.GetPropertyId().value ==
|
|
property_id.value;
|
|
});
|
|
|
|
if (entry != user->properties_.end()) {
|
|
*entry = *property;
|
|
return;
|
|
}
|
|
|
|
user->properties_.push_back(*property);
|
|
}
|
|
|
|
X_STATUS UserTracker::GetProperty(const uint64_t xuid, uint32_t* property_size,
|
|
XUSER_PROPERTY* property) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return X_E_NOTFOUND;
|
|
}
|
|
|
|
*property_size = 0;
|
|
const auto& property_id = property->property_id;
|
|
|
|
const auto entry =
|
|
std::find_if(user->properties_.cbegin(), user->properties_.cend(),
|
|
[property_id](const Property& property_data) {
|
|
return property_data.GetPropertyId().value == property_id;
|
|
});
|
|
|
|
if (entry == user->properties_.cend()) {
|
|
return X_E_INVALIDARG;
|
|
}
|
|
|
|
if (entry->requires_additional_data()) {
|
|
if (!property->data.data.binary.ptr) {
|
|
return X_E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
*property_size = static_cast<uint32_t>(entry->get_data_size());
|
|
entry->WriteToGuest(property);
|
|
return X_E_SUCCESS;
|
|
}
|
|
|
|
const Property* UserTracker::GetProperty(const uint64_t xuid,
|
|
const uint32_t id) const {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto entry =
|
|
std::find_if(user->properties_.cbegin(), user->properties_.cend(),
|
|
[id](const Property& property_data) {
|
|
return property_data.GetPropertyId().value == id;
|
|
});
|
|
|
|
if (entry == user->properties_.cend()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return &(*entry);
|
|
}
|
|
|
|
std::optional<UserSetting> UserTracker::GetGpdSetting(
|
|
UserProfile* user, uint32_t title_id, uint32_t setting_id) const {
|
|
auto game_gpd = user->games_gpd_.find(title_id);
|
|
if (game_gpd != user->games_gpd_.cend()) {
|
|
auto setting = game_gpd->second.GetSetting(setting_id);
|
|
if (setting) {
|
|
return std::make_optional<UserSetting>(
|
|
setting, game_gpd->second.GetSettingData(setting_id));
|
|
}
|
|
}
|
|
|
|
auto setting = user->dashboard_gpd_.GetSetting(setting_id);
|
|
if (setting) {
|
|
return std::make_optional<UserSetting>(
|
|
setting, user->dashboard_gpd_.GetSettingData(setting_id));
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<UserSetting> UserTracker::GetSetting(UserProfile* user,
|
|
uint32_t title_id,
|
|
uint32_t setting_id) const {
|
|
auto gpd_setting = GetGpdSetting(user, title_id, setting_id);
|
|
if (gpd_setting) {
|
|
return gpd_setting.value();
|
|
}
|
|
|
|
return UserSetting::GetDefaultSetting(user, setting_id);
|
|
}
|
|
|
|
bool UserTracker::GetUserSetting(uint64_t xuid, uint32_t title_id,
|
|
uint32_t setting_id,
|
|
X_USER_PROFILE_SETTING* setting_ptr,
|
|
uint32_t& extended_data_address) const {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return false;
|
|
}
|
|
|
|
auto setting = GetSetting(user, title_id, setting_id);
|
|
if (!setting) {
|
|
return false;
|
|
}
|
|
setting_ptr->setting_id = setting_id;
|
|
setting_ptr->source = setting->get_setting_source();
|
|
|
|
setting->WriteToGuest(setting_ptr, extended_data_address);
|
|
return true;
|
|
}
|
|
|
|
void UserTracker::UpdateContext(uint64_t xuid, uint32_t id, uint32_t value) {
|
|
if (!IsUserTracked(xuid)) {
|
|
return;
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
const auto& context_data = spa_data_->GetContext(id);
|
|
if (!context_data) {
|
|
return;
|
|
}
|
|
|
|
if (value > context_data->max_value) {
|
|
return;
|
|
}
|
|
|
|
const auto entry =
|
|
std::find_if(user->properties_.begin(), user->properties_.end(),
|
|
[id](const Property& property_data) {
|
|
return property_data.IsContext() &&
|
|
property_data.GetPropertyId().value == id;
|
|
});
|
|
|
|
if (entry != user->properties_.cend()) {
|
|
*entry = Property(id, value);
|
|
return;
|
|
}
|
|
|
|
user->properties_.push_back(Property(id, value));
|
|
}
|
|
|
|
std::optional<uint32_t> UserTracker::GetUserContext(uint64_t xuid,
|
|
uint32_t id) const {
|
|
if (!IsUserTracked(xuid)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto& context_data = spa_data_->GetContext(id);
|
|
if (!context_data) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto entry = std::find_if(
|
|
user->properties_.cbegin(), user->properties_.cend(),
|
|
[id](const Property& property_data) {
|
|
return property_data.get_type() == X_USER_DATA_TYPE::CONTEXT &&
|
|
property_data.GetPropertyId().value == id;
|
|
});
|
|
|
|
if (entry == user->properties_.cend()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return entry->get_data()->data.u32;
|
|
}
|
|
|
|
std::vector<AttributeKey> UserTracker::GetUserContextIds(uint64_t xuid) const {
|
|
if (!IsUserTracked(xuid)) {
|
|
return {};
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<AttributeKey> entries;
|
|
|
|
for (const auto& property : user->properties_) {
|
|
if (!property.IsContext()) {
|
|
continue;
|
|
}
|
|
|
|
entries.push_back(property.GetPropertyId());
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
std::vector<AttributeKey> UserTracker::GetUserPropertyIds(uint64_t xuid) const {
|
|
if (!IsUserTracked(xuid)) {
|
|
return {};
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<AttributeKey> entries;
|
|
|
|
for (const auto& property : user->properties_) {
|
|
if (property.IsContext()) {
|
|
continue;
|
|
}
|
|
|
|
entries.push_back(property.GetPropertyId());
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
void UserTracker::UpdateSettingValue(uint64_t xuid, uint32_t title_id,
|
|
UserSettingId setting_id,
|
|
int32_t difference) {
|
|
if (!IsUserTracked(xuid)) {
|
|
return;
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
GpdInfo* info = user->GetGpd(title_id);
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
auto setting = info->GetSetting(static_cast<uint32_t>(setting_id));
|
|
|
|
if (!setting) {
|
|
UserSetting new_setting(setting_id, difference);
|
|
info->UpsertSetting(&new_setting);
|
|
return;
|
|
}
|
|
|
|
const int32_t new_value = setting->base_data.s32 + difference;
|
|
UserSetting new_setting(setting_id, new_value);
|
|
info->UpsertSetting(&new_setting);
|
|
}
|
|
|
|
void UserTracker::UpsertSetting(uint64_t xuid, uint32_t title_id,
|
|
const UserSetting* setting) {
|
|
if (!IsUserTracked(xuid)) {
|
|
return;
|
|
}
|
|
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
// Sometimes games like to ignore providing expicitly title_id, so we need to
|
|
// check it.
|
|
if (!title_id) {
|
|
title_id = spa_data_->title_id();
|
|
}
|
|
|
|
GpdInfo* info = user->GetGpd(title_id);
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
info->UpsertSetting(setting);
|
|
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 {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return {};
|
|
}
|
|
|
|
if (!title_id) {
|
|
if (kernel_state()->emulator()->is_title_open()) {
|
|
title_id = kernel_state()->title_id();
|
|
}
|
|
}
|
|
|
|
switch (tile_type) {
|
|
case XTileType::kAchievement: {
|
|
if (title_id == kernel_state()->title_id()) {
|
|
return spa_data_->GetIcon(tile_id);
|
|
} else {
|
|
const auto gpd = user->GetGpd(title_id);
|
|
if (!gpd) {
|
|
return {};
|
|
}
|
|
|
|
return gpd->GetImage(tile_id);
|
|
}
|
|
}
|
|
case XTileType::kGameIcon: {
|
|
const auto gpd = user->GetGpd(title_id);
|
|
if (!gpd) {
|
|
return {};
|
|
}
|
|
|
|
return gpd->GetImage(tile_id);
|
|
}
|
|
case XTileType::kGamerTile:
|
|
case XTileType::kGamerTileSmall:
|
|
case XTileType::kLocalGamerTile:
|
|
case XTileType::kLocalGamerTileSmall:
|
|
case XTileType::kPersonalGamerTile:
|
|
case XTileType::kPersonalGamerTileSmall:
|
|
return user->GetProfileIcon(tile_type);
|
|
|
|
default:
|
|
XELOGW("{}: Unsupported tile_type: {:08X} for title: {:08X} Id: {:16X}",
|
|
__func__, static_cast<uint32_t>(tile_type), title_id, tile_id);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void UserTracker::RefreshTitleSummary(uint64_t xuid, uint32_t title_id) {
|
|
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
auto profile_gpd = user->GetGpd(kDashboardID);
|
|
if (!profile_gpd) {
|
|
return;
|
|
}
|
|
|
|
const auto title_gpd =
|
|
reinterpret_cast<GpdInfoTitle*>(user->GetGpd(title_id));
|
|
if (!title_gpd) {
|
|
return;
|
|
}
|
|
|
|
auto title_data = user->dashboard_gpd_.GetTitleInfo(title_id);
|
|
if (!title_data) {
|
|
return;
|
|
}
|
|
|
|
title_data->achievements_count = title_gpd->GetAchievementCount();
|
|
title_data->achievements_unlocked = title_gpd->GetUnlockedAchievementCount();
|
|
title_data->gamerscore_total = title_gpd->GetTotalGamerscore();
|
|
title_data->gamerscore_earned = title_gpd->GetGamerscore();
|
|
|
|
user->WriteGpd(kDashboardID);
|
|
}
|
|
|
|
} // namespace xam
|
|
} // namespace kernel
|
|
} // namespace xe
|