Skip to content

Commit

Permalink
[Kernel] AchievementManager: Added Interface for attaching custom imp…
Browse files Browse the repository at this point in the history
…lementations
  • Loading branch information
Gliniak committed Dec 10, 2024
1 parent 0368e06 commit 73e9cbd
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 66 deletions.
3 changes: 1 addition & 2 deletions src/xenia/app/emulator_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <memory>
#include <string>

#include "xenia/app/profile_dialogs.h"
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/ui/imgui_dialog.h"
Expand All @@ -25,8 +26,6 @@
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"

#include "xenia/app/profile_dialogs.h"

namespace xe {
namespace app {

Expand Down
12 changes: 12 additions & 0 deletions src/xenia/emulator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "xenia/kernel/user_module.h"
#include "xenia/kernel/util/gameinfo_utils.h"
#include "xenia/kernel/util/xdbf_utils.h"
#include "xenia/kernel/xam/achievement_manager.h"
#include "xenia/kernel/xam/xam_module.h"
#include "xenia/kernel/xbdm/xbdm_module.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
Expand Down Expand Up @@ -1517,6 +1518,17 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
if (!icon_block.empty()) {
display_window_->SetIcon(icon_block.data(), icon_block.size());
}

for (uint8_t slot = 0; slot < XUserMaxUserCount; slot++) {
auto user =
kernel_state_->xam_state()->profile_manager()->GetProfile(slot);

if (user) {
kernel_state_->xam_state()
->achievement_manager()
->LoadTitleAchievements(user->xuid(), db);
}
}
}
}

Expand Down
218 changes: 179 additions & 39 deletions src/xenia/kernel/xam/achievement_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2024 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/

#include "achievement_manager.h"
#include "xenia/kernel/xam/achievement_manager.h"
#include "xenia/emulator.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/kernel/kernel_state.h"
Expand All @@ -17,73 +17,213 @@
DEFINE_bool(show_achievement_notification, false,
"Show achievement notification on screen.", "UI");

DEFINE_string(default_achievement_backend, "",
"Defines which achievement backend should be used as an default. "
"Possible options: [].",
"Achievements");

DECLARE_int32(user_language);

