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 Sep 26, 2024
1 parent 1fd501d commit 308d2ee
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 64 deletions.
2 changes: 0 additions & 2 deletions src/xenia/app/emulator_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"

#define MAX_USERS 4

namespace xe {
namespace app {

Expand Down
11 changes: 11 additions & 0 deletions src/xenia/emulator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "xenia/gpu/graphics_system.h"
#include "xenia/hid/input_driver.h"
#include "xenia/hid/input_system.h"
#include "xenia/kernel/achievement_manager.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/user_module.h"
#include "xenia/kernel/util/gameinfo_utils.h"
Expand Down Expand Up @@ -1339,6 +1340,16 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
main_thread_ = main_thread;
on_launch(title_id_.value(), title_name_);

for (uint32_t i = 0; i < MAX_USERS; i++) {
const kernel::xam::UserProfile* user = kernel_state_->user_profile(i);
if (!user) {
continue;
}

kernel_state_->achievement_manager()->LoadTitleAchievements(
user->xuid(), module->title_id());
}

// Plugins must be loaded after calling LaunchModule() and
// FinishLoadingUserModule() which will apply TUs and patching to the main
// xex.
Expand Down
2 changes: 2 additions & 0 deletions src/xenia/emulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class Window;

namespace xe {

#define MAX_USERS 4

constexpr fourcc_t kEmulatorSaveSignature = make_fourcc("XSAV");
static const std::string kDefaultGameSymbolicLink = "GAME:";
static const std::string kDefaultPartitionSymbolicLink = "D:";
Expand Down
237 changes: 200 additions & 37 deletions src/xenia/kernel/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/achievement_manager.h"
#include "xenia/emulator.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/kernel/kernel_state.h"
Expand All @@ -17,72 +17,235 @@
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 defult. "
"Possible options: [].",
"Achievements");

DECLARE_int32(user_language);

namespace xe {
namespace kernel {

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

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()->user_profile(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;
}

XELOGI("Player: {} Unlocked Achievement: {}", user->name(),
xe::to_utf8(xe::load_and_swap<std::u16string>(
achievement->achievement_name.c_str())));

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()->user_profile(xuid);
if (!user) {
return nullptr;
}

if (!achievements_.count(xuid)) {
return nullptr;
}

for (auto& entry : achievements_.at(xuid)) {
if (entry.achievement_id == achievement_id) {
return const_cast<AchievementGpdStructure*>(&entry);
}
}

return nullptr;
}

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 uint32_t title_id) {
const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf();
const util::XdbfAchievementTableEntry achievement =
title_xdbf.GetAchievement(achievement_id);
if (!title_xdbf.is_valid()) {
return false;
}

if (!achievement.id) {
return;
const auto achievements = title_xdbf.GetAchievements();
if (achievements.empty()) {
return true;
}

const XLanguage title_language = title_xdbf.GetExistingLanguage(
static_cast<XLanguage>(cvars::user_language));
for (const auto& achievement : achievements) {
const std::string label =
title_xdbf.GetStringTableEntry(title_language, achievement.label_id);
const std::string desc = title_xdbf.GetStringTableEntry(
title_language, achievement.description_id);
const std::string locked_desc = title_xdbf.GetStringTableEntry(
title_language, achievement.unachieved_id);

AchievementGpdStructure achievementData = {};
achievementData.struct_size = 0x1C;
achievementData.achievement_id = static_cast<uint32_t>(achievement.id);
achievementData.image_id = achievement.image_id;
achievementData.gamerscore = static_cast<uint32_t>(achievement.gamerscore);
achievementData.flags = achievement.flags;
achievementData.unlock_time.high_part = 0;
achievementData.unlock_time.low_part = 0;
achievementData.achievement_name =
xe::load_and_swap<std::u16string>(xe::to_utf16(label).c_str());
achievementData.unlocked_description =
xe::load_and_swap<std::u16string>(xe::to_utf16(desc).c_str());
achievementData.locked_description =
xe::load_and_swap<std::u16string>(xe::to_utf16(locked_desc).c_str());

achievements_[xuid].push_back(achievementData);
}

// 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;
}

bool GpdAchievementBackend::SaveAchievementData(const uint64_t xuid,
const uint32_t title_id,
const uint32_t achievement_id) {
return true;
}

const std::string label =
title_xdbf.GetStringTableEntry(title_language, achievement.label_id);
const std::string desc = title_xdbf.GetStringTableEntry(
title_language, achievement.description_id);
AchievementManager::AchievementManager() {
default_achievements_backend_ = std::make_unique<GpdAchievementBackend>();

XELOGI("Achievement unlocked: {}", label);
// 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()->user_profile(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 uint32_t title_id) const {
default_achievements_backend_->LoadAchievementsData(xuid, title_id);
}

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 kernel
Expand Down
Loading

0 comments on commit 308d2ee

Please sign in to comment.