rewrite map data model

This commit is contained in:
Martin Michelsen
2024-12-30 09:20:42 -08:00
parent 69f7bb3db9
commit 72ac20e574
95 changed files with 7596 additions and 5125 deletions
+22 -15
View File
@@ -8,6 +8,7 @@
#include <phosg/Random.hh>
#include <phosg/Time.hh>
#include "../CommonFileFormats.hh"
#include "../Compression.hh"
#include "../Loggers.hh"
#include "../PSOEncryption.hh"
@@ -2452,28 +2453,34 @@ CardIndex::CardIndex(
this->compressed_card_definitions = phosg::load_file(filename);
decompressed_data = prs_decompress(this->compressed_card_definitions);
}
// The client can't handle files larger than this
if (decompressed_data.size() > 0x36EC0) {
throw runtime_error("decompressed card list data is too long");
}
// There's a footer after the card definitions (it's a standard-format REL
// file), but we ignore it
if (decompressed_data.size() % sizeof(CardDefinition) != sizeof(CardDefinitionsFooter)) {
throw runtime_error(phosg::string_printf(
"decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)",
decompressed_data.size(), sizeof(CardDefinition), decompressed_data.size() % sizeof(CardDefinition)));
// The card definitions file is a standard REL file; the root offset points
// to an ArrayRef which specifies an array of CardDefinition structs
phosg::StringReader r(decompressed_data);
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
uint32_t offset = r.pget_u32b(footer.root_offset);
uint32_t count = r.pget_u32b(footer.root_offset + 4);
if (offset > decompressed_data.size() ||
((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
throw runtime_error("definitions array reference out of bounds");
}
auto* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data());
size_t max_cards = decompressed_data.size() / sizeof(CardDefinition);
for (size_t x = 0; x < max_cards; x++) {
CardDefinition* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data() + offset);
for (size_t x = 0; x < count; x++) {
auto& def = defs[x];
// The last card entry has the build date and some other metadata (and
// isn't a real card, obviously), so skip it. The game detects this by
// checking for a negative value in type, which we also do here.
if (static_cast<int8_t>(defs[x].type) < 0) {
if (static_cast<int8_t>(def.type) < 0) {
continue;
}
auto entry = make_shared<CardEntry>(CardEntry{defs[x], "", "", "", {}});
auto entry = make_shared<CardEntry>(CardEntry{def, "", "", "", {}});
if (!this->card_definitions.emplace(entry->def.card_id, entry).second) {
throw runtime_error(phosg::string_printf(
"duplicate card id: %08" PRIX32, entry->def.card_id.load()));
@@ -2493,17 +2500,17 @@ CardIndex::CardIndex(
if (!text_filename.empty() || !decompressed_text_filename.empty()) {
try {
entry->text = std::move(card_text.at(defs[x].card_id));
entry->text = std::move(card_text.at(def.card_id));
} catch (const out_of_range&) {
}
try {
entry->debug_tags = std::move(card_tags.at(defs[x].card_id));
entry->debug_tags = std::move(card_tags.at(def.card_id));
} catch (const out_of_range&) {
}
}
if (!dice_text_filename.empty() || !decompressed_dice_text_filename.empty()) {
try {
auto& dice_text_it = card_dice_text.at(defs[x].card_id);
auto& dice_text_it = card_dice_text.at(def.card_id);
entry->dice_caption = std::move(dice_text_it.first);
entry->dice_text = std::move(dice_text_it.second);
} catch (const out_of_range&) {
@@ -2523,7 +2530,7 @@ CardIndex::CardIndex(
if (this->compressed_card_definitions.size() > 0x7BF8) {
// Try to reduce the compressed size by clearing out text
static_game_data_log.info("Compressed card list data is too long (0x%zX bytes); removing text", this->compressed_card_definitions.size());
for (size_t x = 0; x < max_cards; x++) {
for (size_t x = 0; x < count; x++) {
if (static_cast<int8_t>(defs[x].type) < 0) {
continue;
}
+2 -7
View File
@@ -13,6 +13,7 @@
#include <string>
#include <unordered_map>
#include "../CommonFileFormats.hh"
#include "../PlayerSubordinates.hh"
#include "../Text.hh"
#include "../TextIndex.hh"
@@ -814,18 +815,12 @@ struct CardDefinition {
} __packed_ws__(CardDefinition, 0x128);
struct CardDefinitionsFooter {
// Technically the card definitions file is a REL file, so the last 0x20 bytes
// here should be a separate structure.
/* 00 */ be_uint32_t num_cards1;
/* 04 */ be_uint32_t cards_offset; // == 0
/* 08 */ be_uint32_t num_cards2;
/* 0C */ parray<be_uint32_t, 3> unknown_a2;
/* 18 */ parray<be_uint16_t, 0x10> relocations;
/* 38 */ be_uint32_t relocations_offset;
/* 3C */ be_uint32_t num_relocations;
/* 40 */ parray<be_uint32_t, 2> unused1;
/* 48 */ be_uint32_t footer_offset;
/* 4C */ parray<be_uint32_t, 3> unused2;
/* 38 */ RELFileFooterBE rel_footer;
/* 58 */
} __packed_ws__(CardDefinitionsFooter, 0x58);
+1 -1
View File
@@ -2335,7 +2335,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
if (l) {
// Note: Sega's implementation doesn't set EX results values here; they
// did it at game join time instead. We do it here for code simplicity.
if ((l->base_version != Version::GC_EP3_NTE) && l->ep3_ex_result_values) {
if (!this->options.is_nte() && l->ep3_ex_result_values) {
this->send(*l->ep3_ex_result_values);
}
}