[XAM] Implemented SPA Stats Views
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "xenia/emulator.h"
|
||||
|
||||
#include "config.h"
|
||||
@@ -1538,7 +1540,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
entry.description, type,
|
||||
fmt::format("{}", entry.gamerscore)});
|
||||
}
|
||||
XELOGI("-------------------- ACHIEVEMENTS --------------------\n{}",
|
||||
XELOGI("\n-------------------- ACHIEVEMENTS --------------------\n{}",
|
||||
table.str());
|
||||
|
||||
const std::vector<kernel::util::GameInfoDatabase::Property>
|
||||
@@ -1555,7 +1557,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
table.add_row({fmt::format("{:08X}", entry.id), label,
|
||||
fmt::format("{}", entry.data_size)});
|
||||
}
|
||||
XELOGI("-------------------- PROPERTIES --------------------\n{}",
|
||||
XELOGI("\n-------------------- PROPERTIES --------------------\n{}",
|
||||
table.str());
|
||||
|
||||
const std::vector<kernel::util::GameInfoDatabase::Context> contexts_list =
|
||||
@@ -1573,9 +1575,46 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
fmt::format("{}", entry.default_value),
|
||||
fmt::format("{}", entry.max_value)});
|
||||
}
|
||||
XELOGI("-------------------- CONTEXTS --------------------\n{}",
|
||||
XELOGI("\n-------------------- CONTEXTS --------------------\n{}",
|
||||
table.str());
|
||||
|
||||
const std::vector<kernel::util::GameInfoDatabase::StatsView> stats_views =
|
||||
game_info_database_->GetStatsViews();
|
||||
|
||||
// 4D5307EA SPA contains a lot of stats, limit views to log.
|
||||
const auto stats_views_limit = stats_views | std::views::take(100);
|
||||
|
||||
table = tabulate::Table();
|
||||
table.format().multi_byte_characters(true);
|
||||
table.add_row({"ID", "View Type", "Name", "Skilled", "Arbitrated",
|
||||
"Hidden", "Team View", "Online Only"});
|
||||
|
||||
for (const kernel::util::GameInfoDatabase::StatsView& entry :
|
||||
stats_views_limit) {
|
||||
const std::string name =
|
||||
string_util::remove_eol(string_util::trim(entry.view.name));
|
||||
|
||||
const std::string view_type =
|
||||
kernel::xam::GetViewTypeName(entry.view.view_type);
|
||||
|
||||
table.add_row({fmt::format("{:08X}", entry.view.id), view_type, name,
|
||||
entry.view.skilled ? "True" : "False",
|
||||
entry.view.arbitrated ? "True" : "False",
|
||||
entry.view.hidden ? "True" : "False",
|
||||
entry.view.team_view ? "True" : "False",
|
||||
entry.view.online_only ? "True" : "False"});
|
||||
}
|
||||
|
||||
std::string totals;
|
||||
|
||||
if (stats_views.size() > stats_views_limit.size()) {
|
||||
totals = fmt::format("\nViews: {}/{}", stats_views_limit.size(),
|
||||
stats_views.size());
|
||||
}
|
||||
|
||||
XELOGI("\n-------------------- Stats Views --------------------{}\n{}",
|
||||
totals.c_str(), table.str());
|
||||
|
||||
auto icon_block = game_info_database_->GetIcon();
|
||||
if (!icon_block.empty()) {
|
||||
display_window_->SetIcon(icon_block.data(), icon_block.size());
|
||||
|
||||
@@ -149,6 +149,85 @@ GameInfoDatabase::Achievement GameInfoDatabase::GetAchievement(
|
||||
return achievement;
|
||||
}
|
||||
|
||||
GameInfoDatabase::PropertyBag GameInfoDatabase::GetPropertyBag(
|
||||
const xam::PropertyBag& property_bag) const {
|
||||
PropertyBag property_bag_native = {};
|
||||
|
||||
property_bag_native.contexts = {property_bag.contexts.cbegin(),
|
||||
property_bag.contexts.cend()};
|
||||
property_bag_native.properties = {property_bag.properties.cbegin(),
|
||||
property_bag.properties.cend()};
|
||||
|
||||
return property_bag_native;
|
||||
}
|
||||
|
||||
GameInfoDatabase::Field GameInfoDatabase::GetField(
|
||||
const xam::ViewFieldEntry& field_entry) const {
|
||||
Field field = {};
|
||||
|
||||
field.property_id = field_entry.property_id;
|
||||
field.flags = field_entry.flags;
|
||||
field.attribute_id = field_entry.attribute_id;
|
||||
field.aggregation_type = field_entry.aggregation_type;
|
||||
field.ordinal = field_entry.ordinal;
|
||||
field.field_type = field_entry.field_type;
|
||||
field.format_type = field_entry.format_type;
|
||||
field.name = GetLocalizedString(field_entry.string_id);
|
||||
|
||||
if (field.name.empty()) {
|
||||
field.name = xam::AttributeIdToName(field.attribute_id);
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
GameInfoDatabase::StatsView GameInfoDatabase::GetStatsView(
|
||||
const uint32_t id) const {
|
||||
StatsView stats_view = {};
|
||||
|
||||
if (!is_valid_) {
|
||||
return stats_view;
|
||||
}
|
||||
|
||||
const auto xdbf_stats_view = spa_gamedata_->GetStatsView(id);
|
||||
|
||||
if (!xdbf_stats_view.has_value()) {
|
||||
return stats_view;
|
||||
}
|
||||
|
||||
stats_view.view.id = xdbf_stats_view->view_entry.id;
|
||||
|
||||
stats_view.view.arbitrated =
|
||||
xam::IsArbitrated(xdbf_stats_view->view_entry.flags);
|
||||
stats_view.view.hidden = xam::IsHidden(xdbf_stats_view->view_entry.flags);
|
||||
stats_view.view.team_view =
|
||||
xam::IsTeamView(xdbf_stats_view->view_entry.flags);
|
||||
stats_view.view.online_only =
|
||||
xam::IsOnlineOnly(xdbf_stats_view->view_entry.flags);
|
||||
|
||||
stats_view.view.view_type =
|
||||
xam::GetViewType(xdbf_stats_view->view_entry.flags);
|
||||
stats_view.view.skilled = xam::IsLeaderboardIdSkill(stats_view.view.id);
|
||||
|
||||
stats_view.view.shared_index = xdbf_stats_view->view_entry.shared_index;
|
||||
|
||||
stats_view.view.name =
|
||||
GetLocalizedString(xdbf_stats_view->view_entry.string_id);
|
||||
|
||||
for (const auto& column : xdbf_stats_view->shared_view.column_entries) {
|
||||
stats_view.shared_view.column_entries.push_back(GetField(column));
|
||||
}
|
||||
|
||||
for (const auto& row : xdbf_stats_view->shared_view.row_entries) {
|
||||
stats_view.shared_view.row_entries.push_back(GetField(row));
|
||||
}
|
||||
|
||||
stats_view.shared_view.properties =
|
||||
GetPropertyBag(xdbf_stats_view->shared_view.property_bag);
|
||||
|
||||
return stats_view;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> GameInfoDatabase::GetMatchmakingAttributes(
|
||||
const uint32_t id) const {
|
||||
// TODO(Gliniak): Implement when we will fully understand how to read it from
|
||||
@@ -279,6 +358,23 @@ std::vector<GameInfoDatabase::Achievement> GameInfoDatabase::GetAchievements()
|
||||
return achievements;
|
||||
}
|
||||
|
||||
std::vector<GameInfoDatabase::StatsView> GameInfoDatabase::GetStatsViews()
|
||||
const {
|
||||
std::vector<StatsView> stats_views;
|
||||
|
||||
if (!is_valid_) {
|
||||
return stats_views;
|
||||
}
|
||||
|
||||
const auto xdbf_stats_views = spa_gamedata_->GetStatsViews();
|
||||
|
||||
for (const auto& entry : *xdbf_stats_views) {
|
||||
stats_views.push_back(GetStatsView(entry.view_entry.id));
|
||||
}
|
||||
|
||||
return stats_views;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef XENIA_KERNEL_UTIL_GAME_INFO_DATABASE_H_
|
||||
#define XENIA_KERNEL_UTIL_GAME_INFO_DATABASE_H_
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,6 +23,11 @@ namespace util {
|
||||
|
||||
class GameInfoDatabase {
|
||||
public:
|
||||
struct PropertyBag {
|
||||
std::set<uint32_t> contexts;
|
||||
std::set<uint32_t> properties;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
uint32_t id;
|
||||
uint32_t max_value;
|
||||
@@ -60,18 +66,37 @@ class GameInfoDatabase {
|
||||
};
|
||||
|
||||
struct Field {
|
||||
uint32_t ordinal;
|
||||
std::string name;
|
||||
bool is_hidden;
|
||||
uint32_t property_id;
|
||||
uint32_t flags;
|
||||
uint16_t attribute_id;
|
||||
// std::map<property_id, aggregation string>
|
||||
std::map<uint32_t, std::string> property_aggregation;
|
||||
uint16_t aggregation_type;
|
||||
uint8_t ordinal;
|
||||
uint8_t field_type;
|
||||
uint32_t format_type;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct SharedView {
|
||||
std::vector<Field> column_entries;
|
||||
std::vector<Field> row_entries;
|
||||
PropertyBag properties;
|
||||
};
|
||||
|
||||
struct View {
|
||||
uint32_t id;
|
||||
bool arbitrated;
|
||||
bool hidden;
|
||||
bool team_view;
|
||||
bool online_only;
|
||||
bool skilled;
|
||||
kernel::xam::ViewType view_type;
|
||||
uint16_t shared_index;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct StatsView {
|
||||
uint32_t id;
|
||||
std::string name;
|
||||
std::vector<Field> fields;
|
||||
View view;
|
||||
SharedView shared_view;
|
||||
};
|
||||
|
||||
struct ProductInformation {
|
||||
@@ -106,9 +131,11 @@ class GameInfoDatabase {
|
||||
Context GetContext(const uint32_t id) const;
|
||||
Property GetProperty(const uint32_t id) const;
|
||||
Achievement GetAchievement(const uint32_t id) const;
|
||||
PropertyBag GetPropertyBag(const xam::PropertyBag& property_bag) const;
|
||||
Field GetField(const xam::ViewFieldEntry& field_entry) const;
|
||||
StatsView GetStatsView(const uint32_t id) const;
|
||||
|
||||
// TODO: Implement it in the future.
|
||||
StatsView GetStatsView(const uint32_t id) const;
|
||||
std::vector<uint32_t> GetMatchmakingAttributes(const uint32_t id) const;
|
||||
|
||||
// This is extracted from XLast.
|
||||
@@ -120,7 +147,6 @@ class GameInfoDatabase {
|
||||
std::vector<Context> GetContexts() const;
|
||||
std::vector<Property> GetProperties() const;
|
||||
std::vector<Achievement> GetAchievements() const;
|
||||
// TODO: Implement it in the future.
|
||||
std::vector<StatsView> GetStatsViews() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -75,6 +75,19 @@ using UserDataTypes = std::variant<uint32_t, int32_t, float, int64_t, double,
|
||||
std::u16string, std::vector<uint8_t>>;
|
||||
|
||||
constexpr uint32_t kMaxUserDataSize = 0x03E8;
|
||||
constexpr uint32_t kInvalidSize = 0xFFFF;
|
||||
|
||||
constexpr uint32_t kPropertyScopeMask = 0x8000;
|
||||
constexpr uint32_t kPropertyIdMask = 0x7FFF;
|
||||
constexpr uint32_t kPropertyTypeMask = 0xF0000000;
|
||||
|
||||
constexpr uint32_t kInvalidPropertyId = 0xFFFF;
|
||||
|
||||
constexpr uint32_t kInvalidContextId = 0xFFFF;
|
||||
constexpr uint32_t kMaxContextId = 0x7FFF;
|
||||
|
||||
constexpr uint32_t kInvalidContextValue = 0xFFFF;
|
||||
constexpr uint32_t kMaxContextValue = 0x7FFF;
|
||||
|
||||
class UserData {
|
||||
public:
|
||||
|
||||
@@ -24,6 +24,7 @@ void SpaInfo::Load() {
|
||||
LoadAchievements();
|
||||
LoadProperties();
|
||||
LoadContexts();
|
||||
LoadStatsViews();
|
||||
}
|
||||
|
||||
bool operator<(const SpaInfo& first, const SpaInfo& second) {
|
||||
@@ -148,6 +149,109 @@ void SpaInfo::LoadContexts() {
|
||||
}
|
||||
}
|
||||
|
||||
void SpaInfo::LoadStatsViews() {
|
||||
auto stats_metadata =
|
||||
GetEntry(static_cast<uint16_t>(SpaSection::kMetadata), kXdbfIdXvc2);
|
||||
if (!stats_metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto xvc2_head =
|
||||
reinterpret_cast<const XdbfSectionHeader*>(stats_metadata->data.data());
|
||||
assert_true(xvc2_head->magic == kXdbfSignatureXvc2);
|
||||
assert_true(xvc2_head->version == 1);
|
||||
|
||||
std::vector<SharedView> shared_views = {};
|
||||
|
||||
const uint16_t shared_view_metadata_count =
|
||||
xe::load_and_swap<uint16_t>(xvc2_head + 1);
|
||||
|
||||
auto shared_view_meta_table_entry_ptr =
|
||||
reinterpret_cast<const uint8_t*>(xvc2_head + 1);
|
||||
|
||||
shared_view_meta_table_entry_ptr += sizeof(uint16_t);
|
||||
|
||||
for (uint32_t i = 0; i < shared_view_metadata_count; i++) {
|
||||
SharedView shared_view = {};
|
||||
|
||||
auto shared_view_entry_ptr =
|
||||
reinterpret_cast<const SharedViewMetaTableEntry*>(
|
||||
shared_view_meta_table_entry_ptr);
|
||||
|
||||
auto view_field_ptr =
|
||||
reinterpret_cast<const ViewFieldEntry*>(shared_view_entry_ptr + 1);
|
||||
|
||||
for (uint32_t i = 0; i < shared_view_entry_ptr->column_count; i++) {
|
||||
shared_view.column_entries.push_back(
|
||||
*reinterpret_cast<const ViewFieldEntry*>(view_field_ptr + i));
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < shared_view_entry_ptr->row_count; i++) {
|
||||
shared_view.row_entries.push_back(
|
||||
*reinterpret_cast<const ViewFieldEntry*>(
|
||||
view_field_ptr + shared_view_entry_ptr->column_count + i));
|
||||
}
|
||||
|
||||
const uint32_t entries_count =
|
||||
shared_view_entry_ptr->column_count + shared_view_entry_ptr->row_count;
|
||||
|
||||
auto xpbm_head = reinterpret_cast<const XdbfSectionHeader*>(view_field_ptr +
|
||||
entries_count);
|
||||
|
||||
assert_true(xpbm_head->magic == kXdbfSignatureXpbm);
|
||||
assert_true(xpbm_head->version == 1);
|
||||
|
||||
const PropertyBagEntry* property_bag_header_ptr =
|
||||
reinterpret_cast<const PropertyBagEntry*>(xpbm_head + 1);
|
||||
|
||||
auto contexts_ptr =
|
||||
reinterpret_cast<const xe::be<uint32_t>*>(property_bag_header_ptr + 1);
|
||||
|
||||
auto properties_ptr = reinterpret_cast<const xe::be<uint32_t>*>(
|
||||
contexts_ptr + property_bag_header_ptr->contexts_count);
|
||||
|
||||
for (uint32_t i = 0; i < property_bag_header_ptr->contexts_count; i++) {
|
||||
shared_view.property_bag.contexts.insert(contexts_ptr[i]);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < property_bag_header_ptr->properties_count; i++) {
|
||||
shared_view.property_bag.properties.insert(properties_ptr[i]);
|
||||
}
|
||||
|
||||
const uint32_t xpbm_size = xpbm_head->size + sizeof(uint32_t);
|
||||
|
||||
shared_view_meta_table_entry_ptr =
|
||||
reinterpret_cast<const uint8_t*>(xpbm_head) + xpbm_size;
|
||||
|
||||
shared_views.push_back(shared_view);
|
||||
}
|
||||
|
||||
const uint16_t tables_count =
|
||||
xe::load_and_swap<uint16_t>(shared_view_meta_table_entry_ptr);
|
||||
|
||||
const StatsViewTableEntry* stats_views_ptr =
|
||||
reinterpret_cast<const StatsViewTableEntry*>(
|
||||
shared_view_meta_table_entry_ptr + sizeof(uint16_t));
|
||||
|
||||
const auto views = std::vector<StatsViewTableEntry>(
|
||||
stats_views_ptr, stats_views_ptr + tables_count);
|
||||
|
||||
for (const auto& view : views) {
|
||||
ViewTable view_table = {};
|
||||
|
||||
// What are kContextByProperty and kContextByContext views?
|
||||
assert_zero(static_cast<uint32_t>(GetViewType(view.flags)));
|
||||
|
||||
if (view.shared_index < shared_views.size()) {
|
||||
view_table.shared_view = shared_views.at(view.shared_index);
|
||||
}
|
||||
|
||||
view_table.view_entry = view;
|
||||
|
||||
stats_views_.push_back(view_table);
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* SpaInfo::ReadXLast(uint32_t& compressed_size,
|
||||
uint32_t& decompressed_size) {
|
||||
auto xlast_table =
|
||||
@@ -276,6 +380,14 @@ const XdbfPropertyTableEntry* SpaInfo::GetProperty(uint32_t id) {
|
||||
return GetSpaEntry<const XdbfPropertyTableEntry*>(properties_, id);
|
||||
}
|
||||
|
||||
const std::optional<ViewTable> SpaInfo::GetStatsView(uint32_t id) {
|
||||
const auto itr = std::ranges::find_if(
|
||||
stats_views_,
|
||||
[id](const ViewTable& view) { return view.view_entry.id == id; });
|
||||
|
||||
return itr != stats_views_.cend() ? std::make_optional(*itr) : std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T SpaInfo::GetSpaEntry(std::vector<T>& container, uint32_t id) {
|
||||
for (const auto& entry : container) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -47,7 +49,122 @@ enum class TitleFlags {
|
||||
kNeverIncludeInProfile = 2,
|
||||
};
|
||||
|
||||
constexpr uint32_t kViewTypeMask = 0xF;
|
||||
|
||||
enum class ViewType : uint32_t {
|
||||
kLeaderboard = 0,
|
||||
kContextByProperty = 1,
|
||||
kContextByContext = 2
|
||||
};
|
||||
|
||||
enum class StatsViewFlags : uint16_t {
|
||||
kArbitrated = 16,
|
||||
kHidden = 32,
|
||||
kTeamView = 64,
|
||||
kOnlineOnly = 128,
|
||||
};
|
||||
|
||||
enum class AggregationType : uint16_t {
|
||||
kLast = 0x8001,
|
||||
kMax = 0x800B,
|
||||
kSum = 0x8003,
|
||||
kMin = 0x8009,
|
||||
};
|
||||
|
||||
enum class ViewFieldEntryFlags : uint32_t {
|
||||
kHidden = 1,
|
||||
};
|
||||
|
||||
enum class ViewFieldType : uint8_t { kContextField, kPropertyField };
|
||||
|
||||
constexpr inline std::string GetAggregationTypeName(
|
||||
const uint32_t aggregation_type) {
|
||||
switch (static_cast<AggregationType>(aggregation_type)) {
|
||||
case AggregationType::kLast:
|
||||
return "Last";
|
||||
case AggregationType::kMax:
|
||||
return "Max";
|
||||
case AggregationType::kSum:
|
||||
return "Sum";
|
||||
case AggregationType::kMin:
|
||||
return "Min";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr inline std::string GetViewTypeName(const uint32_t view_type) {
|
||||
switch (static_cast<ViewType>(view_type)) {
|
||||
case ViewType::kLeaderboard:
|
||||
return "Leaderboard";
|
||||
case ViewType::kContextByProperty:
|
||||
return "Context by Property";
|
||||
case ViewType::kContextByContext:
|
||||
return "Context by Context";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
constexpr inline std::string AttributeIdToName(const uint16_t id) {
|
||||
switch (id) {
|
||||
case std::numeric_limits<uint16_t>::max():
|
||||
return "Rank";
|
||||
case 65534:
|
||||
return "Rating";
|
||||
case 65533:
|
||||
return "Gamertag";
|
||||
case 65530:
|
||||
return "Attachment Size";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr inline std::string GetViewTypeName(const ViewType view_type) {
|
||||
return GetViewTypeName(static_cast<uint32_t>(view_type));
|
||||
}
|
||||
|
||||
constexpr inline ViewType GetViewType(const uint32_t flags) {
|
||||
return static_cast<ViewType>(flags & kViewTypeMask);
|
||||
}
|
||||
|
||||
constexpr inline bool IsArbitrated(const uint32_t flags) {
|
||||
return flags & static_cast<uint32_t>(StatsViewFlags::kArbitrated);
|
||||
}
|
||||
|
||||
constexpr inline bool IsHidden(const uint32_t flags) {
|
||||
return flags & static_cast<uint32_t>(StatsViewFlags::kHidden);
|
||||
}
|
||||
|
||||
constexpr inline bool IsTeamView(const uint32_t flags) {
|
||||
return flags & static_cast<uint32_t>(StatsViewFlags::kTeamView);
|
||||
}
|
||||
|
||||
constexpr inline bool IsOnlineOnly(const uint32_t flags) {
|
||||
return flags & static_cast<uint32_t>(StatsViewFlags::kOnlineOnly);
|
||||
}
|
||||
|
||||
constexpr inline uint32_t GetSkillLeaderboardId(uint32_t game_type,
|
||||
uint32_t game_mode) {
|
||||
return (0xFFF00000 | (game_type == 1 ? 0xF0000 : 0xE0000)) |
|
||||
(game_mode & 0xFFFF);
|
||||
}
|
||||
|
||||
constexpr inline bool IsLeaderboardIdSkill(uint32_t id) {
|
||||
return (id & 0x2000000) != 0;
|
||||
}
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PropertyBagEntry {
|
||||
xe::be<uint32_t> contexts_count;
|
||||
xe::be<uint32_t> properties_count;
|
||||
};
|
||||
|
||||
struct PropertyBag {
|
||||
std::set<xe::be<uint32_t>> contexts;
|
||||
std::set<xe::be<uint32_t>> properties;
|
||||
};
|
||||
|
||||
struct TitleHeaderData {
|
||||
xe::be<uint32_t> title_id;
|
||||
xe::be<TitleType> title_type;
|
||||
@@ -64,7 +181,7 @@ static_assert_size(TitleHeaderData, 32);
|
||||
|
||||
struct StatsViewTableEntry {
|
||||
xe::be<uint32_t> id;
|
||||
xe::be<uint32_t> flags;
|
||||
xe::be<uint32_t> flags; // StatsViewFlags
|
||||
xe::be<uint16_t> shared_index;
|
||||
xe::be<uint16_t> string_id;
|
||||
xe::be<uint32_t> unused;
|
||||
@@ -74,10 +191,10 @@ 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<uint32_t> flags; // ViewFieldEntryFlags
|
||||
xe::be<uint16_t> attribute_id;
|
||||
xe::be<uint16_t> string_id;
|
||||
xe::be<uint16_t> aggregation_type;
|
||||
xe::be<uint16_t> aggregation_type; // AggregationType
|
||||
xe::be<uint8_t> ordinal;
|
||||
xe::be<uint8_t> field_type;
|
||||
xe::be<uint32_t> format_type;
|
||||
@@ -94,11 +211,6 @@ struct SharedViewMetaTableEntry {
|
||||
};
|
||||
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;
|
||||
@@ -176,8 +288,11 @@ class SpaInfo : public XdbfFile {
|
||||
return properties_;
|
||||
}
|
||||
|
||||
const std::vector<ViewTable>* GetStatsViews() const { return &stats_views_; }
|
||||
|
||||
const XdbfContextTableEntry* GetContext(uint32_t id);
|
||||
const XdbfPropertyTableEntry* GetProperty(uint32_t id);
|
||||
const std::optional<ViewTable> GetStatsView(uint32_t id);
|
||||
|
||||
uint32_t total_gamerscore() const {
|
||||
return std::accumulate(achievements_.cbegin(), achievements_.cend(), 0,
|
||||
@@ -202,8 +317,9 @@ class SpaInfo : public XdbfFile {
|
||||
std::vector<const AchievementTableEntry*> achievements_;
|
||||
std::vector<const XdbfContextTableEntry*> contexts_;
|
||||
std::vector<const XdbfPropertyTableEntry*> properties_;
|
||||
std::vector<ViewTable> stats_views_;
|
||||
|
||||
typedef std::map<uint16_t, std::string> XdbfLanguageStrings;
|
||||
using XdbfLanguageStrings = std::map<uint16_t, std::string>;
|
||||
|
||||
std::map<XLanguage, XdbfLanguageStrings> language_strings_;
|
||||
|
||||
@@ -215,6 +331,8 @@ class SpaInfo : public XdbfFile {
|
||||
void LoadContexts();
|
||||
void LoadProperties();
|
||||
|
||||
void LoadStatsViews();
|
||||
|
||||
template <typename T>
|
||||
static T GetSpaEntry(std::vector<T>& container, uint32_t id);
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ 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 fourcc_t kXdbfSignatureXpbm = make_fourcc("XPBM");
|
||||
|
||||
constexpr uint64_t kXdbfIdTitle = 0x8000;
|
||||
constexpr uint64_t kXdbfIdXstc = 0x58535443;
|
||||
@@ -44,6 +45,7 @@ constexpr uint64_t kXdbfIdXvc2 = 0x58564332;
|
||||
constexpr uint64_t kXdbfIdXmat = 0x584D4154;
|
||||
constexpr uint64_t kXdbfIdXsrc = 0x58535243;
|
||||
constexpr uint64_t kXdbfIdXthd = 0x58544844;
|
||||
constexpr uint64_t kXdbfIdXpbm = 0x5850424D;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct XdbfHeader {
|
||||
|
||||
Reference in New Issue
Block a user