wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
write.cpp
Go to the documentation of this file.
1
14
15#include <wowlib/db/wdc/wdc.hpp>
16
17#include <algorithm>
18#include <cstring>
19#include <format>
20#include <limits>
21#include <string>
22#include <unordered_map>
23#include <utility>
24#include <vector>
25
27#include <wowlib/db/schema.hpp>
29
30namespace wowlib::db::wdc {
31 namespace {
35 std::uint16_t unsignedWidth(std::uint64_t maxv) {
36 std::uint16_t w = 0;
37 while (maxv) {
38 ++w;
39 maxv >>= 1;
40 }
41 return w ? w : std::uint16_t{1};
42 }
43
48 std::uint16_t signedWidth(std::int64_t minv, std::int64_t maxv) {
49 const auto need = [](std::int64_t v) -> std::uint16_t {
50 std::uint16_t w = 1;
51 while (w < 64 && (v < -(std::int64_t{1} << (w - 1)) || v > (std::int64_t{1} << (w - 1)) - 1)) ++w;
52 return w;
53 };
54 return std::max<std::uint16_t>({std::uint16_t{1}, need(minv), need(maxv)});
55 }
56
58 struct FieldPlan {
59 std::uint32_t offsetBits = 0;
60 std::uint16_t elemBits = 32;
61 std::uint16_t elements = 1;
63 std::vector<std::uint32_t> pallet;
64 std::unordered_map<std::string, std::uint32_t> palletIndex;
65 std::uint32_t commonDefault = 0;
66
68 bool isPallet() const {
69 return storage == WdcCompression::Pallet || storage == WdcCompression::PalletArray;
70 }
71
73 bool isCommon() const { return storage == WdcCompression::CommonData; }
75 std::size_t recordBits() const {
76 if (isCommon()) return 0;
77 // A pallet array stores ONE index for the whole array; inline kinds
78 // store every element side by side.
79 return isPallet() ? elemBits : std::size_t{elemBits} * elements;
80 }
81 };
82
88 class Writer {
89 public:
95 Writer(std::uint32_t magic, const TableInfo& info, const RecordSource& source, const TableState& state)
96 : _magic{magic}, _info{info}, _source{source}, _state{state} {
97 std::size_t colidx = 0;
98 for (const Column& c : info.schema) {
99 if (!c.noninline) _cols.emplace_back(colidx, c);
100 else if (c.isRelation) _relationCol = colidx;
101 ++colidx;
102 }
103 }
104
107 Result<FileBuffer> emit() {
108 _planFields();
109 _layoutRecord();
110 _deriveCopies();
111 _deriveCommon();
112 _encodeRecords();
113 if (auto w = _block.write(_stringRegion); !w) return std::unexpected{w.error()};
114 _scanIdRange();
115 return _magic == Wdc1Magic ? _emitWdc1() : _emitWdc3();
116 }
117
118 private:
127 void _planFields() {
128 // Per-column value ranges over every record (copies included — they
129 // must round-trip through the same width).
130 std::vector<std::int64_t> lo(_cols.size(), 0), hi(_cols.size(), 0);
131 for (std::size_t r = 0; r < _source.size(); ++r)
132 for (std::size_t f = 0; f < _cols.size(); ++f)
133 if (_cols[f].second.type == ColumnType::Int)
134 for (std::uint16_t e = 0; e < _cols[f].second.arrayLen; ++e) {
135 const std::int64_t v = _source.getInt(r, _cols[f].first, e);
136 lo[f] = std::min(lo[f], v);
137 hi[f] = std::max(hi[f], v);
138 }
139
140 _plan.resize(_cols.size());
141 for (std::size_t f = 0; f < _cols.size(); ++f) {
142 const Column& col = _cols[f].second;
143 FieldPlan& p = _plan[f];
144 p.elements = col.arrayLen;
145 const auto kind = f < _state.wdcKinds.size()
146 ? static_cast<WdcCompression>(_state.wdcKinds[f])
148 const bool origPallet = kind == WdcCompression::Pallet || kind == WdcCompression::PalletArray;
149 const bool origCommon = kind == WdcCompression::CommonData;
150 const bool palletable = col.type == ColumnType::Int || col.type == ColumnType::Float;
151 if (palletable && col.arrayLen == 1 && origCommon) p.storage = WdcCompression::CommonData;
152 else if (palletable && origPallet)
153 p.storage = col.arrayLen > 1 ? WdcCompression::PalletArray : WdcCompression::Pallet;
154 else if (col.type == ColumnType::Int)
156 else p.storage = WdcCompression::None;
157
158 if (p.isPallet()) p.elemBits = 1; // real width set below, from the distinct count
159 else if (p.isCommon()) p.elemBits = 0;
160 else if (col.type == ColumnType::Int)
161 p.elemBits = std::min<std::uint16_t>(
162 col.isSigned ? signedWidth(lo[f], hi[f]) : unsignedWidth(static_cast<std::uint64_t>(hi[f])), col.bits);
163 else p.elemBits = 32;
164 }
165
166 // Build the pallets from the actual values, then size each index to
167 // the distinct-value count.
168 for (std::size_t r = 0; r < _source.size(); ++r)
169 for (std::size_t f = 0; f < _cols.size(); ++f)
170 if (_plan[f].isPallet()) {
171 std::string key = _palletKey(r, f);
172 if (_plan[f].palletIndex.try_emplace(key, static_cast<std::uint32_t>(_plan[f].palletIndex.size())).
173 second)
174 for (std::uint16_t e = 0; e < _plan[f].elements; ++e) {
175 std::uint32_t slot = 0;
176 std::memcpy(&slot, key.data() + std::size_t{e} * 4, 4);
177 _plan[f].pallet.push_back(slot);
178 }
179 }
180 for (FieldPlan& p : _plan)
181 if (p.isPallet()) {
182 // Implicit widening, not a cast: size_t IS uint64_t on LLP64, so
183 // a static_cast here is -Wuseless-cast on Windows and required
184 // nowhere.
185 const std::uint64_t palletCount = p.palletIndex.size();
186 p.elemBits = unsignedWidth(palletCount);
187 }
188 }
189
193 void _layoutRecord() {
194 std::size_t recordBits = 0;
195 for (FieldPlan& p : _plan) {
196 if (p.storage == WdcCompression::None) recordBits = (recordBits + 7) / 8 * 8;
197 p.offsetBits = static_cast<std::uint32_t>(recordBits);
198 recordBits += p.recordBits();
199 }
200 _recordSize = static_cast<std::uint32_t>((recordBits + 7) / 8);
201 // The header's bitpackedDataOffset points at the first bit-
202 // addressed field's byte (Blizzard uses it for its own tooling; kept
203 // spec-faithful).
204 _bitpackedAt = _recordSize;
205 for (const FieldPlan& p : _plan)
206 if (p.storage != WdcCompression::None && !p.isCommon()) {
207 _bitpackedAt = p.offsetBits / 8;
208 break;
209 }
210 }
211
217 void _deriveCopies() {
218 const std::uint32_t recordCount = static_cast<std::uint32_t>(_source.size());
219 if (_recordSize >= 8) {
220 std::unordered_map<std::string, std::uint32_t> first;
221 _reals.reserve(recordCount);
222 for (std::uint32_t r = 0; r < recordCount; ++r) {
223 std::string key = _valueKey(r);
224 const std::uint32_t id = _source.idOf(r);
225 if (const auto it = first.find(key); it != first.end()) _copies.emplace_back(id, it->second);
226 else {
227 first.emplace(std::move(key), id);
228 _reals.push_back(r);
229 }
230 }
231 }
232 else {
233 _reals.resize(recordCount);
234 for (std::uint32_t r = 0; r < recordCount; ++r) _reals[r] = r;
235 }
236 }
237
242 void _deriveCommon() {
243 _common.resize(_plan.size());
244 std::vector<std::vector<std::pair<std::uint32_t, std::uint32_t>>> raw(_plan.size());
245 for (const std::uint32_t ri : _reals) {
246 const std::uint32_t id = _source.idOf(ri);
247 for (std::size_t f = 0; f < _cols.size(); ++f)
248 if (_plan[f].isCommon()) raw[f].emplace_back(id, _source.getSlot(ri, _cols[f].first, 0));
249 }
250 for (std::size_t f = 0; f < _cols.size(); ++f) {
251 if (!_plan[f].isCommon()) continue;
252 std::unordered_map<std::uint32_t, std::uint32_t> hist;
253 for (const auto& [id, v] : raw[f]) ++hist[v];
254 std::uint32_t def = 0, best = 0;
255 for (const auto& [v, c] : hist)
256 if (c > best) {
257 best = c;
258 def = v;
259 }
260 _plan[f].commonDefault = def;
261 for (const auto& [id, v] : raw[f])
262 if (v != def) _common[f].emplace_back(id, v);
263 std::ranges::sort(_common[f]);
264 }
265 for (const auto& entries : _common) _commonTotal += static_cast<std::uint32_t>(entries.size() * 8);
266 for (const FieldPlan& p : _plan)
267 if (p.isPallet()) _palletTotal += static_cast<std::uint32_t>(p.pallet.size() * 4);
268 }
269
274 void _encodeRecords() {
275 const std::uint32_t realCount = static_cast<std::uint32_t>(_reals.size());
276 _recordsAt = _frameBytesBeforeRecords();
277 _stringsAt = _recordsAt + std::size_t{realCount} * _recordSize;
278
279 std::ignore = _block.add("");
280 // Blizzard string blocks lead with a zero byte
281 _lookup.emplace("", 0);
282
283 _recordRegion.assign(std::size_t{realCount} * _recordSize, std::byte{0});
284 _ids.resize(realCount);
285 for (std::uint32_t i = 0; i < realCount; ++i) {
286 const std::size_t base = std::size_t{i} * _recordSize;
287 BitWriter writer(_recordRegion.data() + base, _recordSize);
288 _encodeRecord(_reals[i], writer, _recordsAt + base);
289 _ids[i] = _source.idOf(_reals[i]);
290 }
291 }
292
298 void _encodeRecord(std::size_t r, BitWriter& writer, std::size_t recordAbs) {
299 for (std::size_t f = 0; f < _cols.size(); ++f) {
300 const std::size_t colidx = _cols[f].first;
301 const Column& col = _cols[f].second;
302 const FieldPlan& p = _plan[f];
303 if (p.isCommon()) continue; // the value lives in commonData only
304 if (p.isPallet()) {
305 const std::string key = _palletKey(r, f);
306 const auto it = p.palletIndex.find(key);
307 writer.write(p.offsetBits, p.elemBits, it != p.palletIndex.end() ? it->second : 0);
308 continue;
309 }
310 for (std::uint16_t e = 0; e < col.arrayLen; ++e) {
311 const std::size_t bitOff = p.offsetBits + std::size_t{e} * p.elemBits;
312 if (col.type == ColumnType::String) {
313 const std::string value{_source.getString(r, colidx, e)};
314 std::uint32_t blockOff = 0;
315 if (const auto it = _lookup.find(value); it != _lookup.end()) blockOff = it->second;
316 else {
317 blockOff = _block.add(value);
318 _lookup.emplace(value, blockOff);
319 }
320 if (_magic == Wdc1Magic)
321 // WDC1 string references are plain offsets into the block.
322 writer.write(bitOff, 32, blockOff);
323 else {
324 // WDC2+ references are relative to the referencing field's
325 // own position; with the single section this writer emits,
326 // blob and file layout coincide.
327 const std::size_t fieldAbs = recordAbs + bitOff / 8;
328 writer.write(bitOff, 32, static_cast<std::uint32_t>(_stringsAt + blockOff - fieldAbs));
329 }
330 }
331 else if (col.type == ColumnType::Float) writer.write(bitOff, 32, _source.getSlot(r, colidx, e));
332 else // Int: bitpacked (signed values keep their low bits)
333 {
334 const std::uint64_t mask = p.elemBits >= 64 ? ~std::uint64_t{0} : (std::uint64_t{1} << p.elemBits) - 1;
335 writer.write(bitOff, p.elemBits, static_cast<std::uint64_t>(_source.getInt(r, colidx, e)) & mask);
336 }
337 }
338 }
339 }
340
346 std::string _valueKey(std::size_t r) const {
347 std::string key;
348 std::size_t colidx = 0;
349 for (const Column& col : _info.schema) {
350 if (!col.isId) {
351 if (col.type == ColumnType::String)
352 for (std::uint16_t e = 0; e < col.arrayLen; ++e) {
353 const std::string_view v = _source.getString(r, colidx, e);
354 const std::uint32_t n = static_cast<std::uint32_t>(v.size());
355 key.append(reinterpret_cast<const char*>(&n), 4);
356 key.append(v);
357 }
358 else if (col.type == ColumnType::Float)
359 for (std::uint16_t e = 0; e < col.arrayLen; ++e) {
360 const std::uint32_t s = _source.getSlot(r, colidx, e);
361 key.append(reinterpret_cast<const char*>(&s), 4);
362 }
363 else if (col.type == ColumnType::Int)
364 for (std::uint16_t e = 0; e < col.arrayLen; ++e) {
365 const std::int64_t v = _source.getInt(r, colidx, e);
366 key.append(reinterpret_cast<const char*>(&v), 8);
367 }
368 }
369 ++colidx;
370 }
371 return key;
372 }
373
379 std::string _palletKey(std::size_t r, std::size_t f) const {
380 const std::uint16_t elements = _plan[f].elements;
381 std::string key(std::size_t{elements} * 4, '\0');
382 for (std::uint16_t e = 0; e < elements; ++e) {
383 const std::uint32_t slot = _source.getSlot(r, _cols[f].first, e);
384 std::memcpy(key.data() + std::size_t{e} * 4, &slot, 4);
385 }
386 return key;
387 }
388
391 void _scanIdRange() {
392 _minId = _source.size() ? _source.idOf(0) : 0;
393 _maxId = _minId;
394 for (std::size_t r = 0; r < _source.size(); ++r) {
395 const std::uint32_t id = _source.idOf(r);
396 _minId = std::min(_minId, id);
397 _maxId = std::max(_maxId, id);
398 }
399 }
400
404 std::size_t _frameBytesBeforeRecords() const {
405 const std::size_t fieldCount = _plan.size();
406 if (_magic == Wdc1Magic)
407 // WDC1: header, field structures, records ... (storage info,
408 // pallet and common data come AFTER the records).
409 return sizeof(Wdc1Header) + fieldCount * sizeof(WdcFieldStructure);
410 const std::size_t prefix = _magic == Wdc5Magic ? sizeof(Wdc5HeaderPrefix) : 0;
411 return prefix + sizeof(Wdc3Header) + sizeof(Wdc3SectionHeader) + fieldCount * sizeof(WdcFieldStructure) +
412 fieldCount * sizeof(WdcFieldStorage) + _palletTotal + _commonTotal;
413 }
414
416 bool _noninlineId() const {
417 return db::detail::idIsNoninline(_info.schema);
418 }
419
422 std::size_t _relationshipBytes() const {
423 return _relationCol == std::numeric_limits<std::size_t>::max() ? 0 : 12 + _reals.size() * 8;
424 }
425
427 static void _append(FileBuffer& out, const void* p, std::size_t n) {
428 const auto* b = static_cast<const std::byte*>(p);
429 out.insert(out.end(), b, b + n);
430 }
431
435 void _appendFieldStructure(FileBuffer& out) const {
436 for (const FieldPlan& p : _plan) {
437 const std::int16_t size = p.isPallet() || p.storage == WdcCompression::None
438 ? std::int16_t{0}
439 : static_cast<std::int16_t>(32 - p.elemBits);
440 WdcFieldStructure fstruct{size, static_cast<std::uint16_t>(p.offsetBits / 8)};
441 _append(out, &fstruct, sizeof fstruct);
442 }
443 }
444
449 void _appendFieldStorage(FileBuffer& out) const {
450 std::size_t cf = 0;
451 for (const FieldPlan& p : _plan) {
452 WdcFieldStorage fs;
453 fs.fieldOffsetBits = static_cast<std::uint16_t>(p.offsetBits);
454 fs.fieldSizeBits = static_cast<std::uint16_t>(p.isPallet()
455 ? p.elemBits
456 : (p.isCommon()
457 ? 0
458 : std::size_t{p.elemBits} * p.elements));
459 fs.storageType = p.storage;
460 if (p.storage == WdcCompression::Bitpacked || p.storage == WdcCompression::BitpackedSigned || p.isPallet()) {
461 fs.val1 = p.offsetBits - _bitpackedAt * 8;
462 fs.val2 = fs.fieldSizeBits;
463 }
464 if (p.isPallet()) {
465 fs.additionalDataSize = static_cast<std::uint32_t>(p.pallet.size() * 4);
466 if (p.storage == WdcCompression::PalletArray) fs.val3 = p.elements;
467 }
468 else if (p.isCommon()) {
469 fs.val1 = p.commonDefault;
470 fs.additionalDataSize = static_cast<std::uint32_t>(_common[cf].size() * 8);
471 }
472 else if (p.storage == WdcCompression::BitpackedSigned && _magic == Wdc1Magic) {
473 fs.storageType = WdcCompression::Bitpacked;
474 fs.val3 = 1; // WDC1: flags bit 0x01 = sign-extend
475 }
476 ++cf;
477 _append(out, &fs, sizeof fs);
478 }
479 }
480
483 void _appendPalletCommon(FileBuffer& out) const {
484 for (const FieldPlan& p : _plan)
485 if (p.isPallet())
486 for (const std::uint32_t v : p.pallet) _append(out, &v, 4);
487 for (const auto& fieldEntries : _common)
488 for (const auto& [id, value] : fieldEntries) {
489 _append(out, &id, 4);
490 _append(out, &value, 4);
491 }
492 }
493
496 void _appendIdsCopies(FileBuffer& out) const {
497 if (_noninlineId())
498 for (const std::uint32_t id : _ids) _append(out, &id, 4);
499 for (const auto& [newId, srcId] : _copies) {
500 _append(out, &newId, 4);
501 _append(out, &srcId, 4);
502 }
503 }
504
509 Result<FileBuffer> _emitWdc1() const {
510 const std::uint32_t realCount = static_cast<std::uint32_t>(_reals.size());
511 Wdc1Header header;
512 header.recordCount = realCount;
513 header.fieldCount = static_cast<std::uint32_t>(_plan.size());
514 header.recordSize = _recordSize;
515 header.stringTableSize = static_cast<std::uint32_t>(_stringRegion.size());
516 header.tableHash = _state.wdcTableHash;
517 header.layoutHash = _state.wdcLayoutHash;
518 header.minId = _minId;
519 header.maxId = _maxId;
520 header.locale = _state.wdcLocale;
521 header.copyTableSize = static_cast<std::uint32_t>(_copies.size() * 8);
522 header.flags = _noninlineId() ? std::uint16_t{WdcFlagNoninlineId} : std::uint16_t{0};
523 header.idIndex = _noninlineId() ? std::uint16_t{0} : db::detail::idFieldIndex(_info.schema);
524 header.totalFieldCount = header.fieldCount;
525 header.bitpackedDataOffset = _bitpackedAt;
526 header.lookupColumnCount = _relationshipBytes() ? 1 : 0;
527 header.offsetMapOffset = 0;
528 header.idListSize = _noninlineId() ? realCount * 4 : 0;
529 header.fieldStorageInfoSize = header.fieldCount * static_cast<std::uint32_t>(sizeof(WdcFieldStorage));
530 header.commonDataSize = _commonTotal;
531 header.palletDataSize = _palletTotal;
532 header.relationshipDataSize = static_cast<std::uint32_t>(_relationshipBytes());
533
534 FileBuffer out;
535 out.reserve(
536 _stringsAt + _stringRegion.size() + header.idListSize + header.copyTableSize + header.
537 fieldStorageInfoSize + _palletTotal + _commonTotal + header.relationshipDataSize);
538 _append(out, &header, sizeof header);
539 _appendFieldStructure(out);
540 out.insert(out.end(), _recordRegion.begin(), _recordRegion.end());
541 out.insert(out.end(), _stringRegion.begin(), _stringRegion.end());
542 _appendIdsCopies(out);
543 _appendFieldStorage(out);
544 _appendPalletCommon(out);
545 _appendRelationship(out);
546 return out;
547 }
548
554 Result<FileBuffer> _emitWdc3() const {
555 const std::uint32_t realCount = static_cast<std::uint32_t>(_reals.size());
556 const std::uint32_t fieldCount = static_cast<std::uint32_t>(_plan.size());
557 const bool hasIdList = _noninlineId();
558
559 Wdc3Header header;
560 header.magic = _magic;
561 header.recordCount = realCount;
562 header.fieldCount = fieldCount;
563 header.recordSize = _recordSize;
564 header.stringTableSize = static_cast<std::uint32_t>(_stringRegion.size());
565 header.tableHash = _state.wdcTableHash;
566 header.layoutHash = _state.wdcLayoutHash;
567 header.minId = _minId;
568 header.maxId = _maxId;
569 header.locale = _state.wdcLocale;
570 header.flags = hasIdList ? std::uint16_t{WdcFlagNoninlineId} : std::uint16_t{0};
571 header.idIndex = hasIdList ? std::uint16_t{0} : db::detail::idFieldIndex(_info.schema);
572 header.totalFieldCount = fieldCount;
573 header.bitpackedDataOffset = _bitpackedAt;
574 header.lookupColumnCount = _relationshipBytes() ? 1 : 0;
575 header.fieldStorageInfoSize = fieldCount * static_cast<std::uint32_t>(sizeof(WdcFieldStorage));
576 header.palletDataSize = _palletTotal;
577 header.commonDataSize = _commonTotal;
578 header.sectionCount = 1;
579
580 Wdc3SectionHeader section;
581 section.fileOffset = static_cast<std::uint32_t>(_recordsAt);
582 section.recordCount = realCount;
583 section.stringTableSize = static_cast<std::uint32_t>(_stringRegion.size());
584 section.idListSize = hasIdList ? realCount * 4 : 0;
585 section.relationshipDataSize = static_cast<std::uint32_t>(_relationshipBytes());
586 section.copyTableCount = static_cast<std::uint32_t>(_copies.size());
587
588 FileBuffer out;
589 out.reserve(
590 _stringsAt + _stringRegion.size() + std::size_t{realCount} * 4
591 + _copies.size() * 8 + _relationshipBytes());
592 if (_magic == Wdc5Magic) {
593 // Preserve the prefix a loaded WDC5 carried; fresh tables emit the
594 // default version with a blank schema name.
595 Wdc5HeaderPrefix prefix;
596 if (_state.wdc5Prefix.size() == sizeof prefix)
597 std::memcpy(&prefix, _state.wdc5Prefix.data(), sizeof prefix);
598 _append(out, &header.magic, sizeof header.magic);
599 _append(out, &prefix, sizeof prefix);
600 _append(out, &header.recordCount, sizeof header - sizeof header.magic);
601 }
602 else _append(out, &header, sizeof header);
603 _append(out, &section, sizeof section);
604 _appendFieldStructure(out);
605 _appendFieldStorage(out);
606 _appendPalletCommon(out);
607 out.insert(out.end(), _recordRegion.begin(), _recordRegion.end());
608 out.insert(out.end(), _stringRegion.begin(), _stringRegion.end());
609 _appendIdsCopies(out);
610 _appendRelationship(out);
611 return out;
612 }
613
617 void _appendRelationship(FileBuffer& out) const {
618 if (_relationCol == std::numeric_limits<std::size_t>::max()) return;
619 const std::uint32_t num = static_cast<std::uint32_t>(_reals.size());
620 std::uint32_t rmin = std::numeric_limits<std::uint32_t>::max(), rmax = 0;
621 std::vector<std::pair<std::uint32_t, std::uint32_t>> entries;
622 entries.reserve(_reals.size());
623 for (std::uint32_t i = 0; i < _reals.size(); ++i) {
624 const auto foreign = static_cast<std::uint32_t>(_source.getInt(_reals[i], _relationCol, 0));
625 entries.emplace_back(foreign, i);
626 rmin = std::min(rmin, foreign);
627 rmax = std::max(rmax, foreign);
628 }
629 if (entries.empty()) rmin = 0;
630 _append(out, &num, 4);
631 _append(out, &rmin, 4);
632 _append(out, &rmax, 4);
633 for (const auto& [foreign, index] : entries) {
634 _append(out, &foreign, 4);
635 _append(out, &index, 4);
636 }
637 }
638
639 std::uint32_t _magic;
640 const TableInfo& _info;
641 const RecordSource& _source;
642 const TableState& _state;
643 std::vector<std::pair<std::size_t, Column>> _cols;
644 std::size_t _relationCol = std::numeric_limits<std::size_t>::max();
645 std::vector<FieldPlan> _plan;
646 std::uint32_t _recordSize = 0;
647 std::uint32_t _bitpackedAt = 0;
648 std::vector<std::uint32_t> _reals;
649 std::vector<std::pair<std::uint32_t, std::uint32_t>> _copies;
650 std::vector<std::vector<std::pair<std::uint32_t, std::uint32_t>>> _common;
651 std::uint32_t _commonTotal = 0;
652 std::uint32_t _palletTotal = 0;
653 formats::StringBlock _block;
654 std::unordered_map<std::string, std::uint32_t> _lookup;
655 FileBuffer _recordRegion;
656 FileBuffer _stringRegion;
657 std::vector<std::uint32_t> _ids;
658 std::size_t _recordsAt = 0;
659 std::size_t _stringsAt = 0;
660 std::uint32_t _minId = 0, _maxId = 0;
661 };
662 }
663
664 Result<FileBuffer> writeWdc(std::uint32_t magic,
665 const TableInfo& info,
666 const RecordSource& source,
667 const TableState& state,
668 EncryptedPolicy policy) {
669 if (info.version < builds::Cata)
671 std::format("{}: a pre-Cata client does not use the WDC formats", info.name));
672
673 if (!state.encrypted.empty() && policy == EncryptedPolicy::Preserve) {
674 if (state.wdcOriginal.empty())
676 std::format(
677 "{}: a WDC table with encrypted sections can only be written by preserving "
678 "its original image, which is not available here", info.name));
679 return FileBuffer{state.wdcOriginal.begin(), state.wdcOriginal.end()};
680 }
681
682 return Writer{magic, info, source, state}.emit();
683 }
684}
The encode source: the codecs read the record vector through this.
Definition codec.hpp:135
Named ClientVersion constants for the exact client builds format features appeared (or vanished) at —...
constexpr ClientVersion Cata
Cataclysm (any 4.x client).
std::uint16_t idFieldIndex(std::span< const Column > schema)
The inline field index of an inline id column (0 when non-inline).
Definition codec.hpp:182
bool idIsNoninline(std::span< const Column > schema)
Whether the id column is stored outside the record ($noninline$ id).
Definition codec.hpp:175
WdcCompression
How a column is packed (field_storage_info.storageType).
Definition binary.hpp:152
@ Bitpacked
Bit-addressed unsigned value (signed in WDC1 when the entry's flags word carries bit 0x01).
Definition binary.hpp:154
@ None
Stored inline at a byte offset, fieldSizeBits wide.
Definition binary.hpp:153
@ BitpackedSigned
Bit-addressed signed value, sign-extended (WDC2+ only; WDC1 spells it Bitpacked + flags 0x01).
Definition binary.hpp:159
@ CommonData
Sparse per-id override table; default when an id is absent.
Definition binary.hpp:156
@ PalletArray
Bit-addressed index into palletData (array per slot).
Definition binary.hpp:158
@ Pallet
Bit-addressed index into palletData (one value per slot).
Definition binary.hpp:157
@ WdcFlagNoninlineId
The id lives in the idList, not the record.
Definition binary.hpp:56
Result< FileBuffer > writeWdc(std::uint32_t magic, const TableInfo &info, const RecordSource &source, const TableState &state, EncryptedPolicy policy)
Encode a canonical image of flavor magic: one unencrypted section, integers bitpacked to their minimu...
Definition write.cpp:664
constexpr std::uint32_t Wdc1Magic
The WDC1 magic as memcpy'd off the file front (the bytes "WDC1").
Definition binary.hpp:32
constexpr std::uint32_t Wdc5Magic
The WDC5 magic as memcpy'd off the file front (the bytes "WDC5").
Definition binary.hpp:41
@ Int
An integer column; Column::bits / Column::isSigned give the shape.
Definition schema.hpp:35
@ Float
A 32-bit IEEE float column.
Definition schema.hpp:36
@ String
A string-block reference column (u32 offset on disk).
Definition schema.hpp:37
EncryptedPolicy
How write() treats a table that still holds keyless (undecryptable) encrypted sections.
Definition codec.hpp:33
@ Preserve
Re-emit the original image verbatim; edits are not applied.
Definition codec.hpp:34
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
Definition error.hpp:107
@ InvalidEntityState
An entity's members disagree (e.g.
Definition error.hpp:40
@ NotSupported
Operation not supported by this backend/provider.
Definition error.hpp:32
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
Definition buffer.hpp:16
Schema reflection over generated client-database record structs: the column list, record stride and f...
StringBlock — the decoded representation of a chunk of zero-terminated strings (MOTX,...
The per-table identity a codec needs beyond the record data: the client version (format selection,...
Definition codec.hpp:58
ClientVersion version
The record's client version.
Definition codec.hpp:59
std::string_view name
The WoWDBDefs table name.
Definition codec.hpp:60
The preserved decode state a table carries between read() and write() so a WDBC/WDB2 write is byte-pe...
Definition codec.hpp:68
std::vector< std::byte > wdcOriginal
Raw image kept when the file has encrypted sections.
Definition codec.hpp:86
std::vector< EncryptedSection > encrypted
Encrypted WDC sections skipped on read.
Definition codec.hpp:82
The WDC-family codec: the non-templated entry points Table<Record> dispatches to for every column-com...