From db0d1f348c3db6c69bb385979742a2e5cea9f649 Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Fri, 16 Oct 2026 17:46:37 +0000 Subject: [PATCH] EBX writer: rewritten arrays keep their own record hash Each array record carries a hash the original files make specific to the field or element type; the writer used to give every array the hash of the first record of the same element type, so a rewrite changed hundreds of them in bam's level root. Arrays read from a document now keep their record's hash; only arrays with no source record borrow as before. --- Engine/Resource/ebx_document.cpp | 20 ++++++++++++++++++-- Engine/Resource/ebx_document.h | 4 ++++ Engine/Resource/ebx_writer.cpp | 8 ++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Engine/Resource/ebx_document.cpp b/Engine/Resource/ebx_document.cpp index efadfaa..a81ef1d 100644 --- a/Engine/Resource/ebx_document.cpp +++ b/Engine/Resource/ebx_document.cpp @@ -1,6 +1,7 @@ #include "ebx_document.h" #include +#include #include #include #include @@ -356,7 +357,9 @@ void map_instances(Document& document, class ValueReader final { public: ValueReader(const std::span bytes, Document& document) - : bytes_(bytes), reader_(bytes), document_(document) {} + : bytes_(bytes), reader_(bytes), document_(document) { + for (const auto& record : document.arrays) arrayHashes_.emplace(record.offset, record.hash); + } void read_instances() { for (auto& instance : document_.instances) { @@ -423,7 +426,19 @@ private: } const auto position = start + field.dataOffset; if (position > document_.dataEnd) throw std::runtime_error("EBX field offset exceeds EBXD"); - output.fields.push_back({descriptorIndex, field.name, read_field(field, position)}); + auto value = read_field(field, position); + if (field.category() == FieldCategory::array) { + const auto* values = std::get_if(&value.data); + if (values && !values->empty()) { + std::int32_t displacement{}; + std::memcpy(&displacement, bytes_.data() + position, sizeof(displacement)); + const auto target = static_cast(position) + displacement - + static_cast(document_.dataStart); + if (const auto found = arrayHashes_.find(target); found != arrayHashes_.end()) + value.arrayHash = found->second; + } + } + output.fields.push_back({descriptorIndex, field.name, std::move(value)}); } } @@ -552,6 +567,7 @@ private: } std::span bytes_; + std::unordered_map arrayHashes_; // record offset -> its hash BinaryReader reader_; Document& document_; unsigned depth_{}; diff --git a/Engine/Resource/ebx_document.h b/Engine/Resource/ebx_document.h index ce63e81..947558d 100644 --- a/Engine/Resource/ebx_document.h +++ b/Engine/Resource/ebx_document.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,9 @@ struct Value { PointerReference, TypeReference, BoxedReference, std::shared_ptr, Array>; Storage data; + // An array's own record hash, kept when the document is written again; a new array has none + // and borrows one (ebx_writer.cpp, array_hash). + std::optional arrayHash; }; struct FieldValue { diff --git a/Engine/Resource/ebx_writer.cpp b/Engine/Resource/ebx_writer.cpp index 1924d66..23b4ea7 100644 --- a/Engine/Resource/ebx_writer.cpp +++ b/Engine/Resource/ebx_writer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,7 @@ private: std::uint32_t slot{}; const Value::Array* values{}; std::size_t descriptor{}; + std::optional hash; bool emitted{}; }; @@ -208,7 +210,7 @@ private: static const Value::Array empty; const auto* array = value ? std::get_if(&value->data) : nullptr; arraysPending_.push_back({checked_u32(position, "EBX array slot"), array ? array : &empty, - descriptorIndex}); + descriptorIndex, value ? value->arrayHash : std::nullopt}); } else if (field.type() == FieldType::structure) { if (field.classRef >= document_.types.size()) throw std::runtime_error("EBX struct type is invalid"); const auto nestedStart = align_up(position, std::max(document_.types[field.classRef].alignment, 1)); @@ -505,8 +507,10 @@ private: write_scalar(field, elementStart, &value); } } + // An array read from a record keeps that record's hash; a new one borrows (array_hash). + const auto hash = arraysPending_[index].hash ? *arraysPending_[index].hash : array_hash(field); arrays_.push_back({checked_u32(data, "EBX array data"), checked_u32(values->size(), "EBX array count"), - array_hash(field), static_cast(field.encodedType << 1), + hash, static_cast(field.encodedType << 1), field.type() == FieldType::structure ? fixup_slot_for_descriptor(field.classRef) : static_cast(0xFFFF)});