wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
table_core.cpp
Go to the documentation of this file.
1
6
8
9#include <algorithm>
10#include <cstring>
11#include <format>
12#include <unordered_map>
13
15#include <wowlib/db/wdb2.hpp>
16#include <wowlib/db/wdbc.hpp>
17#include <wowlib/db/wdc/wdc.hpp>
20
21namespace wowlib::db {
22 Result<void> TableCore::_requireWired() const {
23 if ((_vec && _ops) || (_extSink && _extSource)) return {};
25 "this table base carries no records: construct a concrete "
26 "table class, not the TableBase supertype");
27 }
28
29 Result<void> TableCore::read(std::span<const std::byte> data) {
30 if (auto w = _requireWired(); !w) return w;
31 if (data.size() < sizeof(std::uint32_t))
33 std::format("{}: {} bytes is too small for a client database", _info.name, data.size()));
34 std::uint32_t magic = 0;
35 std::memcpy(&magic, data.data(), sizeof magic);
36 // Either wiring: the codecs only ever see the RecordSink interface.
37 ErasedRecordSink erased{_vec, _ops};
38 RecordSink& sink = _extSink ? *_extSink : static_cast<RecordSink&>(erased);
39 if (magic == WdbcMagic) return readWdbc(_info, data, sink, _state);
40 if (magic == Wdb2Magic) return readWdb2(_info, data, sink, _state);
41 if (wdc::isWdcMagic(magic)) return wdc::readWdc(_info, data, sink, _state);
42 return makeError(ErrorCode::TableMagicUnknown, std::format(
43 "{}: magic '{}' is not a client-database format wowlib supports for " "client {}.{}.{}.{}",
45 _info.version.major, _info.version.minor, _info.version.patch, _info.version.build));
46 }
47
49 if (auto w = _requireWired(); !w) return w;
50 const auto data = fs.readFile(key);
51 if (!data) return std::unexpected{data.error()};
52 return read(*data);
53 }
54
56 if (auto w = _requireWired(); !w) return std::unexpected{w.error()};
57 if (_state.sourceMagic != 0) return _writeAs(_state.sourceMagic, policy);
58 const auto magic = _freshMagic(std::nullopt);
59 if (!magic) return std::unexpected{magic.error()};
60 return _writeAs(*magic, policy);
61 }
62
64 if (auto w = _requireWired(); !w) return w;
65 const FileKey resolved = fs.resolve(key);
66 if (!resolved.path)
68 std::format("saving table {} needs a path for the file key", _info.name));
69 auto data = [&]() -> Result<FileBuffer> {
70 if (_state.sourceMagic != 0) return _writeAs(_state.sourceMagic, policy);
71 const auto magic = _freshMagic(*resolved.path);
72 if (!magic) return std::unexpected{magic.error()};
73 return _writeAs(*magic, policy);
74 }();
75 if (!data) return std::unexpected{data.error()};
76 if (auto r = fs.addFile(*resolved.path, *data); !r) return std::unexpected{r.error()};
77 return {};
78 }
79
82 if (!_requireWired()) return report; // an unwired base holds nothing to violate
83 const std::span<const Column> schema = _info.schema;
84 const ErasedRecordSource erased{_vec, _ops};
85 const RecordSource& source = _extSource ? *_extSource : static_cast<const RecordSource&>(erased);
86
87 // Plenty of client tables are KEYLESS — pure lookup rows with no $id$
88 // column. No primary key to keep unique there.
89 const bool hasId = std::ranges::any_of(schema, [](const Column& c) { return c.isId; });
90 std::unordered_map<std::uint32_t, std::size_t> firstSeen;
91 const std::size_t n = source.size();
92 for (std::size_t r = 0; r < n && !report.full(); ++r) {
93 for (std::size_t c = 0; c < schema.size(); ++c) {
94 const Column& column = schema[c];
95 const std::size_t slots = column.stringSlots();
96 for (std::size_t e = 0; e < slots; ++e)
97 if (source.getString(r, c, e).find('\0') != std::string_view::npos)
98 report.addError(
99 slots > 1
100 ? std::format("records[{}].{}[{}]", r, column.nameView(), e)
101 : std::format("records[{}].{}", r, column.nameView()),
102 "the string holds an embedded NUL; the string block "
103 "terminates entries with it, so it would read back truncated");
104 }
105
106 if (hasId) {
107 const std::uint32_t recordId = source.idOf(r);
108 if (const auto [at, fresh] = firstSeen.try_emplace(recordId, r); !fresh)
109 report.addError(std::format("records[{}]", r),
110 std::format(
111 "duplicate id {} (already used by records[{}]); the " "client indexes records by id",
112 recordId, at->second));
113 }
114 }
115 return report;
116 }
117
118 std::uint32_t TableCore::_db2MagicForVersion() const {
119 const ClientVersion& v = _info.version;
120 if (v < builds::Legion) return Wdb2Magic;
121 if (v < builds::BfA) return wdc::Wdc1Magic;
122 if (v < ClientVersion{10, 1, 0, 48480}) return wdc::Wdc3Magic;
123 if (v < ClientVersion{10, 2, 5, 52432}) return wdc::Wdc4Magic;
124 return wdc::Wdc5Magic;
125 }
126
127 Result<std::uint32_t> TableCore::_freshMagic(std::optional<std::string_view> path) const {
128 if (path) {
129 if (path->ends_with(".dbc")) return WdbcMagic;
130 if (path->ends_with(".db2"))
131 return _info.version < builds::Cata
133 std::format("{}: .db2 does not exist before Cataclysm", _info.name))
134 : Result<std::uint32_t>{_db2MagicForVersion()};
135 }
136 if (_info.version < builds::Cata) return WdbcMagic;
137 return _db2MagicForVersion();
138 }
139
140 Result<FileBuffer> TableCore::_writeAs(std::uint32_t magic, EncryptedPolicy policy) const {
141 ErasedRecordSource erased{_vec, _ops};
142 const RecordSource& source = _extSource ? *_extSource : static_cast<const RecordSource&>(erased);
143 if (magic == WdbcMagic) return writeWdbc(_info, source, _state);
144 if (magic == Wdb2Magic) return writeWdb2(_info, source, _state);
145 if (wdc::isWdcMagic(magic)) return wdc::writeWdc(magic, _info, source, _state, policy);
146 return makeError(ErrorCode::NotImplemented, std::format("{}: writing '{}' tables is not implemented yet",
147 _info.name, formats::fourccToString(
149 }
150}
RecordSink over a live std::vector<Record>& — the decode target.
RecordSource over a const std::vector<Record>& — the encode source.
The decode target: the codecs build the record vector through this, one field at a time.
Definition codec.hpp:110
The encode source: the codecs read the record vector through this.
Definition codec.hpp:135
virtual std::size_t size() const =0
virtual std::uint32_t idOf(std::size_t record) const =0
virtual std::string_view getString(std::size_t record, std::size_t column, std::size_t element) const =0
Result< FileBuffer > write(EncryptedPolicy policy) const
Result< void > read(std::span< const std::byte > data)
formats::ValidationReport validate() const
void addError(std::string path, std::string message)
Record an error finding (see add()).
Named ClientVersion constants for the exact client builds format features appeared (or vanished) at —...
The runtime facade over the static compositions — the primary welder binding surface of the fs layer.
FourCC chunk identifiers: compile-time conversion of the four-letter codes to the host integers chunk...
constexpr ClientVersion Legion
legion (any 7.x client).
constexpr ClientVersion Cata
Cataclysm (any 4.x client).
constexpr ClientVersion BfA
Battle for Azeroth (any 8.x client).
constexpr bool isWdcMagic(std::uint32_t magic)
Whether magic names a format of the WDC family this codec speaks.
Definition binary.hpp:47
constexpr std::uint32_t Wdc3Magic
The WDC3 magic as memcpy'd off the file front (the bytes "WDC3").
Definition binary.hpp:35
constexpr std::uint32_t Wdc4Magic
The WDC4 magic as memcpy'd off the file front (the bytes "WDC4").
Definition binary.hpp:38
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...
Definition read.cpp:390
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
constexpr std::uint32_t WdbcMagic
The WDBC magic as memcpy'd off the file front (the bytes "WDBC").
Definition wdbc.hpp:25
Result< FileBuffer > writeWdbc(const TableInfo &info, const RecordSource &source, const TableState &state)
Encode a WDBC image; the string block preserves decoded offsets and appends new/changed strings (byte...
Definition wdbc.cpp:58
constexpr std::uint32_t Wdb2Magic
The WDB2 magic as memcpy'd off the file front (the bytes "WDB2").
Definition wdb2.hpp:27
EncryptedPolicy
How write() treats a table that still holds keyless (undecryptable) encrypted sections.
Definition codec.hpp:33
Result< void > readWdbc(const TableInfo &info, std::span< const std::byte > data, RecordSink &sink, TableState &state)
Decode a WDBC image: 20-byte header, fixed-stride records, string block.
Definition wdbc.cpp:17
Result< FileBuffer > writeWdb2(const TableInfo &info, const RecordSource &source, const TableState &state)
Encode a WDB2 image; header identity and the verbatim index/copy blocks are re-emitted.
Definition wdb2.cpp:68
Result< void > readWdb2(const TableInfo &info, std::span< const std::byte > data, RecordSink &sink, TableState &state)
Decode a WDB2 image: header, optional id-index block, fixed-stride records, string block,...
Definition wdb2.cpp:14
constexpr std::string fourccToString(std::uint32_t fourcc, FourCCEndian endian=FourCCEndian::Reversed)
The readable four-character spelling of a scanned chunk id.
Definition fourcc.hpp:46
@ Forward
The characters are stored as written: 'AFID' appears in the file as the bytes "AFID".
Definition fourcc.hpp:22
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
@ PathNotResolvable
No FileDataID is known for the given path (listfile miss).
Definition error.hpp:23
@ TableMagicUnknown
A client-database magic wowlib does not support for the requested version.
Definition error.hpp:45
@ NotImplemented
Placeholder during phased implementation.
Definition error.hpp:33
@ NotSupported
Operation not supported by this backend/provider.
Definition error.hpp:32
@ TableTruncated
A client-database header, record block or satellite block overruns the file.
Definition error.hpp:44
std::optional< std::string > path
The canonical client-internal path, if known.
Definition file_key.hpp:31
One column of a record schema, derived from a record member by reflection (typed records) or from the...
Definition schema.hpp:46
constexpr std::string_view nameView() const
The member spelling as a view.
Definition schema.hpp:79
bool isId
Whether this is the table's primary key ($id$).
Definition schema.hpp:64
constexpr std::size_t stringSlots() const
The string-block references the column stores per record (the width of the engine's per-record offset...
Definition schema.hpp:115
ClientVersion version
The record's client version.
Definition codec.hpp:59
TableCore + TableBase — the non-templated heart of every client-database table, and the ONE welded su...
WDB2 — the .db2 format of the Cataclysm..WoD MPQ/early-CASC era (4.0.1.12911 .
WDBC — the client-database format of every pre-Cataclysm .dbc (and the .dbc leftovers of Cataclysm....
The WDC-family codec: the non-templated entry points Table<Record> dispatches to for every column-com...