27 std::string readCString(std::span<const std::byte> file, std::size_t at, std::size_t limit) {
28 limit = std::min(limit, file.size());
29 if (at >= limit)
return {};
30 const auto* bytes =
reinterpret_cast<const char*
>(file.data());
32 while (end < limit && bytes[end] !=
'\0') ++end;
33 return std::string{bytes + at, end - at};
42 std::uint64_t signedFit(std::uint64_t raw, std::size_t bits,
bool isSigned) {
43 if (!isSigned || bits == 0 || bits >= 64)
return raw;
44 const std::uint64_t signBit = std::uint64_t{1} << (bits - 1);
45 if (raw & signBit)
return raw | ~((std::uint64_t{1} << bits) - 1);
61 Decoder(
const WdcImage& img,
const TableInfo& info, RecordSink& sink)
62 : _img{img}, _info{info}, _sink{sink}, _additional{img.fieldAdditionalOffsets()} {
65 std::size_t colidx = 0;
66 for (
const Column& col : info.schema) {
67 if (col.isRelation && col.noninline) _relationCol = colidx;
78 _recordsBefore.reserve(img.sections.size());
79 std::uint64_t recSum = 0;
80 for (
const WdcSection& sec : img.sections) {
81 _recordsBefore.push_back(recSum);
82 recSum += sec.records.size();
84 _totalRecordBytes = recSum;
91 for (
const WdcSection& sec : _img.sections) {
97 reportEncrypted(sec, state);
100 if (
auto r = _decodeSection(sec); !r)
return r;
111 void reportEncrypted(
const WdcSection& sec, TableState& state)
const {
112 EncryptedSection report{.keyHash = sec.header.tactKeyHash, .recordCount = sec.header.recordCount};
113 if (!sec.encryptedIds.empty()) report.ids = sec.encryptedIds;
115 const auto ids = std::span{
116 reinterpret_cast<const std::uint32_t*
>(sec.idList.data()),
117 sec.idList.size() / 4
119 report.ids.assign(ids.begin(), ids.end());
121 state.encrypted.push_back(std::move(report));
129 const std::size_t first = _sink.size();
130 if (_img.isSparse()) {
131 if (
auto r = _decodeSparseSection(sec); !r)
return r;
134 const std::size_t stride = _img.header.recordSize;
135 const std::size_t sectionIndex = _indexOf(sec);
136 for (std::uint32_t r = 0; r < sec.header.recordCount; ++r) {
137 const std::uint32_t
id = _recordId(sec, r);
138 const auto recordBytes = sec.records.subspan(std::size_t{r} * stride, stride);
139 const std::uint64_t recordBlob = _recordsBefore[sectionIndex] + std::uint64_t{r} * stride;
140 const std::size_t rec = _sink.add();
141 _decodeRecord(rec, recordBytes, recordBlob,
id);
144 applyRelationship(sec, first);
156 Result<void> _decodeSparseSection(
const WdcSection& sec) {
157 for (std::uint32_t r = 0; r < sec.header.offsetMapIdCount; ++r) {
158 std::uint32_t offset = 0,
id = 0;
159 std::uint16_t size = 0;
160 std::memcpy(&offset, sec.offsetMap.data() + std::size_t{r} * 6, 4);
161 std::memcpy(&size, sec.offsetMap.data() + std::size_t{r} * 6 + 4, 2);
162 if (std::size_t{r} * 4 + 4 <= sec.offsetMapIds.size()) std::memcpy(
163 &
id, sec.offsetMapIds.data() + std::size_t{r} * 4, 4);
164 if (offset + size > _img.file.size())
166 std::format(
"{}: sparse record {} at {:#x} overruns the file", _info.name, r, offset));
167 const std::size_t rec = _sink.add();
168 decodeSparseRecord(rec, _img.file.subspan(offset, size),
id);
179 std::uint32_t _recordId(
const WdcSection& sec, std::uint32_t r)
const {
180 if (_img.idIsNoninline()) {
181 if (std::size_t{r} * 4 + 4 <= sec.idList.size()) {
182 std::uint32_t
id = 0;
183 std::memcpy(&
id, sec.idList.data() + std::size_t{r} * 4, 4);
188 const auto recordBytes = sec.records.
189 subspan(std::size_t{r} * _img.header.recordSize, _img.header.recordSize);
190 return static_cast<std::uint32_t
>(_img.fieldRaw(_img.header.idIndex, 0, 1, recordBytes, 0, _additional));
202 void _decodeRecord(std::size_t rec,
203 std::span<const std::byte> recordBytes,
204 std::uint64_t recordBlob,
206 std::size_t f = 0, colidx = 0;
207 for (
const Column& col : _info.schema) {
209 if (col.isId) _sink.setInt(rec, colidx, 0,
static_cast<std::int64_t
>(
id));
212 for (std::uint16_t e = 0; e < col.arrayLen; ++e)
213 decodeInlineElement(rec, f, col, e, colidx, recordBytes, recordBlob,
id);
230 void decodeInlineElement(std::size_t rec,
235 std::span<const std::byte> recordBytes,
236 std::uint64_t recordBlob,
238 const std::uint64_t raw = _img.fieldRaw(f, e, col.arrayLen, recordBytes,
id, _additional);
241 const std::uint64_t v = signedFit(raw, _img.elemBitWidth(f, col.arrayLen),
242 _img.fieldIsSigned(f) || col.isSigned);
243 _sink.setInt(rec, colidx, e,
static_cast<std::int64_t
>(v));
247 _sink.setFloat(rec, colidx, e, std::bit_cast<float>(
static_cast<std::uint32_t
>(raw)));
252 const std::size_t elemByte = std::size_t{_img.fieldStorage[f].fieldOffsetBits} / 8 + std::size_t{e} * 4;
253 _sink.setString(rec, colidx, e, _resolveString(recordBlob + elemByte,
static_cast<std::uint32_t
>(raw)));
274 std::string _resolveString(std::uint64_t refBlob, std::uint32_t raw)
const {
276 const WdcSection& sec = _img.sections.front();
277 return readCString(_img.file, std::size_t{sec.stringBase} + raw,
278 std::size_t{sec.stringBase} + sec.strings.size());
283 const std::int64_t target =
static_cast<std::int64_t
>(refBlob) +
static_cast<std::int32_t
>(raw);
284 if (target <
static_cast<std::int64_t
>(_totalRecordBytes))
return {};
285 std::uint64_t t =
static_cast<std::uint64_t
>(target) - _totalRecordBytes;
286 for (std::size_t s = 0; s < _img.sections.size(); ++s) {
287 const WdcSection& sec = _img.sections[s];
288 if (t < sec.strings.size())
289 return readCString(_img.file, std::size_t{sec.stringBase} + t,
290 std::size_t{sec.stringBase} + sec.strings.size());
291 t -= sec.strings.size();
302 void decodeSparseRecord(std::size_t rec, std::span<const std::byte> bytes, std::uint32_t
id) {
303 std::size_t cursor = 0, colidx = 0;
304 for (
const Column& col : _info.schema) {
306 if (col.isId) _sink.setInt(rec, colidx, 0,
static_cast<std::int64_t
>(
id));
310 for (std::uint16_t e = 0; e < col.arrayLen; ++e) {
313 _sink.setInt(rec, colidx, e,
db::detail::readInt(bytes, cursor, col.bits / 8u, col.isSigned));
317 if (cursor + 4 <= bytes.size()) std::memcpy(&v, bytes.data() + cursor, 4);
319 _sink.setFloat(rec, colidx, e, std::bit_cast<float>(v));
323 std::string s = readCString(bytes, cursor, bytes.size());
324 cursor += s.size() + 1;
325 _sink.setString(rec, colidx, e, s);
342 void applyRelationship(
const WdcSection& sec, std::size_t first) {
343 if (_relationCol == std::numeric_limits<std::size_t>::max() || sec.relationship.size() < 12)
return;
344 std::uint32_t num = 0;
345 std::memcpy(&num, sec.relationship.data(), 4);
346 const bool byId = _img.magic !=
Wdc1Magic && _img.magic !=
Wdc3Magic && (_img.header.flags &
348 for (std::uint32_t e = 0; e < num; ++e) {
349 const std::size_t at = 12 + std::size_t{e} * 8;
350 if (at + 8 > sec.relationship.size())
break;
351 std::uint32_t foreign = 0, key = 0;
352 std::memcpy(&foreign, sec.relationship.data() + at, 4);
353 std::memcpy(&key, sec.relationship.data() + at + 4, 4);
354 const std::size_t rec = byId ? _sink.findById(key) : first + key;
355 if (rec < _sink.size())
356 _sink.setInt(rec, _relationCol, 0,
static_cast<std::int64_t
>(foreign));
363 void expandCopies(
const WdcSection& sec) {
364 for (std::size_t c = 0; c + 8 <= sec.copyTable.size(); c += 8) {
365 std::uint32_t newId = 0, srcId = 0;
366 std::memcpy(&newId, sec.copyTable.data() + c, 4);
367 std::memcpy(&srcId, sec.copyTable.data() + c + 4, 4);
368 if (
const std::size_t src = _sink.findById(srcId); src != _sink.size()) _sink.cloneWithId(src, newId);
376 std::size_t _indexOf(
const WdcSection& sec)
const {
377 return static_cast<std::size_t
>(&sec - _img.sections.data());
380 const WdcImage& _img;
381 const TableInfo& _info;
383 std::vector<std::uint32_t> _additional;
384 std::size_t _relationCol = std::numeric_limits<std::size_t>::max();
385 std::vector<std::uint64_t> _recordsBefore;
386 std::uint64_t _totalRecordBytes = 0;
396 if (!parsed)
return std::unexpected{parsed.error()};
403 "{}: the file stores {} inline fields but the generated schema has {} "
414 for (std::size_t f = 0; f < inlineColumns && f < img.
fieldStorage.size(); ++f)
417 const auto* pb =
reinterpret_cast<const std::byte*
>(&img.
wdc5);
421 if (
auto r = Decoder{img, info, sink}.run(state); !r)
return r;
The decode target: the codecs build the record vector through this, one field at a time.
Named ClientVersion constants for the exact client builds format features appeared (or vanished) at —...
Shared codec internals for the fixed-stride formats (WDBC, WDB2): the string pool that reproduces byt...
constexpr ClientVersion Cata
Cataclysm (any 4.x client).
std::int64_t readInt(std::span< const std::byte > image, std::size_t &pos, std::size_t bytes, bool isSigned)
Read a little-endian integer of bytes bytes at pos, sign-extended when isSigned, advancing pos.
bool allZero(std::span< const std::byte > bytes)
Whether every byte of bytes is zero (an empty span counts as zero).
std::size_t inlineColumnCount(std::span< const Column > schema)
The number of INLINE columns (everything not $noninline$).
@ None
Stored inline at a byte offset, fieldSizeBits wide.
constexpr std::uint32_t Wdc3Magic
The WDC3 magic as memcpy'd off the file front (the bytes "WDC3").
Result< void > readWdc(const TableInfo &info, std::span< const std::byte > data, RecordSink &sink, TableState &state)
Decode a WDC-family image onto the schema: every unencrypted section's records (inline fields via the...
@ WdcFlagSecondary
Secondary-key data; in WDC4+ also reorders the sparse id list ahead of the relationship block.
constexpr std::uint32_t Wdc1Magic
The WDC1 magic as memcpy'd off the file front (the bytes "WDC1").
constexpr std::uint32_t Wdc5Magic
The WDC5 magic as memcpy'd off the file front (the bytes "WDC5").
@ BlockRelative
WDC1: offset from the string block's first byte.
@ Int
An integer column; Column::bits / Column::isSigned give the shape.
@ Float
A 32-bit IEEE float column.
@ String
A string-block reference column (u32 offset on disk).
@ LocString
A pre-Cataclysm localized string column (see LocString).
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
@ SchemaMismatch
A client-database record layout disagrees with the generated WoWDBDefs schema.
@ TableMagicUnknown
A client-database magic wowlib does not support for the requested version.
@ TableTruncated
A client-database header, record block or satellite block overruns the file.
Schema reflection over generated client-database record structs: the column list, record stride and f...
std::uint16_t major
Expansion number, e.g.
std::uint16_t minor
Minor version within the expansion.
The per-table identity a codec needs beyond the record data: the client version (format selection,...
ClientVersion version
The record's client version.
std::span< const Column > schema
The record schema (schemaOf<Record>()).
std::string_view name
The WoWDBDefs table name.
The preserved decode state a table carries between read() and write() so a WDBC/WDB2 write is byte-pe...
std::vector< std::uint8_t > wdcKinds
Original per-inline-column compression (WdcCompression).
void reset()
Reset the per-read state before decoding a fresh image.
std::vector< std::byte > wdcOriginal
Raw image kept when the file has encrypted sections.
std::uint32_t wdcTableHash
Preserved WDC3 header tableHash.
std::uint32_t wdcLocale
Preserved WDC3 header locale.
std::vector< EncryptedSection > encrypted
Encrypted WDC sections skipped on read.
std::uint32_t sourceMagic
The magic read() sniffed; 0 for a fresh table.
std::uint32_t wdcLayoutHash
Preserved WDC3 header layoutHash.
std::vector< std::byte > wdc5Prefix
Preserved WDC5 {version, schema string} header prefix.
A parsed WDC-family file: the (normalized) header, shared tables, and located sections.
std::vector< WdcFieldStorage > fieldStorage
std::uint32_t magic
The flavor actually parsed.
Wdc5HeaderPrefix wdc5
Valid when magic == Wdc5Magic.
Wdc3Header header
WDC1 header fields are mapped onto this.
static Result< WdcImage > parse(std::span< const std::byte > data)
Parse and structurally validate data as a WDC-family file, sniffing the flavor off the magic.
The WDC-family codec: the non-templated entry points Table<Record> dispatches to for every column-com...