namespace xe {
namespace kernel {
namespace xam {

AchievementManager::AchievementManager() { unlocked_achievements.clear(); };
GpdAchievementBackend::GpdAchievementBackend() {}
GpdAchievementBackend::~GpdAchievementBackend() {}

void AchievementManager::EarnAchievement(uint64_t xuid, uint32_t title_id,
uint32_t achievement_id) {
if (IsAchievementUnlocked(achievement_id)) {
void GpdAchievementBackend::EarnAchievement(const uint64_t xuid,
const uint32_t title_id,
const uint32_t achievement_id) {
const auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
if (!user) {
return;
}

const Emulator* emulator = kernel_state()->emulator();
ui::WindowedAppContext& app_context =
kernel_state()->emulator()->display_window()->app_context();
ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer();
auto achievement = GetAchievementInfoInternal(xuid, title_id, achievement_id);
if (!achievement) {
return;
}

const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf();
const util::XdbfAchievementTableEntry achievement =
title_xdbf.GetAchievement(achievement_id);
XELOGI("Player: {} Unlocked Achievement: {}", user->name(),
xe::to_utf8(xe::load_and_swap<std::u16string>(
achievement->achievement_name.c_str())));

if (!achievement.id) {
return;
const uint64_t unlock_time = Clock::QueryHostSystemTime();
achievement->flags =
achievement->flags | static_cast<uint32_t>(AchievementFlags::kAchieved);
achievement->unlock_time.high_part = static_cast<uint32_t>(unlock_time >> 32);
achievement->unlock_time.low_part = static_cast<uint32_t>(unlock_time);

SaveAchievementData(xuid, title_id, achievement_id);
}

AchievementGpdStructure* GpdAchievementBackend::GetAchievementInfoInternal(
const uint64_t xuid, const uint32_t title_id,
const uint32_t achievement_id) const {
const auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
if (!user) {
return nullptr;
}

return user->GetAchievement(title_id, achievement_id);
}

const AchievementGpdStructure* GpdAchievementBackend::GetAchievementInfo(
const uint64_t xuid, const uint32_t title_id,
const uint32_t achievement_id) const {
return GetAchievementInfoInternal(xuid, title_id, achievement_id);
}

bool GpdAchievementBackend::IsAchievementUnlocked(
const uint64_t xuid, const uint32_t title_id,
const uint32_t achievement_id) const {
const auto achievement =
GetAchievementInfoInternal(xuid, title_id, achievement_id);

return (achievement->flags &
static_cast<uint32_t>(AchievementFlags::kAchieved)) != 0;
}

const std::vector<AchievementGpdStructure*>
GpdAchievementBackend::GetTitleAchievements(const uint64_t xuid,
const uint32_t title_id) const {
return {};
}

bool GpdAchievementBackend::LoadAchievementsData(
const uint64_t xuid, const util::XdbfGameData title_data) {
auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
if (!user) {
return false;
}

// Question. Should loading for GPD for profile be directly done by profile or
// here?
if (!title_data.is_valid()) {
return false;
}

const XLanguage title_language = title_xdbf.GetExistingLanguage(
const auto achievements = title_data.GetAchievements();
if (achievements.empty()) {
return true;
}

const auto title_id = title_data.GetTitleInformation().title_id;

const XLanguage title_language = title_data.GetExistingLanguage(
static_cast<XLanguage>(cvars::user_language));
for (const auto& achievement : achievements) {
AchievementGpdStructure achievementData(title_language, title_data,
achievement);
user->achievements_[title_id].push_back(achievementData);
}

const std::string label =
title_xdbf.GetStringTableEntry(title_language, achievement.label_id);
const std::string desc = title_xdbf.GetStringTableEntry(
title_language, achievement.description_id);
// TODO(Gliniak): Here should be loader of GPD file for loaded title. That way
// we can load flags and unlock_time from specific user.
return true;
}

XELOGI("Achievement unlocked: {}", label);
bool GpdAchievementBackend::SaveAchievementData(const uint64_t xuid,
const uint32_t title_id,
const uint32_t achievement_id) {
return true;
}

AchievementManager::AchievementManager() {
default_achievements_backend_ = std::make_unique<GpdAchievementBackend>();

// Add any optional backend here.
};
void AchievementManager::EarnAchievement(const uint32_t user_index,
const uint32_t title_id,
const uint32_t achievement_id) const {
const auto user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) {
return;
}

EarnAchievement(user->xuid(), title_id, achievement_id);
};

void AchievementManager::EarnAchievement(const uint64_t xuid,
const uint32_t title_id,
const uint32_t achievement_id) const {
if (!DoesAchievementExist(achievement_id)) {
XELOGW(
"{}: Achievement with ID: {} for title: {:08X} doesn't exist in "
"database!",
__func__, achievement_id, title_id);
return;
}
// Always send request to unlock in 3PP backends. It's up to them to check if
// achievement was unlocked
for (auto& backend : achievement_backends_) {
backend->EarnAchievement(xuid, title_id, achievement_id);
}

if (default_achievements_backend_->IsAchievementUnlocked(xuid, title_id,
achievement_id)) {
return;
}

default_achievements_backend_->EarnAchievement(xuid, title_id,
achievement_id);

unlocked_achievements[achievement_id] = Clock::QueryHostSystemTime();
// Even if we disable popup we still should store info that this
// achievement was earned.
if (!cvars::show_achievement_notification) {
return;
}

const std::string description =
fmt::format("{}G - {}", achievement.gamerscore, label);
const auto achievement = default_achievements_backend_->GetAchievementInfo(
xuid, title_id, achievement_id);

app_context.CallInUIThread([imgui_drawer, description]() {
new xe::ui::AchievementNotificationWindow(
imgui_drawer, "Achievement unlocked", description, 0,
kernel_state()->notification_position_);
});
if (!achievement) {
// Something went really wrong!
return;
}
ShowAchievementEarnedNotification(achievement);
}

bool AchievementManager::IsAchievementUnlocked(uint32_t achievement_id) {
auto itr = unlocked_achievements.find(achievement_id);
void AchievementManager::LoadTitleAchievements(
const uint64_t xuid, const util::XdbfGameData title_data) const {
default_achievements_backend_->LoadAchievementsData(xuid, title_data);
}

return itr != unlocked_achievements.cend();
const AchievementGpdStructure* AchievementManager::GetAchievementInfo(
const uint64_t xuid, const uint32_t title_id,
const uint32_t achievement_id) const {
return default_achievements_backend_->GetAchievementInfo(xuid, title_id,
achievement_id);
}

uint64_t AchievementManager::GetAchievementUnlockTime(uint32_t achievement_id) {
auto itr = unlocked_achievements.find(achievement_id);
if (itr == unlocked_achievements.cend()) {
return 0;
bool AchievementManager::DoesAchievementExist(
const uint32_t achievement_id) const {
const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf();
const util::XdbfAchievementTableEntry achievement =
title_xdbf.GetAchievement(achievement_id);

if (!achievement.id) {
return false;
}
return true;
}

void AchievementManager::ShowAchievementEarnedNotification(
const AchievementGpdStructure* achievement) const {
const std::string description =
fmt::format("{}G - {}", achievement->gamerscore,
xe::to_utf8(xe::load_and_swap<std::u16string>(
achievement->achievement_name.c_str())));

return itr->second;
const Emulator* emulator = kernel_state()->emulator();
ui::WindowedAppContext& app_context =
emulator->display_window()->app_context();
ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer();

app_context.CallInUIThread([imgui_drawer, description]() {
new xe::ui::AchievementNotificationWindow(
imgui_drawer, "Achievement unlocked", description, 0,
kernel_state()->notification_position_);
});
}

} // namespace xam
Expand Down
Loading

0 comments on commit 73e9cbd

Please sign in to comment.