Files
Xenia-Canary/src/xenia/kernel/xam/xdbf/gpd_info.h
2025-10-30 17:58:21 +01:00

204 lines
5.7 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. *
******************************************************************************
*/
#ifndef XENIA_KERNEL_XAM_XDBF_GPD_INFO_H_
#define XENIA_KERNEL_XAM_XDBF_GPD_INFO_H_
#include <span>
#include <string>
#include <vector>
#include "xenia/base/byte_order.h"
#include "xenia/kernel/util/xfiletime.h"
#include "xenia/kernel/xam/user_data.h"
#include "xenia/kernel/xam/xdbf/xdbf_io.h"
namespace xe {
namespace kernel {
namespace xam {
class UserSetting;
enum class GpdSection : uint16_t {
kAchievement = 0x1, // TitleGpd exclusive
kImage = 0x2,
kSetting = 0x3,
kTitle = 0x4, // Dashboard Gpd exclusive
kString = 0x5,
kProtectedAchievement = 0x6, // GFWL only
};
enum class AchievementTypeFlags : uint32_t {
kCompletion = 0x1,
kLeveling = 0x2,
kUnlock = 0x3,
kEvent = 0x4,
kTournament = 0x5,
kCheckpoint = 0x6,
kOther = 0x7,
};
enum class AchievementFlags : uint32_t {
kTypeMask = 0x7,
kShowUnachieved = 0x8,
kAchievedOnline = 0x10000,
kAchieved = 0x20000,
kNotAchievable = 0x40000,
kWasNotAchievable = 0x80000,
kPlatformMask = 0x700000,
kColorizable = 0x1000000, // avatar awards only?
};
struct X_XDBF_AVATARAWARDS_COUNTER {
uint8_t earned;
uint8_t possible;
};
static_assert_size(X_XDBF_AVATARAWARDS_COUNTER, 2);
#pragma pack(push, 1)
struct X_XDBF_GPD_ACHIEVEMENT {
xe::be<uint32_t> magic;
xe::be<uint32_t> id;
xe::be<uint32_t> image_id;
xe::be<uint32_t> gamerscore;
xe::be<uint32_t> flags;
xe::be<uint64_t> unlock_time;
// wchar_t* title;
// wchar_t* description;
// wchar_t* unlocked_description;
bool is_achievement_unlocked() const {
return flags & static_cast<uint32_t>(AchievementFlags::kAchieved);
}
};
static_assert_size(X_XDBF_GPD_ACHIEVEMENT, 0x1C);
struct X_XDBF_GPD_TITLE_PLAYED {
xe::be<uint32_t> title_id;
xe::be<uint32_t> achievements_count;
xe::be<uint32_t> achievements_unlocked;
xe::be<uint32_t> gamerscore_total;
xe::be<uint32_t> gamerscore_earned;
xe::be<uint16_t> online_achievement_count;
X_XDBF_AVATARAWARDS_COUNTER all_avatar_awards;
X_XDBF_AVATARAWARDS_COUNTER male_avatar_awards;
X_XDBF_AVATARAWARDS_COUNTER female_avatar_awards;
xe::be<uint32_t>
flags; // 1 - Offline unlocked, must be synced. 2 - Achievement Unlocked.
// Image missing. 0x10 - Avatar unlocked. Avatar missing.
X_FILETIME last_played;
// xe::be<char16_t> title_name[64]; // size seems to be variable inside GPDs.
bool include_in_enumerator() const { return achievements_count != 0; }
};
static_assert_size(X_XDBF_GPD_TITLE_PLAYED, 0x28);
struct X_XDBF_GPD_SETTING_HEADER {
xe::be<uint32_t> setting_id;
xe::be<uint32_t> unknown_1;
X_USER_DATA_TYPE setting_type;
char unknown[7];
X_USER_DATA_UNION base_data;
bool RequiresBuffer() const {
return setting_type == X_USER_DATA_TYPE::BINARY ||
setting_type == X_USER_DATA_TYPE::WSTRING;
}
};
static_assert_size(X_XDBF_GPD_SETTING_HEADER, 0x18);
#pragma pack(pop)
inline AchievementTypeFlags constexpr GetAchievementType(const uint32_t flags) {
return static_cast<AchievementTypeFlags>(
flags & static_cast<uint32_t>(AchievementFlags::kTypeMask));
}
inline std::string constexpr GetAchievementTypeName(
const AchievementTypeFlags type) {
switch (type) {
case AchievementTypeFlags::kCompletion:
return "Completion";
break;
case AchievementTypeFlags::kLeveling:
return "Leveling";
break;
case AchievementTypeFlags::kUnlock:
return "Unlock";
break;
case AchievementTypeFlags::kEvent:
return "Event";
break;
case AchievementTypeFlags::kTournament:
return "Tournament";
break;
case AchievementTypeFlags::kCheckpoint:
return "Checkpoint";
break;
case AchievementTypeFlags::kOther:
default:
return "Other";
break;
}
}
class GpdInfo : public XdbfFile {
public:
GpdInfo();
GpdInfo(const uint32_t title_id);
GpdInfo(const uint32_t title_id, const std::vector<uint8_t> buffer);
~GpdInfo() = default;
// Normally GPD ALWAYS contains one free entry that indicates EOF
bool IsValid() const { return !free_entries_.empty(); }
// r/w image, setting, string.
std::span<const uint8_t> GetImage(uint64_t id) const;
void AddImage(uint32_t id, std::span<const uint8_t> image_data);
X_XDBF_GPD_SETTING_HEADER* GetSetting(uint32_t id);
std::span<const uint8_t> GetSettingData(uint32_t id);
void UpsertSetting(const UserSetting* setting_data);
std::u16string GetString(uint32_t id) const;
void AddString(uint32_t id, std::u16string string_data);
std::vector<uint8_t> Serialize() const;
protected:
static bool IsSyncEntry(const Entry* const entry);
static bool IsEntryOfSection(const Entry* const entry,
const GpdSection section);
void UpsertEntry(Entry* entry);
void DeleteEntry(const Entry* entry);
uint32_t FindFreeLocation(const uint32_t entry_size);
private:
static constexpr uint32_t base_entry_count = 512;
uint32_t title_id_ = 0;
void InsertEntry(Entry* entry);
std::vector<const Entry*> GetSortedEntries() const;
void ResizeEntryTable();
void ReallocateEntry(Entry* entry, uint32_t required_size);
void MarkSpaceAsFree(uint32_t offset, uint32_t size);
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_XDBF_GPD_INFO_H_