[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:
committed by
Radosław Gliński
parent
ccf7adf015
commit
1110cdd372
309
src/xenia/kernel/xam/xdbf/gpd_info.cc
Normal file
309
src/xenia/kernel/xam/xdbf/gpd_info.cc
Normal 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
|
||||
158
src/xenia/kernel/xam/xdbf/gpd_info.h
Normal file
158
src/xenia/kernel/xam/xdbf/gpd_info.h
Normal 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_
|
||||
114
src/xenia/kernel/xam/xdbf/gpd_info_profile.cc
Normal file
114
src/xenia/kernel/xam/xdbf/gpd_info_profile.cc
Normal 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
|
||||
50
src/xenia/kernel/xam/xdbf/gpd_info_profile.h
Normal file
50
src/xenia/kernel/xam/xdbf/gpd_info_profile.h
Normal 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_
|
||||
196
src/xenia/kernel/xam/xdbf/gpd_info_title.cc
Normal file
196
src/xenia/kernel/xam/xdbf/gpd_info_title.cc
Normal 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
|
||||
60
src/xenia/kernel/xam/xdbf/gpd_info_title.h
Normal file
60
src/xenia/kernel/xam/xdbf/gpd_info_title.h
Normal 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_
|
||||
300
src/xenia/kernel/xam/xdbf/spa_info.cc
Normal file
300
src/xenia/kernel/xam/xdbf/spa_info.cc
Normal 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
|
||||
223
src/xenia/kernel/xam/xdbf/spa_info.h
Normal file
223
src/xenia/kernel/xam/xdbf/spa_info.h
Normal 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_
|
||||
100
src/xenia/kernel/xam/xdbf/xdbf_io.cc
Normal file
100
src/xenia/kernel/xam/xdbf/xdbf_io.cc
Normal 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
|
||||
192
src/xenia/kernel/xam/xdbf/xdbf_io.h
Normal file
192
src/xenia/kernel/xam/xdbf/xdbf_io.h
Normal 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_
|
||||
Reference in New Issue
Block a user