[Kernel] AchievementManager: Added Interface for attaching custom implementations
This commit is contained in:
parent
0368e06b71
commit
e93373660f
|
@ -13,6 +13,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/app/profile_dialogs.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/gpu/command_processor.h"
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/ui/imgui_dialog.h"
|
#include "xenia/ui/imgui_dialog.h"
|
||||||
|
@ -25,8 +26,6 @@
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
#include "xenia/app/profile_dialogs.h"
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "xenia/kernel/user_module.h"
|
#include "xenia/kernel/user_module.h"
|
||||||
#include "xenia/kernel/util/gameinfo_utils.h"
|
#include "xenia/kernel/util/gameinfo_utils.h"
|
||||||
#include "xenia/kernel/util/xdbf_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/xam/xam_module.h"
|
||||||
#include "xenia/kernel/xbdm/xbdm_module.h"
|
#include "xenia/kernel/xbdm/xbdm_module.h"
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||||
|
@ -1517,6 +1518,17 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
if (!icon_block.empty()) {
|
if (!icon_block.empty()) {
|
||||||
display_window_->SetIcon(icon_block.data(), icon_block.size());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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/emulator.h"
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
@ -17,52 +17,218 @@
|
||||||
DEFINE_bool(show_achievement_notification, false,
|
DEFINE_bool(show_achievement_notification, false,
|
||||||
"Show achievement notification on screen.", "UI");
|
"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);
|
DECLARE_int32(user_language);
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
AchievementManager::AchievementManager() { unlocked_achievements.clear(); };
|
GpdAchievementBackend::GpdAchievementBackend() {}
|
||||||
|
GpdAchievementBackend::~GpdAchievementBackend() {}
|
||||||
|
|
||||||
void AchievementManager::EarnAchievement(uint64_t xuid, uint32_t title_id,
|
void GpdAchievementBackend::EarnAchievement(const uint64_t xuid,
|
||||||
uint32_t achievement_id) {
|
const uint32_t title_id,
|
||||||
if (IsAchievementUnlocked(achievement_id)) {
|
const uint32_t achievement_id) {
|
||||||
|
const auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
||||||
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Emulator* emulator = kernel_state()->emulator();
|
auto achievement = GetAchievementInfoInternal(xuid, title_id, achievement_id);
|
||||||
ui::WindowedAppContext& app_context =
|
if (!achievement) {
|
||||||
kernel_state()->emulator()->display_window()->app_context();
|
return;
|
||||||
ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer();
|
}
|
||||||
|
|
||||||
|
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()->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 {
|
||||||
|
const auto user = kernel_state()->xam_state()->GetUserProfile(xuid);
|
||||||
|
if (!user) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return user->GetTitleAchievements(title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!cvars::show_achievement_notification) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto achievement = default_achievements_backend_->GetAchievementInfo(
|
||||||
|
xuid, title_id, achievement_id);
|
||||||
|
|
||||||
|
if (!achievement) {
|
||||||
|
// Something went really wrong!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowAchievementEarnedNotification(achievement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementManager::LoadTitleAchievements(
|
||||||
|
const uint64_t xuid, const util::XdbfGameData title_data) const {
|
||||||
|
default_achievements_backend_->LoadAchievementsData(xuid, title_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<AchievementGpdStructure>*
|
||||||
|
AchievementManager::GetTitleAchievements(const uint64_t xuid,
|
||||||
|
const uint32_t title_id) const {
|
||||||
|
return default_achievements_backend_->GetTitleAchievements(xuid, title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AchievementManager::DoesAchievementExist(
|
||||||
|
const uint32_t achievement_id) const {
|
||||||
const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf();
|
const util::XdbfGameData title_xdbf = kernel_state()->title_xdbf();
|
||||||
const util::XdbfAchievementTableEntry achievement =
|
const util::XdbfAchievementTableEntry achievement =
|
||||||
title_xdbf.GetAchievement(achievement_id);
|
title_xdbf.GetAchievement(achievement_id);
|
||||||
|
|
||||||
if (!achievement.id) {
|
if (!achievement.id) {
|
||||||
return;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
const XLanguage title_language = title_xdbf.GetExistingLanguage(
|
|
||||||
static_cast<XLanguage>(cvars::user_language));
|
|
||||||
|
|
||||||
const std::string label =
|
|
||||||
title_xdbf.GetStringTableEntry(title_language, achievement.label_id);
|
|
||||||
const std::string desc = title_xdbf.GetStringTableEntry(
|
|
||||||
title_language, achievement.description_id);
|
|
||||||
|
|
||||||
XELOGI("Achievement unlocked: {}", label);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementManager::ShowAchievementEarnedNotification(
|
||||||
|
const AchievementGpdStructure* achievement) const {
|
||||||
const std::string description =
|
const std::string description =
|
||||||
fmt::format("{}G - {}", achievement.gamerscore, label);
|
fmt::format("{}G - {}", achievement->gamerscore,
|
||||||
|
xe::to_utf8(xe::load_and_swap<std::u16string>(
|
||||||
|
achievement->achievement_name.c_str())));
|
||||||
|
|
||||||
|
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]() {
|
app_context.CallInUIThread([imgui_drawer, description]() {
|
||||||
new xe::ui::AchievementNotificationWindow(
|
new xe::ui::AchievementNotificationWindow(
|
||||||
|
@ -71,21 +237,6 @@ void AchievementManager::EarnAchievement(uint64_t xuid, uint32_t title_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AchievementManager::IsAchievementUnlocked(uint32_t achievement_id) {
|
|
||||||
auto itr = unlocked_achievements.find(achievement_id);
|
|
||||||
|
|
||||||
return itr != unlocked_achievements.cend();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t AchievementManager::GetAchievementUnlockTime(uint32_t achievement_id) {
|
|
||||||
auto itr = unlocked_achievements.find(achievement_id);
|
|
||||||
if (itr == unlocked_achievements.cend()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return itr->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace xam
|
} // namespace xam
|
||||||
} // namespace kernel
|
} // namespace kernel
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -14,16 +14,16 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/kernel/util/xdbf_utils.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
// TODO(gibbed): probably a FILETIME/LARGE_INTEGER, unknown currently
|
|
||||||
struct X_ACHIEVEMENT_UNLOCK_TIME {
|
struct X_ACHIEVEMENT_UNLOCK_TIME {
|
||||||
xe::be<uint32_t> unk_0;
|
xe::be<uint32_t> high_part;
|
||||||
xe::be<uint32_t> unk_4;
|
xe::be<uint32_t> low_part;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct X_ACHIEVEMENT_DETAILS {
|
struct X_ACHIEVEMENT_DETAILS {
|
||||||
|
@ -40,20 +40,160 @@ struct X_ACHIEVEMENT_DETAILS {
|
||||||
};
|
};
|
||||||
static_assert_size(X_ACHIEVEMENT_DETAILS, 36);
|
static_assert_size(X_ACHIEVEMENT_DETAILS, 36);
|
||||||
|
|
||||||
|
// This is structure used inside GPD file.
|
||||||
|
// GPD is writeable XDBF.
|
||||||
|
// There are two info instances
|
||||||
|
// 1. In Dashboard ID which contains single GPD that contains info about any
|
||||||
|
// booted game (name, title_id, last boot time etc)
|
||||||
|
// 2. In specific Title ID directory GPD contains there structure below for
|
||||||
|
// every achievement. (unlocked or not)
|
||||||
|
struct AchievementGpdStructure {
|
||||||
|
AchievementGpdStructure(const XLanguage language,
|
||||||
|
const util::XdbfGameData xdbf,
|
||||||
|
const util::XdbfAchievementTableEntry& xdbf_entry) {
|
||||||
|
const std::string label =
|
||||||
|
xdbf.GetStringTableEntry(language, xdbf_entry.label_id);
|
||||||
|
const std::string desc =
|
||||||
|
xdbf.GetStringTableEntry(language, xdbf_entry.description_id);
|
||||||
|
const std::string locked_desc =
|
||||||
|
xdbf.GetStringTableEntry(language, xdbf_entry.unachieved_id);
|
||||||
|
|
||||||
|
struct_size = 0x1C;
|
||||||
|
achievement_id = static_cast<xe::be<uint32_t>>(xdbf_entry.id);
|
||||||
|
image_id = xdbf_entry.image_id;
|
||||||
|
gamerscore = static_cast<xe::be<uint32_t>>(xdbf_entry.gamerscore);
|
||||||
|
flags = xdbf_entry.flags;
|
||||||
|
unlock_time = {};
|
||||||
|
achievement_name =
|
||||||
|
xe::load_and_swap<std::u16string>(xe::to_utf16(label).c_str());
|
||||||
|
unlocked_description =
|
||||||
|
xe::load_and_swap<std::u16string>(xe::to_utf16(desc).c_str());
|
||||||
|
locked_description =
|
||||||
|
xe::load_and_swap<std::u16string>(xe::to_utf16(locked_desc).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
xe::be<uint32_t> struct_size;
|
||||||
|
xe::be<uint32_t> achievement_id;
|
||||||
|
xe::be<uint32_t> image_id;
|
||||||
|
xe::be<uint32_t> gamerscore;
|
||||||
|
xe::be<uint32_t> flags;
|
||||||
|
X_ACHIEVEMENT_UNLOCK_TIME unlock_time;
|
||||||
|
std::u16string achievement_name;
|
||||||
|
std::u16string unlocked_description;
|
||||||
|
std::u16string locked_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AchievementType : uint32_t {
|
||||||
|
kCompletion = 1,
|
||||||
|
kLeveling = 2,
|
||||||
|
kUnlock = 3,
|
||||||
|
kEvent = 4,
|
||||||
|
kTournament = 5,
|
||||||
|
kCheckpoint = 6,
|
||||||
|
kOther = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AchievementPlatform : uint32_t {
|
||||||
|
kX360 = 0x100000,
|
||||||
|
kPC = 0x200000,
|
||||||
|
kMobile = 0x300000,
|
||||||
|
kWebGames = 0x400000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AchievementFlags : uint32_t {
|
||||||
|
kTypeMask = 0x7,
|
||||||
|
kShowUnachieved = 0x8,
|
||||||
|
kAchievedOnline = 0x10000,
|
||||||
|
kAchieved = 0x20000,
|
||||||
|
kNotAchievable = 0x40000,
|
||||||
|
kWasNotAchievable = 0x80000,
|
||||||
|
kPlatformMask = 0x700000,
|
||||||
|
kColorizable = 0x1000000, // avatar awards only?
|
||||||
|
};
|
||||||
|
|
||||||
|
class AchievementBackendInterface {
|
||||||
|
public:
|
||||||
|
virtual ~AchievementBackendInterface() {};
|
||||||
|
|
||||||
|
virtual void EarnAchievement(const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) = 0;
|
||||||
|
|
||||||
|
virtual bool IsAchievementUnlocked(const uint64_t xuid,
|
||||||
|
const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const = 0;
|
||||||
|
|
||||||
|
virtual const AchievementGpdStructure* GetAchievementInfo(
|
||||||
|
const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const = 0;
|
||||||
|
virtual const std::vector<AchievementGpdStructure>* GetTitleAchievements(
|
||||||
|
const uint64_t xuid, const uint32_t title_id) const = 0;
|
||||||
|
virtual bool LoadAchievementsData(const uint64_t xuid,
|
||||||
|
const util::XdbfGameData title_data) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual bool SaveAchievementsData(const uint64_t xuid,
|
||||||
|
const uint32_t title_id) = 0;
|
||||||
|
virtual bool SaveAchievementData(const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpdAchievementBackend : public AchievementBackendInterface {
|
||||||
|
public:
|
||||||
|
GpdAchievementBackend();
|
||||||
|
~GpdAchievementBackend();
|
||||||
|
|
||||||
|
void EarnAchievement(const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) override;
|
||||||
|
bool IsAchievementUnlocked(const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const override;
|
||||||
|
const AchievementGpdStructure* GetAchievementInfo(
|
||||||
|
const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const override;
|
||||||
|
const std::vector<AchievementGpdStructure>* GetTitleAchievements(
|
||||||
|
const uint64_t xuid, const uint32_t title_id) const override;
|
||||||
|
bool LoadAchievementsData(const uint64_t xuid,
|
||||||
|
const util::XdbfGameData title_data) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AchievementGpdStructure* GetAchievementInfoInternal(
|
||||||
|
const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const;
|
||||||
|
|
||||||
|
bool SaveAchievementsData(const uint64_t xuid,
|
||||||
|
const uint32_t title_id) override {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
bool SaveAchievementData(const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) override;
|
||||||
|
};
|
||||||
|
|
||||||
class AchievementManager {
|
class AchievementManager {
|
||||||
public:
|
public:
|
||||||
AchievementManager();
|
AchievementManager();
|
||||||
|
|
||||||
void EarnAchievement(uint64_t xuid, uint32_t title_id,
|
void LoadTitleAchievements(const uint64_t xuid,
|
||||||
uint32_t achievement_id);
|
const util::XdbfGameData title_id) const;
|
||||||
|
|
||||||
bool IsAchievementUnlocked(uint32_t achievement_id);
|
void EarnAchievement(const uint32_t user_index, const uint32_t title_id,
|
||||||
uint64_t GetAchievementUnlockTime(uint32_t achievement_id);
|
const uint32_t achievement_id) const;
|
||||||
|
void EarnAchievement(const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const;
|
||||||
|
const AchievementGpdStructure* GetAchievementInfo(
|
||||||
|
const uint64_t xuid, const uint32_t title_id,
|
||||||
|
const uint32_t achievement_id) const;
|
||||||
|
const std::vector<AchievementGpdStructure>* GetTitleAchievements(
|
||||||
|
const uint64_t xuid, const uint32_t title_id) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<uint32_t, uint64_t> unlocked_achievements;
|
bool DoesAchievementExist(const uint32_t achievement_id) const;
|
||||||
// void Load();
|
void ShowAchievementEarnedNotification(
|
||||||
// void Save();
|
const AchievementGpdStructure* achievement) const;
|
||||||
|
|
||||||
|
// This contains all backends with exception of default storage.
|
||||||
|
std::vector<std::unique_ptr<AchievementBackendInterface>>
|
||||||
|
achievement_backends_;
|
||||||
|
|
||||||
|
std::unique_ptr<AchievementBackendInterface> default_achievements_backend_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xam
|
} // namespace xam
|
||||||
|
|
|
@ -102,7 +102,8 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||||
(X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr);
|
(X_XUSER_ACHIEVEMENT*)memory_->TranslateVirtual(achievements_ptr);
|
||||||
for (uint32_t i = 0; i < achievement_count; i++, achievement++) {
|
for (uint32_t i = 0; i < achievement_count; i++, achievement++) {
|
||||||
kernel_state_->achievement_manager()->EarnAchievement(
|
kernel_state_->achievement_manager()->EarnAchievement(
|
||||||
achievement->user_idx, 0, achievement->achievement_id);
|
achievement->user_idx, kernel_state_->title_id(),
|
||||||
|
achievement->achievement_id);
|
||||||
}
|
}
|
||||||
return X_E_SUCCESS;
|
return X_E_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,6 +232,31 @@ Property* UserProfile::GetProperty(const AttributeKey id) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AchievementGpdStructure* UserProfile::GetAchievement(const uint32_t title_id,
|
||||||
|
const uint32_t id) {
|
||||||
|
auto title_achievements = achievements_.find(title_id);
|
||||||
|
if (title_achievements == achievements_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& entry : title_achievements->second) {
|
||||||
|
if (entry.achievement_id == id) {
|
||||||
|
return &entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AchievementGpdStructure>* UserProfile::GetTitleAchievements(
|
||||||
|
const uint32_t title_id) {
|
||||||
|
auto title_achievements = achievements_.find(title_id);
|
||||||
|
if (title_achievements == achievements_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &title_achievements->second;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace xam
|
} // namespace xam
|
||||||
} // namespace kernel
|
} // namespace kernel
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "xenia/base/byte_stream.h"
|
#include "xenia/base/byte_stream.h"
|
||||||
#include "xenia/kernel/util/property.h"
|
#include "xenia/kernel/util/property.h"
|
||||||
#include "xenia/kernel/util/xuserdata.h"
|
#include "xenia/kernel/util/xuserdata.h"
|
||||||
|
#include "xenia/kernel/xam/achievement_manager.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -173,12 +174,21 @@ class UserProfile {
|
||||||
|
|
||||||
std::map<uint32_t, uint32_t> contexts_;
|
std::map<uint32_t, uint32_t> contexts_;
|
||||||
|
|
||||||
|
friend class GpdAchievementBackend;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AchievementGpdStructure* GetAchievement(const uint32_t title_id,
|
||||||
|
const uint32_t id);
|
||||||
|
std::vector<AchievementGpdStructure>* GetTitleAchievements(
|
||||||
|
const uint32_t title_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t xuid_;
|
uint64_t xuid_;
|
||||||
X_XAMACCOUNTINFO account_info_;
|
X_XAMACCOUNTINFO account_info_;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<UserSetting>> setting_list_;
|
std::vector<std::unique_ptr<UserSetting>> setting_list_;
|
||||||
std::unordered_map<uint32_t, UserSetting*> settings_;
|
std::unordered_map<uint32_t, UserSetting*> settings_;
|
||||||
|
std::map<uint32_t, std::vector<AchievementGpdStructure>> achievements_;
|
||||||
|
|
||||||
std::vector<Property> properties_;
|
std::vector<Property> properties_;
|
||||||
|
|
||||||
|
|
|
@ -609,32 +609,35 @@ dword_result_t XamUserCreateAchievementEnumerator_entry(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto user = kernel_state()->xam_state()->GetUserProfile(user_index);
|
||||||
|
if (!user) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t requester_xuid = user->xuid();
|
||||||
|
if (xuid) {
|
||||||
|
requester_xuid = xuid;
|
||||||
|
}
|
||||||
|
|
||||||
const util::XdbfGameData db = kernel_state()->title_xdbf();
|
const util::XdbfGameData db = kernel_state()->title_xdbf();
|
||||||
|
uint32_t title_id_ = title_id ? title_id : kernel_state()->title_id();
|
||||||
|
|
||||||
if (db.is_valid()) {
|
const auto user_title_achievements =
|
||||||
const XLanguage language =
|
kernel_state()->achievement_manager()->GetTitleAchievements(
|
||||||
db.GetExistingLanguage(static_cast<XLanguage>(cvars::user_language));
|
requester_xuid, title_id_);
|
||||||
const std::vector<util::XdbfAchievementTableEntry> achievement_list =
|
|
||||||
db.GetAchievements();
|
|
||||||
|
|
||||||
for (const util::XdbfAchievementTableEntry& entry : achievement_list) {
|
|
||||||
auto is_unlocked =
|
|
||||||
kernel_state()->achievement_manager()->IsAchievementUnlocked(
|
|
||||||
entry.id);
|
|
||||||
auto unlock_time =
|
|
||||||
kernel_state()->achievement_manager()->GetAchievementUnlockTime(
|
|
||||||
entry.id);
|
|
||||||
|
|
||||||
|
if (user_title_achievements) {
|
||||||
|
for (const auto& entry : *user_title_achievements) {
|
||||||
auto item = XAchievementEnumerator::AchievementDetails{
|
auto item = XAchievementEnumerator::AchievementDetails{
|
||||||
entry.id,
|
entry.achievement_id,
|
||||||
xe::to_utf16(db.GetStringTableEntry(language, entry.label_id)),
|
xe::load_and_swap<std::u16string>(entry.achievement_name.c_str()),
|
||||||
xe::to_utf16(db.GetStringTableEntry(language, entry.description_id)),
|
xe::load_and_swap<std::u16string>(entry.unlocked_description.c_str()),
|
||||||
xe::to_utf16(db.GetStringTableEntry(language, entry.unachieved_id)),
|
xe::load_and_swap<std::u16string>(entry.locked_description.c_str()),
|
||||||
entry.image_id,
|
entry.image_id,
|
||||||
entry.gamerscore,
|
entry.gamerscore,
|
||||||
(uint32_t)(unlock_time << 31),
|
entry.unlock_time.high_part,
|
||||||
(uint32_t)unlock_time,
|
entry.unlock_time.low_part,
|
||||||
is_unlocked ? entry.flags | 0x20000 : entry.flags};
|
entry.flags};
|
||||||
|
|
||||||
e->AppendItem(item);
|
e->AppendItem(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,8 +106,8 @@ uint32_t XAchievementEnumerator::WriteItems(uint32_t buffer_ptr,
|
||||||
!!(flags_ & 4) ? AppendString(string_buffer, item.unachieved) : 0;
|
!!(flags_ & 4) ? AppendString(string_buffer, item.unachieved) : 0;
|
||||||
details[i].image_id = item.image_id;
|
details[i].image_id = item.image_id;
|
||||||
details[i].gamerscore = item.gamerscore;
|
details[i].gamerscore = item.gamerscore;
|
||||||
details[i].unlock_time.unk_0 = item.unlock_time.unk_0;
|
details[i].unlock_time.high_part = item.unlock_time.high_part;
|
||||||
details[i].unlock_time.unk_4 = item.unlock_time.unk_4;
|
details[i].unlock_time.low_part = item.unlock_time.low_part;
|
||||||
details[i].flags = item.flags;
|
details[i].flags = item.flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,8 +124,8 @@ class XAchievementEnumerator : public XEnumerator {
|
||||||
uint32_t image_id;
|
uint32_t image_id;
|
||||||
uint32_t gamerscore;
|
uint32_t gamerscore;
|
||||||
struct {
|
struct {
|
||||||
uint32_t unk_0;
|
uint32_t high_part;
|
||||||
uint32_t unk_4;
|
uint32_t low_part;
|
||||||
} unlock_time;
|
} unlock_time;
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue