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.
This commit is contained in:
Dave Johnson committed 2026-10-16 20:58:02 +00:00
1 parent f22adf2348
commit db0d1f348c
3 files changed
+28 -4

No files matched your search

+18 -2
View File
@@ -1,6 +1,7 @@
#include "ebx_document.h"
#include <algorithm>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <unordered_map>
@@ -356,7 +357,9 @@ void map_instances(Document& document,
class ValueReader final {
public:
ValueReader(const std::span<const std::byte> 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::Array>(&value.data);
if (values && !values->empty()) {
std::int32_t displacement{};
std::memcpy(&displacement, bytes_.data() + position, sizeof(displacement));
const auto target = static_cast<std::int64_t>(position) + displacement -
static_cast<std::int64_t>(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<const std::byte> bytes_;
std::unordered_map<std::int64_t, std::uint32_t> arrayHashes_; // record offset -> its hash
BinaryReader reader_;
Document& document_;
unsigned depth_{};
+4
View File
@@ -6,6 +6,7 @@
#include <cstdint>
#include <span>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>
@@ -149,6 +150,9 @@ struct Value {
PointerReference, TypeReference, BoxedReference,
std::shared_ptr<struct Object>, 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<std::uint32_t> arrayHash;
};
struct FieldValue {
+6 -2
View File
@@ -6,6 +6,7 @@
#include <cmath>
#include <cstring>
#include <limits>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string_view>
@@ -106,6 +107,7 @@ private:
std::uint32_t slot{};
const Value::Array* values{};
std::size_t descriptor{};
std::optional<std::uint32_t> hash;
bool emitted{};
};
@@ -208,7 +210,7 @@ private:
static const Value::Array empty;
const auto* array = value ? std::get_if<Value::Array>(&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<std::size_t>(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<std::uint16_t>(field.encodedType << 1),
hash, static_cast<std::uint16_t>(field.encodedType << 1),
field.type() == FieldType::structure
? fixup_slot_for_descriptor(field.classRef)
: static_cast<std::uint16_t>(0xFFFF)});