16 std::unexpected<Error> fail(std::string msg) {
22 void takeRaw(std::span<const std::byte> data, std::size_t& pos,
void* out, std::size_t n) {
23 std::memcpy(out, data.data() + pos, n);
29 std::uint32_t
magic = 0;
30 if (data.size() >=
sizeof magic) std::memcpy(&
magic, data.data(),
sizeof magic);
42 if (data.size() <
sizeof h1)
return fail(
"WDC1: file smaller than its header");
43 std::memcpy(&h1, data.data(),
sizeof h1);
69 std::size_t pos =
sizeof h1;
70 const std::size_t fieldStructBytes = std::size_t{h1.
fieldCount} *
sizeof(WdcFieldStructure);
71 if (data.size() < pos + fieldStructBytes)
return fail(
"WDC1: field structure table overruns the file");
77 sec.header.fileOffset =
static_cast<std::uint32_t
>(pos);
90 "WDC1: sparse offset_map_offset out of range");
92 sec.stringBase =
static_cast<std::uint32_t
>(pos);
96 if (pos + denseCount * 6 > data.size())
return fail(
"WDC1: dense offset map overruns the file");
97 for (std::size_t i = 0; i < denseCount; ++i) {
98 std::uint32_t offset = 0;
99 std::memcpy(&offset, data.data() + pos + i * 6, 4);
100 if (offset == 0)
continue;
101 const std::byte* entry = data.data() + pos + i * 6;
105 pos += denseCount * 6;
108 sec.header.offsetMapIdCount =
static_cast<std::uint32_t
>(img.
ownedOffsetMapIds.size());
112 if (pos + recBytes + h1.
stringTableSize > data.size())
return fail(
"WDC1: records+strings overrun the file");
113 sec.records = data.subspan(pos, recBytes);
114 sec.stringBase =
static_cast<std::uint32_t
>(pos + recBytes);
122 commonDataSize + h1.
relationshipDataSize > data.size())
return fail(
"WDC1: trailing blocks overrun the file");
123 sec.idList = data.subspan(pos, h1.
idListSize);
143 img.
sections.push_back(std::move(sec));
147 Result<WdcImage> WdcImage::_parseWdc3(std::span<const std::byte> data, std::uint32_t magic) {
155 std::size_t pos =
sizeof(std::uint32_t);
156 const std::size_t header_rest =
sizeof(Wdc3Header) -
sizeof(std::uint32_t);
157 const std::size_t prefix =
magic ==
Wdc5Magic ?
sizeof(Wdc5HeaderPrefix) : 0;
158 if (data.size() < pos + prefix + header_rest)
return fail(
"WDC: file smaller than its header");
159 if (prefix) takeRaw(data, pos, &img.wdc5,
sizeof img.wdc5);
160 img.header.magic =
magic;
161 takeRaw(data, pos, &img.header.recordCount, header_rest);
162 const Wdc3Header& h = img.header;
164 const std::size_t sectionsBytes = std::size_t{h.sectionCount} *
sizeof(Wdc3SectionHeader);
165 const std::size_t fieldStructBytes = std::size_t{h.fieldCount} *
sizeof(WdcFieldStructure);
166 if (data.size() < pos + sectionsBytes + fieldStructBytes + h.fieldStorageInfoSize + h.palletDataSize + h.
167 commonDataSize)
return fail(
"WDC: header tables overrun the file");
169 std::vector<Wdc3SectionHeader> sectionHeaders(h.sectionCount);
170 if (h.sectionCount) takeRaw(data, pos, sectionHeaders.data(), sectionsBytes);
172 img.fieldStructure.resize(h.fieldCount);
173 if (h.fieldCount) takeRaw(data, pos, img.fieldStructure.data(), fieldStructBytes);
175 const std::size_t storageCount = h.fieldStorageInfoSize /
sizeof(WdcFieldStorage);
176 img.fieldStorage.resize(storageCount);
177 if (storageCount) takeRaw(data, pos, img.fieldStorage.data(), h.fieldStorageInfoSize);
179 img.palletData = data.subspan(pos, h.palletDataSize);
180 pos += h.palletDataSize;
181 img.commonData = data.subspan(pos, h.commonDataSize);
182 pos += h.commonDataSize;
188 std::vector<std::vector<std::uint32_t>> encryptedIds(h.sectionCount);
190 for (std::uint32_t s = 0; s < h.sectionCount; ++s) {
191 if (sectionHeaders[s].tactKeyHash == 0)
continue;
192 std::uint32_t count = 0;
193 if (pos + 4 > data.size())
return fail(
"WDC: encrypted_status overruns the file");
194 takeRaw(data, pos, &count, 4);
195 if (pos + std::size_t{count} * 4 > data.size())
return fail(
"WDC: encrypted_status id list overruns the file");
196 encryptedIds[s].resize(count);
197 if (count) takeRaw(data, pos, encryptedIds[s].data(), std::size_t{count} * 4);
201 img.sections.reserve(h.sectionCount);
202 for (std::uint32_t s = 0; s < h.sectionCount; ++s) {
203 const Wdc3SectionHeader& sh = sectionHeaders[s];
206 sec.encrypted = sh.tactKeyHash != 0;
207 sec.encryptedIds = std::move(encryptedIds[s]);
209 std::size_t p = sh.fileOffset;
210 if (p > data.size())
return fail(
"WDC: section file_offset past end");
213 if (sh.offsetRecordsEnd < sh.fileOffset || sh.offsetRecordsEnd > data.size())
return fail(
214 "WDC: sparse offset_records_end out of range");
215 sec.records = data.subspan(p, sh.offsetRecordsEnd - p);
216 sec.stringBase = sh.fileOffset;
217 p = sh.offsetRecordsEnd;
220 const std::size_t recBytes = std::size_t{sh.recordCount} * h.recordSize;
221 if (p + recBytes + sh.stringTableSize > data.size())
return fail(
222 "WDC: section records+strings overrun the file");
223 sec.records = data.subspan(p, recBytes);
224 sec.stringBase =
static_cast<std::uint32_t
>(p + recBytes);
225 sec.strings = data.subspan(p + recBytes, sh.stringTableSize);
226 p += recBytes + sh.stringTableSize;
229 auto take = [&](std::size_t n, std::span<const std::byte>& out) ->
bool {
230 if (p + n > data.size())
return false;
231 out = data.subspan(p, n);
241 if (!take(sh.idListSize, sec.idList))
return fail(
"WDC: id_list overruns the file");
242 if (!take(std::size_t{sh.copyTableCount} * 8, sec.copyTable))
return fail(
"WDC: copy_table overruns the file");
243 if (!take(std::size_t{sh.offsetMapIdCount} * 6, sec.offsetMap))
return fail(
244 "WDC: offset_map overruns the file");
245 if (!idsAfterRelationship && !take(std::size_t{sh.offsetMapIdCount} * 4, sec.offsetMapIds))
return fail(
246 "WDC: offset_map id list overruns the file");
247 if (!take(sh.relationshipDataSize, sec.relationship))
return fail(
"WDC: relationship block overruns the file");
248 if (idsAfterRelationship && !take(std::size_t{sh.offsetMapIdCount} * 4, sec.offsetMapIds))
return fail(
249 "WDC: offset_map id list overruns the file");
251 img.sections.push_back(std::move(sec));
257 std::vector<std::uint32_t> offsets(
fieldStorage.size());
258 std::uint32_t pallet = 0, common = 0;
263 pallet +=
fs.additionalDataSize;
267 common +=
fs.additionalDataSize;
274 std::uint32_t element,
275 std::uint32_t arrayCount,
276 std::span<const std::byte> recordBytes,
278 const std::vector<std::uint32_t>& additional)
const {
281 const BitReader reader(recordBytes.data(), recordBytes.size());
282 const std::size_t elemBits =
elemBitWidth(field, arrayCount);
283 switch (
fs.storageType) {
289 return reader.
read(std::size_t{
fs.fieldOffsetBits} + std::size_t{element} * elemBits, elemBits);
291 const std::uint32_t index =
static_cast<std::uint32_t
>(reader.
read(
fs.fieldOffsetBits,
fs.fieldSizeBits));
292 return _palletValue(additional[field], index, 0, 1);
297 const std::uint32_t index =
static_cast<std::uint32_t
>(reader.
read(
fs.fieldOffsetBits,
fs.fieldSizeBits));
298 return _palletValue(additional[field], index, element, arrayCount);
303 return _commonValue(additional[field],
fs.additionalDataSize,
id,
fs.val1);
311 const std::uint32_t count = arrayCount ? arrayCount : 1;
312 switch (
fs.storageType) {
316 return fs.fieldSizeBits / count;
322 std::uint32_t WdcImage::_palletValue(std::uint32_t base,
324 std::uint32_t element,
325 std::uint32_t arraySize)
const {
326 const std::size_t stride = std::size_t{arraySize ? arraySize : 1} * 4;
327 const std::size_t at = base + std::size_t{index} * stride + std::size_t{element} * 4;
334 std::uint32_t WdcImage::_commonValue(std::uint32_t base,
337 std::uint32_t fallback)
const {
338 if (base + size >
commonData.size())
return fallback;
339 const std::byte* block =
commonData.data() + base;
340 const std::size_t count = size / 8;
342 std::size_t lo = 0, hi = count;
344 const std::size_t mid = (lo + hi) / 2;
345 std::uint32_t key = 0;
346 std::memcpy(&key, block + mid * 8, 4);
349 std::memcpy(&v, block + mid * 8 + 4, 4);
352 if (key <
id) lo = mid + 1;
A bounds-guarded little-endian bit reader over a record's byte span.
std::uint64_t read(std::size_t bitOffset, std::size_t bits) const
Read bits (<= 64) starting at absolute bit offset bitOffset.
WdcImage — the structural parser of the WDC family (WDC1/WDC3/WDC4/WDC5) and the per-field value deco...
@ Bitpacked
Bit-addressed unsigned value (signed in WDC1 when the entry's flags word carries bit 0x01).
@ None
Stored inline at a byte offset, fieldSizeBits wide.
@ BitpackedSigned
Bit-addressed signed value, sign-extended (WDC2+ only; WDC1 spells it Bitpacked + flags 0x01).
@ CommonData
Sparse per-id override table; default when an id is absent.
@ PalletArray
Bit-addressed index into palletData (array per slot).
@ Pallet
Bit-addressed index into palletData (one value per slot).
constexpr std::uint32_t Wdc3Magic
The WDC3 magic as memcpy'd off the file front (the bytes "WDC3").
constexpr std::uint32_t Wdc4Magic
The WDC4 magic as memcpy'd off the file front (the bytes "WDC4").
@ WdcFlagSparse
Offset-map layout + inline null-terminated strings.
@ 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").
@ FieldRelative
WDC2+: offset from the referencing field's own position.
@ BlockRelative
WDC1: offset from the string block's first byte.
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.
@ 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.
The 24-byte field_storage_info entry: how one column is stored.
A parsed WDC-family file: the (normalized) header, shared tables, and located sections.
std::span< const std::byte > file
std::size_t elemBitWidth(std::size_t field, std::uint32_t arrayCount) const
The bit width of one element of inline field field: for the inline kinds, the field's total width div...
std::vector< WdcFieldStorage > fieldStorage
std::uint32_t magic
The flavor actually parsed.
std::span< const std::byte > commonData
std::uint64_t fieldRaw(std::size_t field, std::uint32_t element, std::uint32_t arrayCount, std::span< const std::byte > recordBytes, std::uint32_t id, const std::vector< std::uint32_t > &additional) const
Decode array element element of inline field field for one record.
Wdc3Header header
WDC1 header fields are mapped onto this.
std::vector< std::uint32_t > fieldAdditionalOffsets() const
The byte offset into palletData / commonData where each field's additional data begins (accumulated i...
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.
std::vector< WdcFieldStructure > fieldStructure
std::span< const std::byte > palletData
std::vector< WdcSection > sections
std::vector< std::byte > ownedOffsetMap
Owned backing for WDC1's normalized sparse map (filtered entries + the ids the dense map only implied...
std::vector< std::uint32_t > ownedOffsetMapIds