[Kernel] Added support for writing/reading GPD files

This breaks settings in games that are using them and savefiles in games that use settings to store progress
This commit is contained in:
Gliniak
2024-12-15 13:58:08 +01:00
committed by Radosław Gliński
parent ccf7adf015
commit 1110cdd372
58 changed files with 4642 additions and 2122 deletions

View File

@@ -0,0 +1,309 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xam/xdbf/gpd_info.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/user_settings.h"
#include <map>
#include <ranges>
namespace xe {
namespace kernel {
namespace xam {
GpdInfo::GpdInfo() : XdbfFile(), title_id_(-1) {}
GpdInfo::GpdInfo(const uint32_t title_id) : XdbfFile(), title_id_(title_id) {
header_.entry_count = 1 * base_entry_count;
header_.free_count = 1 * base_entry_count;
header_.free_used = 1;
// Add free entry at the end
XdbfFileLoc loc;
loc.size = 0xFFFFFFFF;
loc.offset = 0;
free_entries_.push_back(loc);
}
GpdInfo::GpdInfo(const uint32_t title_id, const std::vector<uint8_t> buffer)
: XdbfFile({buffer.data(), buffer.size()}), title_id_(title_id) {}
std::span<const uint8_t> GpdInfo::GetImage(uint64_t id) const {
const Entry* entry = GetEntry(static_cast<uint16_t>(GpdSection::kImage), id);
if (!entry) {
return {};
}
return {entry->data.data(), entry->data.size()};
}
void GpdInfo::AddImage(uint32_t id, std::span<const uint8_t> image_data) {
Entry* entry = GetEntry(static_cast<uint16_t>(GpdSection::kImage), id);
if (entry || !image_data.size()) {
return;
}
Entry new_entry(id, static_cast<uint16_t>(GpdSection::kImage),
static_cast<uint32_t>(image_data.size()));
memcpy(new_entry.data.data(), image_data.data(), image_data.size());
UpsertEntry(&new_entry);
}
X_XDBF_GPD_SETTING_HEADER* GpdInfo::GetSetting(uint32_t id) {
Entry* entry = GetEntry(static_cast<uint16_t>(GpdSection::kSetting), id);
if (!entry) {
return nullptr;
}
return reinterpret_cast<X_XDBF_GPD_SETTING_HEADER*>(entry->data.data());
}
std::span<const uint8_t> GpdInfo::GetSettingData(uint32_t id) {
X_XDBF_GPD_SETTING_HEADER* setting = GetSetting(id);
if (!setting) {
return {};
}
if (setting->setting_type != X_USER_DATA_TYPE::BINARY &&
setting->setting_type != X_USER_DATA_TYPE::WSTRING) {
return {};
}
const uint32_t size = setting->base_data.binary.size;
const uint8_t* data_ptr = reinterpret_cast<uint8_t*>(setting + 1);
return {data_ptr, size};
}
void GpdInfo::UpsertSetting(const UserSetting* setting_data) {
const auto serialized_data = setting_data->Serialize();
Entry new_entry(setting_data->get_setting_id(),
static_cast<uint16_t>(GpdSection::kSetting),
static_cast<uint32_t>(serialized_data.size()));
memcpy(new_entry.data.data(), serialized_data.data(), serialized_data.size());
UpsertEntry(&new_entry);
}
std::u16string GpdInfo::GetString(uint32_t id) const {
const Entry* entry = GetEntry(static_cast<uint16_t>(GpdSection::kString), id);
if (!entry) {
return {};
}
return string_util::read_u16string_and_swap(
reinterpret_cast<const char16_t*>(entry->data.data()));
}
void GpdInfo::AddString(uint32_t id, std::u16string string_data) {
Entry* entry = GetEntry(static_cast<uint16_t>(GpdSection::kString), id);
if (entry != nullptr) {
return;
}
const uint32_t entry_size =
static_cast<uint32_t>(string_util::size_in_bytes(string_data));
Entry new_entry(id, static_cast<uint16_t>(GpdSection::kString), entry_size);
string_util::copy_and_swap_truncating(
reinterpret_cast<char16_t*>(new_entry.data.data()), string_data,
string_data.length() + 1);
UpsertEntry(&new_entry);
}
std::vector<uint8_t> GpdInfo::Serialize() const {
// Resize to proper size.
const uint32_t entries_table_size = sizeof(XdbfEntry) * header_.entry_count;
const uint32_t free_table_size = sizeof(XdbfFileLoc) * header_.free_count;
const uint32_t gpd_size = sizeof(XdbfHeader) + entries_table_size +
free_table_size + CalculateEntriesSize();
std::vector<uint8_t> data(gpd_size);
// Header part
uint8_t* write_ptr = data.data();
// Write header
memcpy(write_ptr, &header_, sizeof(XdbfHeader));
write_ptr += sizeof(XdbfHeader);
// Entries in XDBF are sorted by section lowest-to-highest
std::vector<const Entry*> entries = GetSortedEntries();
for (const auto& entry : entries) {
memcpy(write_ptr, &entry->info, sizeof(XdbfEntry));
write_ptr += sizeof(XdbfEntry);
}
const auto empty_entries_count = header_.entry_count - entries.size();
// Set remaining bytes to 0
write_ptr =
std::fill_n(write_ptr, empty_entries_count * sizeof(XdbfEntry), 0);
// Free header part
for (const auto& entry : free_entries_) {
memcpy(write_ptr, &entry, sizeof(XdbfFileLoc));
write_ptr += sizeof(XdbfFileLoc);
}
const auto empty_free_entries_count =
header_.free_count - free_entries_.size();
write_ptr =
std::fill_n(write_ptr, empty_free_entries_count * sizeof(XdbfFileLoc), 0);
// Entries data
for (const auto& entry : entries) {
if (!entry->info.size) {
continue;
}
memcpy(write_ptr + entry->info.offset, entry->data.data(),
entry->data.size());
}
return data;
}
bool GpdInfo::IsSyncEntry(const Entry* const entry) {
return entry->info.id == 0x100000000 || entry->info.id == 0x200000000;
}
bool GpdInfo::IsEntryOfSection(const Entry* const entry,
const GpdSection section) {
return entry->info.section == static_cast<uint16_t>(section);
}
void GpdInfo::UpsertEntry(Entry* updated_entry) {
auto entry = GetEntry(updated_entry->info.section, updated_entry->info.id);
if (entry) {
DeleteEntry(entry);
}
InsertEntry(updated_entry);
}
uint32_t GpdInfo::FindFreeLocation(const uint32_t entry_size) {
assert_false(free_entries_.empty());
uint32_t offset = free_entries_.back().offset;
auto itr = std::find_if(
free_entries_.begin(), free_entries_.end(),
[entry_size](XdbfFileLoc entry) { return entry.size == entry_size; });
// We have exact match, so just get offset and remove entry
if (itr != free_entries_.cend()) {
offset = itr->offset;
header_.free_used--;
free_entries_.erase(itr);
return offset;
}
// Check for any entry that matches size.
itr = std::find_if(
free_entries_.begin(), free_entries_.end(),
[entry_size](XdbfFileLoc entry) { return entry.size > entry_size; });
// There is an requirement that there is always at least one entry, so no need
// to check for valid entry.
offset = itr->offset;
itr->offset += entry_size;
itr->size -= entry_size;
return offset;
}
void GpdInfo::InsertEntry(Entry* entry) {
ResizeEntryTable();
entry->info.offset = FindFreeLocation(entry->info.size);
entries_.push_back(*entry);
header_.entry_used++;
}
void GpdInfo::DeleteEntry(const Entry* entry) {
// Don't really remove entry. Just remove entry in the entry table.
MarkSpaceAsFree(entry->info.offset, entry->info.size);
auto itr =
std::find_if(entries_.begin(), entries_.end(), [entry](Entry first) {
return entry->info.section == first.info.section &&
first.info.id == entry->info.id;
});
if (itr != entries_.end()) {
entries_.erase(itr);
}
header_.entry_used--;
}
std::vector<const Entry*> GpdInfo::GetSortedEntries() const {
std::vector<const Entry*> sorted_entries;
for (auto& entry : entries_) {
sorted_entries.push_back(&entry);
}
std::sort(sorted_entries.begin(), sorted_entries.end(),
[](const Entry* first, const Entry* second) {
if (first->info.section == second->info.section) {
return first->info.id < second->info.id;
}
return first->info.section < second->info.section;
});
return sorted_entries;
}
void GpdInfo::ResizeEntryTable() {
// There is no need to recalculate offsets as they're in relation to end of
// this entries count.
if (header_.entry_used >= header_.entry_count) {
header_.entry_count =
xe::round_up(header_.entry_count + 1, base_entry_count, true);
}
if (header_.free_used >= header_.free_count) {
header_.free_used =
xe::round_up(header_.free_used + 1, base_entry_count, true);
}
}
void GpdInfo::ReallocateEntry(Entry* entry, uint32_t required_size) {
MarkSpaceAsFree(entry->info.offset, entry->info.size);
// Now find new location for out entry
entry->info.size = required_size;
entry->info.offset = FindFreeLocation(required_size);
}
void GpdInfo::MarkSpaceAsFree(uint32_t offset, uint32_t size) {
XdbfFileLoc loc;
loc.size = size;
loc.offset = offset;
ResizeEntryTable();
free_entries_.emplace(free_entries_.begin(), loc);
header_.free_used++;
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@@ -0,0 +1,158 @@
/**
******************************************************************************
* 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 "xenia/kernel/xam/user_data.h"
#include "xenia/kernel/xam/xdbf/xdbf_io.h"
#include <span>
#include <string>
#include <vector>
#include "xenia/xbox.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 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)
class GpdInfo : public XdbfFile {
public:
GpdInfo();
GpdInfo(const uint32_t title_id);
GpdInfo(const uint32_t title_id, const std::vector<uint8_t> buffer);
// 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);
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);
void DeleteEntry(const 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_

View File

@@ -0,0 +1,114 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xam/xdbf/gpd_info_profile.h"
#include "xenia/base/string_util.h"
#include <ranges>
namespace xe {
namespace kernel {
namespace xam {
const std::vector<const X_XDBF_GPD_TITLE_PLAYED*>
GpdInfoProfile::GetTitlesInfo() const {
std::vector<const X_XDBF_GPD_TITLE_PLAYED*> entries;
auto titles = entries_ | std::views::filter([](const auto& entry) {
return !IsSyncEntry(&entry);
}) |
std::views::filter([](const auto& entry) {
return IsEntryOfSection(&entry, GpdSection::kTitle);
});
for (const auto& title : titles) {
entries.push_back(
reinterpret_cast<const X_XDBF_GPD_TITLE_PLAYED*>(title.data.data()));
}
return entries;
};
X_XDBF_GPD_TITLE_PLAYED* GpdInfoProfile::GetTitleInfo(const uint32_t title_id) {
auto title = entries_ | std::views::filter([](const auto& entry) {
return !IsSyncEntry(&entry);
}) |
std::views::filter([](const auto& entry) {
return IsEntryOfSection(&entry, GpdSection::kTitle);
}) |
std::views::filter([title_id](const auto& entry) {
return static_cast<uint32_t>(entry.info.id) == title_id;
});
if (title.empty()) {
return nullptr;
}
return reinterpret_cast<X_XDBF_GPD_TITLE_PLAYED*>(title.begin()->data.data());
}
std::u16string GpdInfoProfile::GetTitleName(const uint32_t title_id) const {
const Entry* entry =
GetEntry(static_cast<uint16_t>(GpdSection::kTitle), title_id);
if (!entry) {
return std::u16string();
}
return string_util::read_u16string_and_swap(reinterpret_cast<const char16_t*>(
entry->data.data() + sizeof(X_XDBF_GPD_TITLE_PLAYED)));
}
void GpdInfoProfile::AddNewTitle(const SpaInfo* title_data) {
const X_XDBF_GPD_TITLE_PLAYED title_gpd_data =
FillTitlePlayedData(title_data);
const std::u16string title_name = xe::to_utf16(title_data->title_name());
const uint32_t entry_size =
sizeof(X_XDBF_GPD_TITLE_PLAYED) +
static_cast<uint32_t>(string_util::size_in_bytes(title_name));
Entry entry(title_data->title_id(), static_cast<uint16_t>(GpdSection::kTitle),
entry_size);
memcpy(entry.data.data(), &title_gpd_data, sizeof(X_XDBF_GPD_TITLE_PLAYED));
string_util::copy_and_swap_truncating(
reinterpret_cast<char16_t*>(entry.data.data() +
sizeof(X_XDBF_GPD_TITLE_PLAYED)),
title_name, title_name.size() + 1);
UpsertEntry(&entry);
}
X_XDBF_GPD_TITLE_PLAYED GpdInfoProfile::FillTitlePlayedData(
const SpaInfo* title_data) const {
X_XDBF_GPD_TITLE_PLAYED title_gpd_data = {};
title_gpd_data.title_id = title_data->title_id();
title_gpd_data.achievements_count = title_data->achievement_count();
title_gpd_data.gamerscore_total = title_data->total_gamerscore();
return title_gpd_data;
}
void GpdInfoProfile::UpdateTitleInfo(const uint32_t title_id,
X_XDBF_GPD_TITLE_PLAYED* title_data) {
X_XDBF_GPD_TITLE_PLAYED* current_info = GetTitleInfo(title_id);
if (!current_info) {
return;
}
memcpy(current_info, title_data, sizeof(X_XDBF_GPD_TITLE_PLAYED));
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@@ -0,0 +1,50 @@
/**
******************************************************************************
* 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_PROFILE_H_
#define XENIA_KERNEL_XAM_XDBF_GPD_INFO_PROFILE_H_
#include "xenia/kernel/xam/xdbf/gpd_info.h"
#include "xenia/kernel/xam/xdbf/spa_info.h"
#include <string>
#include <vector>
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
namespace xam {
class GpdInfoProfile : public GpdInfo {
public:
GpdInfoProfile() : GpdInfo(0xFFFE07D1) {};
GpdInfoProfile(const std::vector<uint8_t> buffer)
: GpdInfo(0xFFFE07D1, buffer) {};
~GpdInfoProfile() {};
void AddNewTitle(const SpaInfo* title_data);
void UpdateTitleInfo(const uint32_t title_id,
X_XDBF_GPD_TITLE_PLAYED* title_data);
const std::vector<const X_XDBF_GPD_TITLE_PLAYED*> GetTitlesInfo() const;
X_XDBF_GPD_TITLE_PLAYED* GetTitleInfo(const uint32_t title_id);
std::u16string GetTitleName(const uint32_t title_id) const;
private:
X_XDBF_GPD_TITLE_PLAYED FillTitlePlayedData(const SpaInfo* title_data) const;
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_XDBF_GPD_INFO_PROFILE_H_

View File

@@ -0,0 +1,196 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xam/xdbf/gpd_info_title.h"
#include <ranges>
namespace xe {
namespace kernel {
namespace xam {
X_XDBF_GPD_ACHIEVEMENT* GpdInfoTitle::GetAchievementEntry(const uint32_t id) {
Entry* entry = GetEntry(static_cast<uint16_t>(GpdSection::kAchievement), id);
if (!entry) {
return nullptr;
}
return reinterpret_cast<X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
}
const char16_t* GpdInfoTitle::GetAchievementTitlePtr(const uint32_t id) {
X_XDBF_GPD_ACHIEVEMENT* achievement_ptr = GetAchievementEntry(id);
if (!achievement_ptr) {
return nullptr;
}
return reinterpret_cast<const char16_t*>(++achievement_ptr);
}
const char16_t* GpdInfoTitle::GetAchievementDescriptionPtr(const uint32_t id) {
// We need to get ptr to first string. These are one after another in memory.
const char16_t* title_ptr = GetAchievementTitlePtr(id);
if (!title_ptr) {
return nullptr;
}
return reinterpret_cast<const char16_t*>(title_ptr +
GetAchievementTitle(id).length());
}
const char16_t* GpdInfoTitle::GetAchievementUnachievedDescriptionPtr(
const uint32_t id) {
const char16_t* title_ptr = GetAchievementDescriptionPtr(id);
if (!title_ptr) {
return nullptr;
}
return reinterpret_cast<const char16_t*>(
title_ptr + GetAchievementDescription(id).length());
}
std::u16string GpdInfoTitle::GetAchievementTitle(const uint32_t id) {
auto title_ptr = GetAchievementTitlePtr(id);
if (!title_ptr) {
return std::u16string();
}
return string_util::read_u16string_and_swap(title_ptr);
}
std::u16string GpdInfoTitle::GetAchievementDescription(const uint32_t id) {
auto description_ptr = GetAchievementDescriptionPtr(id);
if (!description_ptr) {
return std::u16string();
}
return string_util::read_u16string_and_swap(description_ptr);
}
std::u16string GpdInfoTitle::GetAchievementUnachievedDescription(
const uint32_t id) {
auto description_ptr = GetAchievementUnachievedDescriptionPtr(id);
if (!description_ptr) {
return std::u16string();
}
return string_util::read_u16string_and_swap(description_ptr);
}
std::vector<uint32_t> GpdInfoTitle::GetAchievementsIds() const {
std::vector<uint32_t> ids;
auto achievements =
entries_ | std::views::filter([](const auto& entry) {
return !IsSyncEntry(&entry);
}) |
std::views::filter([](const auto& entry) {
return IsEntryOfSection(&entry, GpdSection::kAchievement);
});
for (const auto& achievement : achievements) {
ids.push_back(static_cast<uint32_t>(achievement.info.id));
}
return ids;
}
void GpdInfoTitle::AddAchievement(const AchievementDetails* header) {
Entry* entry =
GetEntry(static_cast<uint16_t>(GpdSection::kAchievement), header->id);
if (entry) {
return;
}
X_XDBF_GPD_ACHIEVEMENT internal_info;
internal_info.magic = sizeof(X_XDBF_GPD_ACHIEVEMENT);
internal_info.id = header->id;
internal_info.image_id = header->image_id;
internal_info.gamerscore = header->gamerscore;
internal_info.flags = header->flags;
internal_info.unlock_time = 0;
const uint32_t strings_size =
static_cast<uint32_t>(string_util::size_in_bytes(header->label) +
string_util::size_in_bytes(header->description) +
string_util::size_in_bytes(header->unachieved));
const uint32_t entry_size = sizeof(X_XDBF_GPD_ACHIEVEMENT) + strings_size;
Entry new_entry(header->id, static_cast<uint16_t>(GpdSection::kAchievement),
entry_size);
uint8_t* write_ptr = new_entry.data.data();
memcpy(write_ptr, &internal_info, sizeof(X_XDBF_GPD_ACHIEVEMENT));
write_ptr += sizeof(X_XDBF_GPD_ACHIEVEMENT);
string_util::copy_and_swap_truncating(reinterpret_cast<char16_t*>(write_ptr),
header->label,
header->label.length() + 1);
write_ptr += string_util::size_in_bytes(header->label);
string_util::copy_and_swap_truncating(reinterpret_cast<char16_t*>(write_ptr),
header->description,
header->description.length() + 1);
write_ptr += string_util::size_in_bytes(header->description);
string_util::copy_and_swap_truncating(reinterpret_cast<char16_t*>(write_ptr),
header->unachieved,
header->unachieved.length() + 1);
UpsertEntry(&new_entry);
}
uint32_t GpdInfoTitle::GetTotalGamerscore() {
const auto ids = GetAchievementsIds();
uint32_t gamerscore = 0;
for (const auto id : ids) {
gamerscore += GetAchievementEntry(id)->gamerscore;
}
return gamerscore;
}
uint32_t GpdInfoTitle::GetGamerscore() {
const auto ids = GetAchievementsIds();
uint32_t gamerscore = 0;
for (const auto id : ids) {
const auto entry = GetAchievementEntry(id);
if (entry->is_achievement_unlocked()) {
gamerscore += GetAchievementEntry(id)->gamerscore;
}
}
return gamerscore;
}
uint32_t GpdInfoTitle::GetAchievementCount() {
return static_cast<uint32_t>(GetAchievementsIds().size());
}
uint32_t GpdInfoTitle::GetUnlockedAchievementCount() {
const auto ids = GetAchievementsIds();
uint32_t count = 0;
for (const auto id : ids) {
const auto entry = GetAchievementEntry(id);
if (entry->is_achievement_unlocked()) {
count += 1;
}
}
return count;
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@@ -0,0 +1,60 @@
/**
******************************************************************************
* 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_TITLE_H_
#define XENIA_KERNEL_XAM_XDBF_GPD_INFO_TITLE_H_
#include "xenia/kernel/xam/achievement_manager.h"
#include "xenia/kernel/xam/xdbf/gpd_info.h"
#include "xenia/kernel/xam/xdbf/xdbf_io.h"
#include <string>
#include <vector>
#include "xenia/base/memory.h"
#include "xenia/base/string_util.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
namespace xam {
class GpdInfoTitle : public GpdInfo {
public:
GpdInfoTitle() : GpdInfo(-1) {};
GpdInfoTitle(const uint32_t title_id) : GpdInfo(title_id) {};
GpdInfoTitle(const uint32_t title_id, const std::vector<uint8_t> buffer)
: GpdInfo(title_id, buffer) {};
~GpdInfoTitle() {};
std::vector<uint32_t> GetAchievementsIds() const;
void AddAchievement(const AchievementDetails* header);
X_XDBF_GPD_ACHIEVEMENT* GetAchievementEntry(const uint32_t id);
std::u16string GetAchievementTitle(const uint32_t id);
std::u16string GetAchievementDescription(const uint32_t id);
std::u16string GetAchievementUnachievedDescription(const uint32_t id);
uint32_t GetTotalGamerscore();
uint32_t GetGamerscore();
uint32_t GetAchievementCount();
uint32_t GetUnlockedAchievementCount();
private:
const char16_t* GetAchievementTitlePtr(const uint32_t id);
const char16_t* GetAchievementDescriptionPtr(const uint32_t id);
const char16_t* GetAchievementUnachievedDescriptionPtr(const uint32_t id);
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_XDBF_GPD_INFO_TITLE_H_

View File

@@ -0,0 +1,300 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xam/xdbf/spa_info.h"
namespace xe {
namespace kernel {
namespace xam {
SpaInfo::SpaInfo(const std::span<uint8_t> buffer) : XdbfFile(buffer) {
// On creation we only need to load basic info. This is to prevent unnecessary
// load if we have updated SPA in TU/DLC.
LoadTitleInformation();
}
void SpaInfo::Load() {
LoadLanguageData();
LoadAchievements();
LoadProperties();
LoadContexts();
}
bool operator<(const SpaInfo& first, const SpaInfo& second) {
return std::tie(first.title_header_.major, first.title_header_.minor,
first.title_header_.build, first.title_header_.revision) <
std::tie(second.title_header_.major, second.title_header_.minor,
second.title_header_.build, second.title_header_.revision);
}
bool operator==(const SpaInfo& first, const SpaInfo& second) {
return std::tie(first.title_header_.major, first.title_header_.minor,
first.title_header_.build, first.title_header_.revision) ==
std::tie(second.title_header_.major, second.title_header_.minor,
second.title_header_.build, second.title_header_.revision);
}
bool operator<=(const SpaInfo& first, const SpaInfo& second) {
return first < second || first == second;
}
void SpaInfo::LoadLanguageData() {
for (uint64_t language = 1;
language < static_cast<uint64_t>(XLanguage::kMaxLanguages); language++) {
auto section =
GetEntry(static_cast<uint16_t>(SpaSection::kStringTable), language);
if (!section) {
continue;
}
auto section_header =
reinterpret_cast<const XdbfSectionHeaderEx*>(section->data.data());
assert_true(section_header->magic == kXdbfSignatureXstr);
assert_true(section_header->version == 1);
const uint8_t* ptr = section->data.data() + sizeof(XdbfSectionHeaderEx);
XdbfLanguageStrings strings;
for (uint16_t i = 0; i < section_header->count; i++) {
const XdbfStringTableEntry* entry =
reinterpret_cast<const XdbfStringTableEntry*>(ptr);
std::string string_data = std::string(
reinterpret_cast<const char*>(ptr + sizeof(XdbfStringTableEntry)),
entry->string_length);
strings[entry->id] = string_data;
ptr += entry->string_length + sizeof(XdbfStringTableEntry);
}
language_strings_[static_cast<XLanguage>(language)] = strings;
}
}
void SpaInfo::LoadAchievements() {
auto section =
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXach);
if (!section) {
return;
}
auto section_header =
reinterpret_cast<const XdbfSectionHeaderEx*>(section->data.data());
assert_true(section_header->magic == kXdbfSignatureXach);
assert_true(section_header->version == 1);
AchievementTableEntry* ptr = reinterpret_cast<AchievementTableEntry*>(
section->data.data() + sizeof(XdbfSectionHeaderEx));
for (uint32_t i = 0; i < section_header->count; i++) {
achievements_.push_back(&ptr[i]);
}
}
void SpaInfo::LoadProperties() {
auto property_table =
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXprp);
if (!property_table) {
return;
}
auto xprp_head =
reinterpret_cast<const XdbfSectionHeader*>(property_table->data.data());
assert_true(xprp_head->magic == kXdbfSignatureXprp);
assert_true(xprp_head->version == 1);
const uint8_t* ptr = property_table->data.data() + sizeof(XdbfSectionHeader);
const uint16_t properties_count =
xe::byte_swap(*reinterpret_cast<const uint16_t*>(ptr));
ptr += sizeof(uint16_t);
for (uint16_t i = 0; i < properties_count; i++) {
auto entry = reinterpret_cast<const XdbfPropertyTableEntry*>(ptr);
ptr += sizeof(XdbfPropertyTableEntry);
properties_.push_back(entry);
}
}
void SpaInfo::LoadContexts() {
auto contexts_table =
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXctx);
if (!contexts_table) {
return;
}
auto xcxt_head =
reinterpret_cast<const XdbfSectionHeader*>(contexts_table->data.data());
assert_true(xcxt_head->magic == kXdbfSignatureXcxt);
assert_true(xcxt_head->version == 1);
const uint8_t* ptr = contexts_table->data.data() + sizeof(XdbfSectionHeader);
const uint32_t contexts_count =
xe::byte_swap(*reinterpret_cast<const uint32_t*>(ptr));
ptr += sizeof(uint32_t);
for (uint32_t i = 0; i < contexts_count; i++) {
auto entry = reinterpret_cast<const XdbfContextTableEntry*>(ptr);
ptr += sizeof(XdbfContextTableEntry);
contexts_.push_back(entry);
}
}
const uint8_t* SpaInfo::ReadXLast(uint32_t& compressed_size,
uint32_t& decompressed_size) {
auto xlast_table =
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXsrc);
if (!xlast_table) {
return nullptr;
}
auto xlast_head =
reinterpret_cast<const XdbfSectionHeader*>(xlast_table->data.data());
assert_true(xlast_head->magic == kXdbfSignatureXsrc);
assert_true(xlast_head->version == 1);
const uint8_t* ptr = xlast_table->data.data() + sizeof(XdbfSectionHeader);
const uint32_t filename_length =
xe::byte_swap(*reinterpret_cast<const uint32_t*>(ptr));
ptr += sizeof(uint32_t) + filename_length;
decompressed_size = xe::byte_swap(*reinterpret_cast<const uint32_t*>(ptr));
ptr += sizeof(uint32_t);
compressed_size = xe::byte_swap(*reinterpret_cast<const uint32_t*>(ptr));
ptr += sizeof(uint32_t);
return ptr;
}
XLanguage SpaInfo::GetExistingLanguage(XLanguage language_to_check) const {
// A bit of a hack. Check if title in specific language exist.
// If it doesn't then for sure language is not supported.
return title_name(language_to_check).empty() ? default_language()
: language_to_check;
}
std::span<const uint8_t> SpaInfo::title_icon() const {
return GetIcon(kXdbfIdTitle);
}
XLanguage SpaInfo::default_language() const {
auto block =
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXstc);
if (!block) {
return XLanguage::kEnglish;
}
auto xstc = reinterpret_cast<const XdbfXstc*>(block->data.data());
return static_cast<XLanguage>(static_cast<uint32_t>(xstc->default_language));
}
bool SpaInfo::is_system_app() const {
return title_header_.title_type == TitleType::kSystem;
}
bool SpaInfo::is_demo() const {
return title_header_.title_type == TitleType::kDemo;
}
bool SpaInfo::include_in_profile() const {
if (title_header_.flags &
static_cast<uint32_t>(TitleFlags::kAlwaysIncludeInProfile)) {
return true;
}
if (title_header_.flags &
static_cast<uint32_t>(TitleFlags::kNeverIncludeInProfile)) {
return false;
}
return !is_demo();
}
uint32_t SpaInfo::title_id() const { return title_header_.title_id; }
std::string SpaInfo::title_name() const {
return GetStringTableEntry(default_language(), kXdbfIdTitle);
}
std::string SpaInfo::title_name(XLanguage language) const {
return GetStringTableEntry(language, kXdbfIdTitle);
}
// PRIVATE
void SpaInfo::LoadTitleInformation() {
auto section =
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXthd);
if (!section) {
return;
}
auto section_header =
reinterpret_cast<const XdbfSectionHeader*>(section->data.data());
assert_true(section_header->magic == kXdbfSignatureXthd);
assert_true(section_header->version == 1);
TitleHeaderData* ptr = reinterpret_cast<TitleHeaderData*>(
section->data.data() + sizeof(XdbfSectionHeader));
title_header_ = *ptr;
}
std::string SpaInfo::GetStringTableEntry(XLanguage language,
uint16_t string_id) const {
auto language_table = language_strings_.find(language);
if (language_table == language_strings_.cend()) {
return "";
}
auto entry = language_table->second.find(string_id);
if (entry == language_table->second.cend()) {
return "";
}
return entry->second;
}
const AchievementTableEntry* SpaInfo::GetAchievement(uint32_t id) {
return GetSpaEntry<const AchievementTableEntry*>(achievements_, id);
}
const XdbfContextTableEntry* SpaInfo::GetContext(uint32_t id) {
return GetSpaEntry<const XdbfContextTableEntry*>(contexts_, id);
}
const XdbfPropertyTableEntry* SpaInfo::GetProperty(uint32_t id) {
return GetSpaEntry<const XdbfPropertyTableEntry*>(properties_, id);
}
template <typename T>
T SpaInfo::GetSpaEntry(std::vector<T>& container, uint32_t id) {
for (const auto& entry : container) {
if (entry->id != id) {
continue;
}
return entry;
}
return nullptr;
}
std::span<const uint8_t> SpaInfo::GetIcon(uint64_t id) const {
auto entry = GetEntry(static_cast<uint16_t>(SpaSection::kImage), id);
if (!entry) {
return {};
}
return {entry->data.data(), entry->data.size()};
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@@ -0,0 +1,223 @@
/**
******************************************************************************
* 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_SPA_INFO_H_
#define XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_
#include "xenia/kernel/xam/xdbf/xdbf_io.h"
#include <map>
#include <numeric>
#include <string>
#include <vector>
#include "xenia/base/memory.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
namespace xam {
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp
enum class SpaSection : uint16_t {
kMetadata = 0x0001,
kImage = 0x0002,
kStringTable = 0x0003,
};
enum class TitleType : uint32_t {
kSystem = 0,
kFull = 1,
kDemo = 2,
kDownload = 3,
};
enum class TitleFlags {
kAlwaysIncludeInProfile = 1,
kNeverIncludeInProfile = 2,
};
#pragma pack(push, 1)
struct TitleHeaderData {
xe::be<uint32_t> title_id;
xe::be<TitleType> title_type;
xe::be<uint16_t> major;
xe::be<uint16_t> minor;
xe::be<uint16_t> build;
xe::be<uint16_t> revision;
xe::be<uint32_t> flags;
xe::be<uint32_t> padding_1;
xe::be<uint32_t> padding_2;
xe::be<uint32_t> padding_3;
};
static_assert_size(TitleHeaderData, 32);
struct StatsViewTableEntry {
xe::be<uint32_t> id;
xe::be<uint32_t> flags;
xe::be<uint16_t> shared_index;
xe::be<uint16_t> string_id;
xe::be<uint32_t> unused;
};
static_assert_size(StatsViewTableEntry, 0x10);
struct ViewFieldEntry {
xe::be<uint32_t> size;
xe::be<uint32_t> property_id;
xe::be<uint32_t> flags;
xe::be<uint16_t> attribute_id;
xe::be<uint16_t> string_id;
xe::be<uint16_t> aggregation_type;
xe::be<uint8_t> ordinal;
xe::be<uint8_t> field_type;
xe::be<uint32_t> format_type;
xe::be<uint32_t> unused_1;
xe::be<uint32_t> unused_2;
};
static_assert_size(ViewFieldEntry, 0x20);
struct SharedViewMetaTableEntry {
xe::be<uint16_t> column_count;
xe::be<uint16_t> row_count;
xe::be<uint32_t> unused_1;
xe::be<uint32_t> unused_2;
};
static_assert_size(SharedViewMetaTableEntry, 0xC);
struct PropertyBag {
std::vector<xe::be<uint32_t>> contexts;
std::vector<xe::be<uint32_t>> properties;
};
struct SharedView {
std::vector<ViewFieldEntry> column_entries;
std::vector<ViewFieldEntry> row_entries;
PropertyBag property_bag;
};
struct ViewTable {
StatsViewTableEntry view_entry;
SharedView shared_view;
};
struct AchievementTableEntry {
xe::be<uint16_t> id;
xe::be<uint16_t> label_id;
xe::be<uint16_t> description_id;
xe::be<uint16_t> unachieved_id;
xe::be<uint32_t> image_id;
xe::be<uint16_t> gamerscore;
xe::be<uint16_t> unkE;
xe::be<uint32_t> flags;
xe::be<uint32_t> unk14;
xe::be<uint32_t> unk18;
xe::be<uint32_t> unk1C;
xe::be<uint32_t> unk20;
};
static_assert_size(AchievementTableEntry, 0x24);
#pragma pack(pop)
class SpaInfo : public XdbfFile {
public:
SpaInfo(const std::span<uint8_t> buffer);
void Load();
const uint8_t* ReadXLast(uint32_t& compressed_size,
uint32_t& decompressed_size);
// Checks if provided language exist, if not returns default title language.
XLanguage GetExistingLanguage(XLanguage language_to_check) const;
// The game icon image, if found.
std::span<const uint8_t> title_icon() const;
std::span<const uint8_t> GetIcon(uint64_t id) const;
// The game's default language.
XLanguage default_language() const;
bool is_system_app() const;
bool is_demo() const;
bool include_in_profile() const;
uint32_t title_id() const;
// The game's title in its default language.
std::string title_name() const;
std::string title_name(XLanguage language) const;
uint32_t achievement_count() const {
return static_cast<uint32_t>(achievements_.size());
}
const AchievementTableEntry* GetAchievement(uint32_t id);
std::vector<const AchievementTableEntry*> GetAchievements() const {
return achievements_;
}
std::vector<const XdbfContextTableEntry*> GetContexts() const {
return contexts_;
}
std::vector<const XdbfPropertyTableEntry*> GetProperties() const {
return properties_;
}
const XdbfContextTableEntry* GetContext(uint32_t id);
const XdbfPropertyTableEntry* GetProperty(uint32_t id);
uint32_t total_gamerscore() const {
return std::accumulate(achievements_.cbegin(), achievements_.cend(), 0,
[](uint32_t sum, const auto& entry) {
return sum + entry->gamerscore;
});
}
friend bool operator<(const SpaInfo& first, const SpaInfo& second);
friend bool operator<=(const SpaInfo& first, const SpaInfo& second);
friend bool operator==(const SpaInfo& first, const SpaInfo& second);
std::string GetStringTableEntry(XLanguage language, uint16_t string_id) const;
private:
// Base info. There should be comparator between different SpaInfos and entry
// with newer data should replace old one. Such situation can happen when game
// adds achievements and so on with DLC.
TitleHeaderData title_header_;
// SPA is Read-Only so it's reasonable to make it readonly.
std::vector<const AchievementTableEntry*> achievements_;
std::vector<const XdbfContextTableEntry*> contexts_;
std::vector<const XdbfPropertyTableEntry*> properties_;
typedef std::map<uint16_t, std::string> XdbfLanguageStrings;
std::map<XLanguage, XdbfLanguageStrings> language_strings_;
void LoadTitleInformation();
void LoadAchievements();
void LoadLanguageData();
void LoadContexts();
void LoadProperties();
template <typename T>
static T GetSpaEntry(std::vector<T>& container, uint32_t id);
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_

View File

@@ -0,0 +1,100 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Emulator. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xam/xdbf/xdbf_io.h"
namespace xe {
namespace kernel {
namespace xam {
XdbfFile::XdbfFile(const std::span<const uint8_t> buffer) {
if (buffer.size() <= sizeof(XdbfHeader)) {
return;
}
const uint8_t* ptr = buffer.data();
if (!LoadHeader(reinterpret_cast<const XdbfHeader*>(ptr))) {
return;
}
ptr += sizeof(XdbfHeader);
const XdbfFileLoc* free_ptr = reinterpret_cast<const XdbfFileLoc*>(
ptr + (sizeof(XdbfEntry) * header_.entry_count));
const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(free_ptr) +
(sizeof(XdbfFileLoc) * header_.free_count);
LoadEntries(reinterpret_cast<const XdbfEntry*>(ptr), data_ptr);
LoadFreeEntries(free_ptr);
}
bool XdbfFile::LoadHeader(const XdbfHeader* header) {
if (!header || header->magic != kXdbfSignatureXdbf) {
return false;
}
memcpy(&header_, header, sizeof(XdbfHeader));
return true;
}
uint32_t XdbfFile::CalculateEntriesSize() const {
// XDBF always contains at least 1 free entry that marks EOF. That's why we
// can use it to get size of data in file.
return free_entries_.back().offset;
}
void XdbfFile::LoadEntries(const XdbfEntry* table_of_content,
const uint8_t* data_ptr) {
if (!table_of_content || !data_ptr) {
return;
}
for (uint32_t i = 0; i < header_.entry_used; i++) {
entries_.push_back({table_of_content++, data_ptr});
}
}
void XdbfFile::LoadFreeEntries(const XdbfFileLoc* free_entries) {
for (uint32_t i = 0; i < header_.free_used; i++) {
free_entries_.push_back(*free_entries);
free_entries++;
}
}
Entry* XdbfFile::GetEntry(uint16_t section, uint64_t id) {
for (Entry& entry : entries_) {
if (entry.info.id != id || entry.info.section != section) {
continue;
}
return &entry;
}
return nullptr;
}
const Entry* const XdbfFile::GetEntry(uint16_t section, uint64_t id) const {
for (const Entry& entry : entries_) {
if (entry.info.id != id || entry.info.section != section) {
continue;
}
return &entry;
}
return nullptr;
}
uint32_t XdbfFile::CalculateDataStartOffset() const {
const uint32_t entry_size = sizeof(XdbfEntry) * header_.entry_count;
const uint32_t free_size = sizeof(XdbfFileLoc) * header_.free_count;
return sizeof(XdbfHeader) + entry_size + free_size;
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@@ -0,0 +1,192 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Emulator. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_KERNEL_XAM_XDBF_XDBF_IO_H_
#define XENIA_KERNEL_XAM_XDBF_XDBF_IO_H_
#include <span>
#include <string>
#include <vector>
#include "xenia/base/memory.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
namespace xam {
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp
constexpr fourcc_t kXdbfSignatureXdbf = make_fourcc("XDBF");
constexpr fourcc_t kXdbfSignatureXstc = make_fourcc("XSTC");
constexpr fourcc_t kXdbfSignatureXstr = make_fourcc("XSTR");
constexpr fourcc_t kXdbfSignatureXach = make_fourcc("XACH");
constexpr fourcc_t kXdbfSignatureXprp = make_fourcc("XPRP");
constexpr fourcc_t kXdbfSignatureXcxt = make_fourcc("XCXT");
constexpr fourcc_t kXdbfSignatureXvc2 = make_fourcc("XVC2");
constexpr fourcc_t kXdbfSignatureXmat = make_fourcc("XMAT");
constexpr fourcc_t kXdbfSignatureXsrc = make_fourcc("XSRC");
constexpr fourcc_t kXdbfSignatureXthd = make_fourcc("XTHD");
constexpr uint64_t kXdbfIdTitle = 0x8000;
constexpr uint64_t kXdbfIdXstc = 0x58535443;
constexpr uint64_t kXdbfIdXach = 0x58414348;
constexpr uint64_t kXdbfIdXprp = 0x58505250;
constexpr uint64_t kXdbfIdXctx = 0x58435854;
constexpr uint64_t kXdbfIdXvc2 = 0x58564332;
constexpr uint64_t kXdbfIdXmat = 0x584D4154;
constexpr uint64_t kXdbfIdXsrc = 0x58535243;
constexpr uint64_t kXdbfIdXthd = 0x58544844;
#pragma pack(push, 1)
struct XdbfHeader {
XdbfHeader() {
magic = kXdbfSignatureXdbf;
version = 0x10000;
entry_count = 0;
entry_used = 0;
free_count = 0;
free_used = 0;
}
xe::be<uint32_t> magic;
xe::be<uint32_t> version;
xe::be<uint32_t> entry_count;
xe::be<uint32_t> entry_used;
xe::be<uint32_t> free_count;
xe::be<uint32_t> free_used;
};
static_assert_size(XdbfHeader, 24);
struct XdbfEntry {
xe::be<uint16_t> section;
xe::be<uint64_t> id;
xe::be<uint32_t> offset;
xe::be<uint32_t> size;
};
static_assert_size(XdbfEntry, 18);
struct XdbfFileLoc {
xe::be<uint32_t> offset;
xe::be<uint32_t> size;
};
static_assert_size(XdbfFileLoc, 8);
struct XdbfXstc {
xe::be<uint32_t> magic;
xe::be<uint32_t> version;
xe::be<uint32_t> size;
xe::be<uint32_t> default_language;
};
static_assert_size(XdbfXstc, 16);
struct XdbfSectionHeader {
xe::be<uint32_t> magic;
xe::be<uint32_t> version;
xe::be<uint32_t> size;
};
static_assert_size(XdbfSectionHeader, 12);
struct XdbfSectionHeaderEx {
xe::be<uint32_t> magic;
xe::be<uint32_t> version;
xe::be<uint32_t> size;
xe::be<uint16_t> count;
};
static_assert_size(XdbfSectionHeaderEx, 14);
struct XdbfStringTableEntry {
xe::be<uint16_t> id;
xe::be<uint16_t> string_length;
};
static_assert_size(XdbfStringTableEntry, 4);
struct XdbfContextTableEntry {
xe::be<uint32_t> id;
xe::be<uint16_t> unk1;
xe::be<uint16_t> string_id;
xe::be<uint32_t> max_value;
xe::be<uint32_t> default_value;
};
static_assert_size(XdbfContextTableEntry, 16);
struct XdbfPropertyTableEntry {
xe::be<uint32_t> id;
xe::be<uint16_t> string_id;
xe::be<uint16_t> data_size;
};
static_assert_size(XdbfPropertyTableEntry, 8);
#pragma pack(pop)
struct XdbfBlock {
const uint8_t* buffer;
size_t size;
operator bool() const { return buffer != nullptr; }
};
struct Entry {
Entry() {
info.id = 0;
info.offset = 0;
info.section = 0;
info.size = 0;
}
// Offset must be filled externally!
Entry(const uint64_t id, const uint16_t section, const uint32_t size) {
info.id = id;
info.section = section;
info.size = size;
data.resize(size);
}
Entry(const XdbfEntry* entry, const uint8_t* entry_data) {
info = *entry;
data.resize(info.size);
memcpy(data.data(), entry_data + info.offset, info.size);
}
XdbfEntry info;
std::vector<uint8_t> data;
};
// Wraps an XDBF (XboxDataBaseFormat) in-memory database.
// https://free60project.github.io/wiki/XDBF.html
class XdbfFile {
public:
XdbfFile() {};
XdbfFile(const std::span<const uint8_t> buffer);
const Entry* const GetEntry(uint16_t section, uint64_t id) const;
protected:
XdbfHeader header_ = {};
std::vector<Entry> entries_ = {};
std::vector<XdbfFileLoc> free_entries_ = {};
// Gets an entry in the given section.
// If the entry is not found the returned block will be nullptr.
Entry* GetEntry(uint16_t section, uint64_t id);
uint32_t CalculateDataStartOffset() const;
uint32_t CalculateEntriesSize() const;
private:
bool LoadHeader(const XdbfHeader* header);
void LoadEntries(const XdbfEntry* table_of_content, const uint8_t* data_ptr);
void LoadFreeEntries(const XdbfFileLoc* free_entries);
};
} // namespace xam
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XAM_XDBF_XDBF_IO_H_