[Kernel] Add achievement stuff to XdbfWrapper, convert it to a full XDBF parser...
XdbfWrapper would only wrap existing in-memory data, making modifying data (or creating new data) non-trivial. The new XdbfFile class parses all the XDBF stuff into different vectors, making modifications a lot easier. GpdFile and SpaFile are both children of XdbfWrapper, SpaFile has the same functions as the older XdbfGameData class, but now includes support for parsing achievements too. The GpdFile class also supports achievements, and both classes parse into a common XdbfAchievement struct, which makes it simple to eg. copy achievements from a SPA file into a new GPD file. TODO: - images? - GPD settings (user_profile has some code for this, not sure how much we can use here though...) - modify user_profile to make use of this
This commit is contained in:
parent
35e79d1979
commit
52984280c3
|
@ -8,68 +8,139 @@
|
|||
*/
|
||||
|
||||
#include "xenia/kernel/util/xdbf_utils.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace util {
|
||||
|
||||
constexpr uint32_t kXdbfMagicXdbf = 'XDBF';
|
||||
constexpr uint32_t kXdbfMagicXstc = 'XSTC';
|
||||
constexpr uint32_t kXdbfMagicXstr = 'XSTR';
|
||||
|
||||
XdbfWrapper::XdbfWrapper(const uint8_t* data, size_t data_size)
|
||||
: data_(data), data_size_(data_size) {
|
||||
if (!data || data_size <= sizeof(XbdfHeader)) {
|
||||
data_ = nullptr;
|
||||
return;
|
||||
bool XdbfFile::Read(const uint8_t* data, size_t data_size) {
|
||||
if (!data || data_size <= sizeof(X_XDBF_HEADER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* ptr = data_;
|
||||
|
||||
header_ = reinterpret_cast<const XbdfHeader*>(ptr);
|
||||
ptr += sizeof(XbdfHeader);
|
||||
if (header_->magic != kXdbfMagicXdbf) {
|
||||
data_ = nullptr;
|
||||
return;
|
||||
auto* ptr = data;
|
||||
memcpy(&header, ptr, sizeof(X_XDBF_HEADER));
|
||||
if (header.magic != kXdbfMagicXdbf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entries_ = reinterpret_cast<const XbdfEntry*>(ptr);
|
||||
ptr += sizeof(XbdfEntry) * header_->entry_count;
|
||||
ptr += sizeof(X_XDBF_HEADER);
|
||||
|
||||
files_ = reinterpret_cast<const XbdfFileLoc*>(ptr);
|
||||
ptr += sizeof(XbdfFileLoc) * header_->free_count;
|
||||
auto* free_ptr = (const X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) *
|
||||
header.entry_count));
|
||||
auto* data_ptr =
|
||||
(uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header.free_count);
|
||||
|
||||
content_offset_ = ptr;
|
||||
for (uint32_t i = 0; i < header.entry_used; i++) {
|
||||
XdbfEntry entry;
|
||||
memcpy(&entry.info, ptr, sizeof(X_XDBF_ENTRY));
|
||||
entry.data.resize(entry.info.size);
|
||||
memcpy(entry.data.data(), data_ptr + entry.info.offset, entry.info.size);
|
||||
entries.push_back(entry);
|
||||
|
||||
ptr += sizeof(X_XDBF_ENTRY);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < header.free_used; i++) {
|
||||
free_entries.push_back(*free_ptr);
|
||||
free_ptr++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XdbfBlock XdbfWrapper::GetEntry(XdbfSection section, uint64_t id) const {
|
||||
for (uint32_t i = 0; i < header_->entry_used; ++i) {
|
||||
auto& entry = entries_[i];
|
||||
if (entry.section == static_cast<uint16_t>(section) && entry.id == id) {
|
||||
XdbfBlock block;
|
||||
block.buffer = content_offset_ + entry.offset;
|
||||
block.size = entry.size;
|
||||
return block;
|
||||
bool XdbfFile::Write(uint8_t* data, size_t* data_size) {
|
||||
*data_size = 0;
|
||||
|
||||
*data_size += sizeof(X_XDBF_HEADER);
|
||||
*data_size += entries.size() * sizeof(X_XDBF_ENTRY);
|
||||
*data_size += free_entries.size() * sizeof(X_XDBF_FILELOC);
|
||||
|
||||
size_t entries_size = 0;
|
||||
for (auto ent : entries) {
|
||||
entries_size += ent.data.size();
|
||||
}
|
||||
|
||||
*data_size += entries_size;
|
||||
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
header.entry_count = header.entry_used = (uint32_t)entries.size();
|
||||
header.free_count = header.free_used = (uint32_t)free_entries.size();
|
||||
|
||||
auto* ptr = data;
|
||||
memcpy(ptr, &header, sizeof(X_XDBF_HEADER));
|
||||
ptr += sizeof(X_XDBF_HEADER);
|
||||
|
||||
auto* free_ptr =
|
||||
(X_XDBF_FILELOC*)(ptr + (sizeof(X_XDBF_ENTRY) * header.entry_count));
|
||||
auto* data_start =
|
||||
(uint8_t*)free_ptr + (sizeof(X_XDBF_FILELOC) * header.free_count);
|
||||
|
||||
auto* data_ptr = data_start;
|
||||
for (auto ent : entries) {
|
||||
ent.info.offset = (uint32_t)(data_ptr - data_start);
|
||||
ent.info.size = (uint32_t)ent.data.size();
|
||||
memcpy(ptr, &ent.info, sizeof(X_XDBF_ENTRY));
|
||||
|
||||
memcpy(data_ptr, ent.data.data(), ent.data.size());
|
||||
data_ptr += ent.data.size();
|
||||
ptr += sizeof(X_XDBF_ENTRY);
|
||||
}
|
||||
|
||||
for (auto ent : free_entries) {
|
||||
memcpy(free_ptr, &ent, sizeof(X_XDBF_FILELOC));
|
||||
free_ptr++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XdbfEntry* XdbfFile::GetEntry(uint16_t section, uint64_t id) const {
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
auto* entry = (XdbfEntry*)&entries[i];
|
||||
if (entry->info.section != section || entry->info.id != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
return {0};
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string XdbfWrapper::GetStringTableEntry(XdbfLocale locale,
|
||||
uint16_t string_id) const {
|
||||
auto language_block =
|
||||
GetEntry(XdbfSection::kStringTable, static_cast<uint64_t>(locale));
|
||||
if (!language_block) {
|
||||
return "";
|
||||
bool XdbfFile::UpdateEntry(XdbfEntry entry) {
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
auto* ent = (XdbfEntry*)&entries[i];
|
||||
if (ent->info.section != entry.info.section ||
|
||||
ent->info.id != entry.info.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ent->data = entry.data;
|
||||
ent->info.size = (uint32_t)entry.data.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto xstr_head =
|
||||
reinterpret_cast<const XdbfXstrHeader*>(language_block.buffer);
|
||||
assert_true(xstr_head->magic == kXdbfMagicXstr);
|
||||
assert_true(xstr_head->version == 1);
|
||||
XdbfEntry new_entry;
|
||||
new_entry.info.section = entry.info.section;
|
||||
new_entry.info.id = entry.info.id;
|
||||
new_entry.info.size = (uint32_t)entry.data.size();
|
||||
new_entry.data = entry.data;
|
||||
|
||||
const uint8_t* ptr = language_block.buffer + sizeof(XdbfXstrHeader);
|
||||
for (uint16_t i = 0; i < xstr_head->string_count; ++i) {
|
||||
entries.push_back(new_entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetStringTableEntry_(const uint8_t* table_start, uint16_t string_id,
|
||||
uint16_t count) {
|
||||
auto* ptr = table_start;
|
||||
for (uint16_t i = 0; i < count; ++i) {
|
||||
auto entry = reinterpret_cast<const XdbfStringTableEntry*>(ptr);
|
||||
ptr += sizeof(XdbfStringTableEntry);
|
||||
if (entry->id == string_id) {
|
||||
|
@ -81,25 +152,219 @@ std::string XdbfWrapper::GetStringTableEntry(XdbfLocale locale,
|
|||
return "";
|
||||
}
|
||||
|
||||
constexpr uint64_t kXdbfIdTitle = 0x8000;
|
||||
constexpr uint64_t kXdbfIdXstc = 0x58535443;
|
||||
std::string SpaFile::GetStringTableEntry(XdbfLocale locale,
|
||||
uint16_t string_id) const {
|
||||
auto xstr_table =
|
||||
GetEntry(static_cast<uint16_t>(XdbfSpaSection::kStringTable),
|
||||
static_cast<uint64_t>(locale));
|
||||
if (!xstr_table) {
|
||||
return "";
|
||||
}
|
||||
|
||||
XdbfBlock XdbfGameData::icon() const {
|
||||
return GetEntry(XdbfSection::kImage, kXdbfIdTitle);
|
||||
auto xstr_head =
|
||||
reinterpret_cast<const X_XDBF_TABLE_HEADER*>(xstr_table->data.data());
|
||||
assert_true(xstr_head->magic == static_cast<uint32_t>(XdbfSpaID::Xstr));
|
||||
assert_true(xstr_head->version == 1);
|
||||
|
||||
const uint8_t* ptr = xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER);
|
||||
|
||||
return GetStringTableEntry_(ptr, string_id, xstr_head->count);
|
||||
}
|
||||
|
||||
XdbfLocale XdbfGameData::default_language() const {
|
||||
auto block = GetEntry(XdbfSection::kMetadata, kXdbfIdXstc);
|
||||
if (!block.buffer) {
|
||||
uint32_t SpaFile::GetAchievements(
|
||||
XdbfLocale locale, std::vector<XdbfAchievement>* achievements) const {
|
||||
auto xach_table = GetEntry(static_cast<uint16_t>(XdbfSpaSection::kMetadata),
|
||||
static_cast<uint64_t>(XdbfSpaID::Xach));
|
||||
if (!xach_table) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto xach_head =
|
||||
reinterpret_cast<const X_XDBF_TABLE_HEADER*>(xach_table->data.data());
|
||||
assert_true(xach_head->magic == static_cast<uint32_t>(XdbfSpaID::Xach));
|
||||
assert_true(xach_head->version == 1);
|
||||
|
||||
auto xstr_table =
|
||||
GetEntry(static_cast<uint16_t>(XdbfSpaSection::kStringTable),
|
||||
static_cast<uint64_t>(locale));
|
||||
if (!xstr_table) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto xstr_head =
|
||||
reinterpret_cast<const X_XDBF_TABLE_HEADER*>(xstr_table->data.data());
|
||||
assert_true(xstr_head->magic == static_cast<uint32_t>(XdbfSpaID::Xstr));
|
||||
assert_true(xstr_head->version == 1);
|
||||
|
||||
const uint8_t* xstr_ptr =
|
||||
xstr_table->data.data() + sizeof(X_XDBF_TABLE_HEADER);
|
||||
|
||||
if (achievements) {
|
||||
auto* ach_data =
|
||||
reinterpret_cast<const X_XDBF_SPA_ACHIEVEMENT*>(xach_head + 1);
|
||||
for (uint32_t i = 0; i < xach_head->count; i++) {
|
||||
XdbfAchievement ach;
|
||||
ach.id = ach_data->id;
|
||||
ach.image_id = ach_data->image_id;
|
||||
ach.gamerscore = ach_data->gamerscore;
|
||||
ach.flags = ach_data->flags;
|
||||
|
||||
ach.label = xe::to_wstring(
|
||||
GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count));
|
||||
|
||||
ach.description = xe::to_wstring(GetStringTableEntry_(
|
||||
xstr_ptr, ach_data->description_id, xstr_head->count));
|
||||
|
||||
ach.unachieved_desc = xe::to_wstring(GetStringTableEntry_(
|
||||
xstr_ptr, ach_data->unachieved_id, xstr_head->count));
|
||||
|
||||
achievements->push_back(ach);
|
||||
ach_data++;
|
||||
}
|
||||
}
|
||||
|
||||
return xach_head->count;
|
||||
}
|
||||
|
||||
XdbfEntry* SpaFile::GetIcon() const {
|
||||
return GetEntry(static_cast<uint16_t>(XdbfSpaSection::kImage),
|
||||
static_cast<uint64_t>(XdbfSpaID::Title));
|
||||
}
|
||||
|
||||
XdbfLocale SpaFile::GetDefaultLocale() const {
|
||||
auto block = GetEntry(static_cast<uint16_t>(XdbfSpaSection::kMetadata),
|
||||
static_cast<uint64_t>(XdbfSpaID::Xstc));
|
||||
if (!block) {
|
||||
return XdbfLocale::kEnglish;
|
||||
}
|
||||
auto xstc = reinterpret_cast<const XdbfXstc*>(block.buffer);
|
||||
assert_true(xstc->magic == kXdbfMagicXstc);
|
||||
|
||||
auto xstc = reinterpret_cast<const X_XDBF_XSTC_DATA*>(block->data.data());
|
||||
assert_true(xstc->magic == static_cast<uint32_t>(XdbfSpaID::Xstc));
|
||||
|
||||
return static_cast<XdbfLocale>(static_cast<uint32_t>(xstc->default_language));
|
||||
}
|
||||
|
||||
std::string XdbfGameData::title() const {
|
||||
return GetStringTableEntry(default_language(), kXdbfIdTitle);
|
||||
std::string SpaFile::GetTitle() const {
|
||||
return GetStringTableEntry(GetDefaultLocale(),
|
||||
static_cast<uint16_t>(XdbfSpaID::Title));
|
||||
}
|
||||
|
||||
std::wstring ReadNullTermString(const wchar_t* ptr) {
|
||||
std::wstring retval;
|
||||
wchar_t data = xe::byte_swap(*ptr);
|
||||
while (data != 0) {
|
||||
retval += data;
|
||||
ptr++;
|
||||
data = xe::byte_swap(*ptr);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void ConvertGPDToXdbfAchievement(const X_XDBF_GPD_ACHIEVEMENT* src,
|
||||
XdbfAchievement* dest) {
|
||||
dest->id = src->id;
|
||||
dest->image_id = src->image_id;
|
||||
dest->gamerscore = src->gamerscore;
|
||||
dest->flags = src->flags;
|
||||
dest->unlock_time = src->unlock_time;
|
||||
|
||||
auto* txt_ptr = reinterpret_cast<const uint8_t*>(src + 1);
|
||||
|
||||
dest->label = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||
|
||||
txt_ptr += (dest->label.length() * 2) + 2;
|
||||
dest->description = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||
|
||||
txt_ptr += (dest->description.length() * 2) + 2;
|
||||
dest->unachieved_desc = ReadNullTermString((const wchar_t*)txt_ptr);
|
||||
}
|
||||
|
||||
bool GpdFile::GetAchievement(uint16_t id, XdbfAchievement* dest) {
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
auto* entry = (XdbfEntry*)&entries[i];
|
||||
if (entry->info.section !=
|
||||
static_cast<uint16_t>(XdbfGpdSection::kAchievement) ||
|
||||
entry->info.id != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* ach_data =
|
||||
reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
|
||||
|
||||
ConvertGPDToXdbfAchievement(ach_data, dest);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t GpdFile::GetAchievements(
|
||||
std::vector<XdbfAchievement>* achievements) const {
|
||||
uint32_t ach_count = 0;
|
||||
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
auto* entry = (XdbfEntry*)&entries[i];
|
||||
if (entry->info.section !=
|
||||
static_cast<uint16_t>(XdbfGpdSection::kAchievement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ach_count++;
|
||||
|
||||
if (achievements) {
|
||||
auto* ach_data =
|
||||
reinterpret_cast<const X_XDBF_GPD_ACHIEVEMENT*>(entry->data.data());
|
||||
|
||||
XdbfAchievement ach;
|
||||
ConvertGPDToXdbfAchievement(ach_data, &ach);
|
||||
|
||||
achievements->push_back(ach);
|
||||
}
|
||||
}
|
||||
|
||||
return ach_count;
|
||||
}
|
||||
|
||||
bool GpdFile::UpdateAchievement(XdbfAchievement ach) {
|
||||
XdbfEntry ent;
|
||||
ent.info.section = static_cast<uint16_t>(XdbfGpdSection::kAchievement);
|
||||
ent.info.id = ach.id;
|
||||
|
||||
// calculate entry size...
|
||||
size_t label_len = (ach.label.length() * 2) + 2;
|
||||
size_t desc_len = (ach.description.length() * 2) + 2;
|
||||
size_t unach_len = (ach.unachieved_desc.length() * 2) + 2;
|
||||
|
||||
size_t est_size = sizeof(X_XDBF_GPD_ACHIEVEMENT);
|
||||
est_size += label_len;
|
||||
est_size += desc_len;
|
||||
est_size += unach_len;
|
||||
|
||||
ent.data.resize(est_size);
|
||||
memset(ent.data.data(), 0, est_size);
|
||||
|
||||
// convert XdbfAchievement to GPD achievement
|
||||
auto* ach_data = reinterpret_cast<X_XDBF_GPD_ACHIEVEMENT*>(ent.data.data());
|
||||
ach_data->id = ach.id;
|
||||
ach_data->image_id = ach.image_id;
|
||||
ach_data->gamerscore = ach.gamerscore;
|
||||
ach_data->flags = ach.flags;
|
||||
ach_data->unlock_time = ach.unlock_time;
|
||||
|
||||
auto* label_ptr = reinterpret_cast<uint8_t*>(ent.data.data() +
|
||||
sizeof(X_XDBF_GPD_ACHIEVEMENT));
|
||||
auto* desc_ptr = label_ptr + label_len;
|
||||
auto* unach_ptr = desc_ptr + desc_len;
|
||||
|
||||
xe::copy_and_swap<wchar_t>((wchar_t*)label_ptr, ach.label.c_str(),
|
||||
ach.label.size());
|
||||
xe::copy_and_swap<wchar_t>((wchar_t*)desc_ptr, ach.description.c_str(),
|
||||
ach.description.size());
|
||||
xe::copy_and_swap<wchar_t>((wchar_t*)unach_ptr, ach.unachieved_desc.c_str(),
|
||||
ach.unachieved_desc.size());
|
||||
|
||||
UpdateEntry(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
|
|
@ -22,10 +22,26 @@ namespace util {
|
|||
// 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 XdbfSection : uint16_t {
|
||||
kMetadata = 0x0001,
|
||||
kImage = 0x0002,
|
||||
kStringTable = 0x0003,
|
||||
enum class XdbfSpaID : uint64_t {
|
||||
Xach = 'XACH',
|
||||
Xstr = 'XSTR',
|
||||
Xstc = 'XSTC',
|
||||
Title = 0x8000,
|
||||
};
|
||||
|
||||
enum class XdbfSpaSection : uint16_t {
|
||||
kMetadata = 0x1,
|
||||
kImage = 0x2,
|
||||
kStringTable = 0x3,
|
||||
};
|
||||
|
||||
enum class XdbfGpdSection : uint16_t {
|
||||
kAchievement = 0x1,
|
||||
kImage = 0x2,
|
||||
kSetting = 0x3,
|
||||
kTitle = 0x4,
|
||||
kString = 0x5,
|
||||
kSecurity = 0x6
|
||||
};
|
||||
|
||||
// Found by dumping the kSectionStringTable sections of various games:
|
||||
|
@ -41,102 +57,186 @@ enum class XdbfLocale : uint32_t {
|
|||
kChinese = 8,
|
||||
};
|
||||
|
||||
struct XdbfBlock {
|
||||
const uint8_t* buffer;
|
||||
size_t size;
|
||||
struct XdbfStringTableEntry {
|
||||
xe::be<uint16_t> id;
|
||||
xe::be<uint16_t> string_length;
|
||||
};
|
||||
static_assert_size(XdbfStringTableEntry, 4);
|
||||
|
||||
operator bool() const { return buffer != nullptr; }
|
||||
#pragma pack(push, 1)
|
||||
struct X_XDBF_HEADER {
|
||||
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(X_XDBF_HEADER, 24);
|
||||
|
||||
struct X_XDBF_ENTRY {
|
||||
xe::be<uint16_t> section;
|
||||
xe::be<uint64_t> id;
|
||||
xe::be<uint32_t> offset;
|
||||
xe::be<uint32_t> size;
|
||||
};
|
||||
static_assert_size(X_XDBF_ENTRY, 18);
|
||||
|
||||
struct X_XDBF_FILELOC {
|
||||
xe::be<uint32_t> offset;
|
||||
xe::be<uint32_t> size;
|
||||
};
|
||||
static_assert_size(X_XDBF_FILELOC, 8);
|
||||
|
||||
struct X_XDBF_XSTC_DATA {
|
||||
xe::be<uint32_t> magic;
|
||||
xe::be<uint32_t> version;
|
||||
xe::be<uint32_t> size;
|
||||
xe::be<uint32_t> default_language;
|
||||
};
|
||||
static_assert_size(X_XDBF_XSTC_DATA, 16);
|
||||
|
||||
struct X_XDBF_TABLE_HEADER {
|
||||
xe::be<uint32_t> magic;
|
||||
xe::be<uint32_t> version;
|
||||
xe::be<uint32_t> size;
|
||||
xe::be<uint16_t> count;
|
||||
};
|
||||
static_assert_size(X_XDBF_TABLE_HEADER, 14);
|
||||
|
||||
struct X_XDBF_SPA_ACHIEVEMENT {
|
||||
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(X_XDBF_SPA_ACHIEVEMENT, 0x24);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// Wraps an XBDF (XboxDataBaseFormat) in-memory database.
|
||||
// https://free60project.github.io/wiki/XDBF.html
|
||||
class XdbfWrapper {
|
||||
public:
|
||||
XdbfWrapper(const uint8_t* data, size_t data_size);
|
||||
|
||||
// True if the target memory contains a valid XDBF instance.
|
||||
bool is_valid() const { return data_ != nullptr; }
|
||||
|
||||
// Gets an entry in the given section.
|
||||
// If the entry is not found the returned block will be nullptr.
|
||||
XdbfBlock GetEntry(XdbfSection section, uint64_t id) const;
|
||||
|
||||
// Gets a string from the string table in the given language.
|
||||
// Returns the empty string if the entry is not found.
|
||||
std::string GetStringTableEntry(XdbfLocale locale, uint16_t string_id) const;
|
||||
|
||||
protected:
|
||||
#pragma pack(push, 1)
|
||||
struct XbdfHeader {
|
||||
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(XbdfHeader, 24);
|
||||
|
||||
struct XbdfEntry {
|
||||
xe::be<uint16_t> section;
|
||||
xe::be<uint64_t> id;
|
||||
xe::be<uint32_t> offset;
|
||||
xe::be<uint32_t> size;
|
||||
};
|
||||
static_assert_size(XbdfEntry, 18);
|
||||
|
||||
struct XbdfFileLoc {
|
||||
xe::be<uint32_t> offset;
|
||||
xe::be<uint32_t> size;
|
||||
};
|
||||
static_assert_size(XbdfFileLoc, 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 XdbfXstrHeader {
|
||||
xe::be<uint32_t> magic;
|
||||
xe::be<uint32_t> version;
|
||||
xe::be<uint32_t> size;
|
||||
xe::be<uint16_t> string_count;
|
||||
};
|
||||
static_assert_size(XdbfXstrHeader, 14);
|
||||
|
||||
struct XdbfStringTableEntry {
|
||||
xe::be<uint16_t> id;
|
||||
xe::be<uint16_t> string_length;
|
||||
};
|
||||
static_assert_size(XdbfStringTableEntry, 4);
|
||||
#pragma pack(pop)
|
||||
|
||||
private:
|
||||
const uint8_t* data_ = nullptr;
|
||||
size_t data_size_ = 0;
|
||||
const uint8_t* content_offset_ = nullptr;
|
||||
|
||||
const XbdfHeader* header_ = nullptr;
|
||||
const XbdfEntry* entries_ = nullptr;
|
||||
const XbdfFileLoc* files_ = nullptr;
|
||||
enum class XdbfAchievementType : uint32_t {
|
||||
kCompletion = 1,
|
||||
kLeveling = 2,
|
||||
kUnlock = 3,
|
||||
kEvent = 4,
|
||||
kTournament = 5,
|
||||
kCheckpoint = 6,
|
||||
kOther = 7,
|
||||
};
|
||||
|
||||
class XdbfGameData : public XdbfWrapper {
|
||||
enum class XdbfAchievementFlags : uint32_t {
|
||||
kTypeMask = 0x7,
|
||||
kShowUnachieved = 0x8,
|
||||
kAchievedOnline = 0x10000,
|
||||
kAchieved = 0x20000
|
||||
};
|
||||
|
||||
struct XdbfAchievement {
|
||||
uint16_t id = 0;
|
||||
std::wstring label;
|
||||
std::wstring description;
|
||||
std::wstring unachieved_desc;
|
||||
uint32_t image_id = 0;
|
||||
uint32_t gamerscore = 0;
|
||||
uint32_t flags = 0;
|
||||
uint64_t unlock_time = 0;
|
||||
|
||||
XdbfAchievementType GetType() {
|
||||
return static_cast<XdbfAchievementType>(
|
||||
flags & static_cast<uint32_t>(XdbfAchievementFlags::kTypeMask));
|
||||
}
|
||||
|
||||
bool IsUnlocked() {
|
||||
return flags & static_cast<uint32_t>(XdbfAchievementFlags::kAchieved);
|
||||
}
|
||||
|
||||
bool IsUnlockedOnline() {
|
||||
return flags & static_cast<uint32_t>(XdbfAchievementFlags::kAchievedOnline);
|
||||
}
|
||||
|
||||
void Unlock(bool online = false) {
|
||||
flags |= static_cast<uint32_t>(XdbfAchievementFlags::kAchieved);
|
||||
if (online) {
|
||||
flags |= static_cast<uint32_t>(XdbfAchievementFlags::kAchievedOnline);
|
||||
}
|
||||
// TODO: set unlock time?
|
||||
}
|
||||
|
||||
void Lock() {
|
||||
flags = flags & ~(static_cast<uint32_t>(XdbfAchievementFlags::kAchieved));
|
||||
flags =
|
||||
flags & ~(static_cast<uint32_t>(XdbfAchievementFlags::kAchievedOnline));
|
||||
unlock_time = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct XdbfEntry {
|
||||
X_XDBF_ENTRY info;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
// Parses/creates an XDBF (XboxDataBaseFormat) file
|
||||
// http://www.free60.org/wiki/XDBF
|
||||
class XdbfFile {
|
||||
public:
|
||||
XdbfGameData(const uint8_t* data, size_t data_size)
|
||||
: XdbfWrapper(data, data_size) {}
|
||||
XdbfFile() {
|
||||
header.magic = 'XDBF';
|
||||
header.version = 1;
|
||||
}
|
||||
|
||||
// The game icon image, if found.
|
||||
XdbfBlock icon() const;
|
||||
bool Read(const uint8_t* data, size_t data_size);
|
||||
bool Write(uint8_t* data, size_t* data_size);
|
||||
|
||||
// The game's default language.
|
||||
XdbfLocale default_language() const;
|
||||
XdbfEntry* GetEntry(uint16_t section, uint64_t id) const;
|
||||
|
||||
// The game's title in its default language.
|
||||
std::string title() const;
|
||||
// Updates (or adds) an entry
|
||||
bool UpdateEntry(XdbfEntry entry);
|
||||
|
||||
protected:
|
||||
X_XDBF_HEADER header;
|
||||
std::vector<XdbfEntry> entries;
|
||||
std::vector<X_XDBF_FILELOC> free_entries;
|
||||
};
|
||||
|
||||
class SpaFile : public XdbfFile {
|
||||
public:
|
||||
std::string GetStringTableEntry(XdbfLocale locale, uint16_t string_id) const;
|
||||
|
||||
uint32_t GetAchievements(XdbfLocale locale,
|
||||
std::vector<XdbfAchievement>* achievements) const;
|
||||
|
||||
XdbfEntry* GetIcon() const;
|
||||
XdbfLocale GetDefaultLocale() const;
|
||||
std::string GetTitle() const;
|
||||
};
|
||||
|
||||
class GpdFile : public XdbfFile {
|
||||
public:
|
||||
bool GetAchievement(uint16_t id, XdbfAchievement* dest);
|
||||
uint32_t GetAchievements(std::vector<XdbfAchievement>* achievements) const;
|
||||
|
||||
// Updates (or adds) an achievement
|
||||
bool UpdateAchievement(XdbfAchievement ach);
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
|
|
Loading…
Reference in New Issue