wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
wdt.hpp
Go to the documentation of this file.
1#pragma once
2
10
11#include <array>
12#include <format>
13#include <span>
14#include <string>
15#include <string_view>
16
19#include <wowlib/core/error.hpp>
21#include <wowlib/core/lang.hpp>
31
32namespace wowlib::formats::wdt {
33 using root::WDTRoot;
34
44 struct [[
45 =welder::weld,
46 =welder::weld_as("WDT"),
48 =welder::doc(R"(
49 A whole map description, abstract over the client version — the main
50 .wdt file and its era's satellite files (_occ/_lgt/_fogs/_mpv) as one
51 entity. Construct the concrete version with WDT.for_version(expansion),
52 then read()/write(); the per-version WDT* classes are subclasses. See
53 https://wowdev.wiki/WDT.)")
54 ]] WDTBase : FileEntityBase {};
55
56 namespace detail {
57 // --- version-range satellite traits (unwelded) ----------------------------
58 // One trait per satellite introduction; welder flattens an active trait's
59 // entity member onto the assembly binding.
60
62 template <ClientVersion V>
63 struct SatellitesWod {
64 [[=welder::doc(
65 "The _occ.wdt occlusion satellite (WoD+); default-empty when "
66 "the file does not exist.")]]
68
69 [[=welder::doc(
70 "The _lgt.wdt lights satellite (WoD+); default-empty when the "
71 "file does not exist.")]]
73 };
74
76 template <ClientVersion V>
77 struct SatellitesLegion725 {
78 [[=welder::doc("The _fogs.wdt volumetric-fog satellite (Legion 7.2.5+); "
79 "default-empty when the file does not exist.")]]
81 };
82
84 template <ClientVersion V>
85 struct SatellitesBfa {
86 [[=welder::doc("The _mpv.wdt particulate-volume satellite (BfA+); "
87 "default-empty when the file does not exist.")]]
89 };
90 }
91
92 namespace detail {
98 WMO an object-only map shows; the satellites carry the map-wide
99 occlusion heightmaps, placed lights, volumetric fogs and particulate
100 volumes of the eras that have them. Satellites locate by the
101 "{map}_occ.wdt" naming convention up to 8.1 and by the MPHD
102 FileDataIDs after; a missing satellite stays default-empty and is not
103 written back.
104
105 @tparam V the client version this assembly targets.
106 @see https://wowdev.wiki/WDT */
107 template <ClientVersion V>
108 struct [[
109 =welder::weld,
110 =welder::doc(R"(
111 A whole map description for one client version: the main .wdt file
112 and its era's satellite files (_occ/_lgt since WoD, _fogs since
113 Legion 7.2.5, _mpv since BfA) as one entity. Satellites locate by
114 the "{map}_occ.wdt" naming convention up to 8.1 and by the MPHD
115 FileDataIDs after; a missing satellite stays default-empty. An
116 entity read from a client and left unmodified rewrites
117 byte-for-byte. See https://wowdev.wiki/WDT.)")
118 ]] WDT
123 static constexpr ClientVersion Version = V;
124
125 [[=welder::doc("The main file contents.")]]
127
128 // read()/write() weld the (FileSystem, FileKey) load/save on LUA AND C#
129 // ONLY — on Python the module glue attaches the read()/write()/convert()/
130 // for_version() surface to WDTBase instead (dispatching to the concrete
131 // version), so the per-version Python classes stay pure data. Lua and C#
132 // have no such glue, so they take these methods directly.
133
134 [[=welder::mark::only(welder::lang::lua, wowlib::lang::Cs),
135 =welder::doc(
136 "Load the WDT and every satellite file present from a client "
137 "filesystem, replacing this entity's contents.")]]
138 Result<void> read(fs::FileSystem& fs [[=welder::doc("the filesystem gateway")]],
139 const FileKey& key [[=welder::doc("the main file identity (path and/or FileDataID)")]]);
140
141 [[=welder::mark::only(welder::lang::lua, wowlib::lang::Cs),
142 =welder::doc("Serialize and store the WDT (main file and every engaged "
143 "satellite) through the filesystem's project overlay; satellite "
144 "file names are derived from the main key, which must resolve "
145 "to a path.")]]
146 Result<void> write(fs::FileSystem& fs [[=welder::doc("the filesystem gateway")]],
147 const FileKey& key [[=welder::doc("the main file identity; must resolve to a path")]]) const;
148
149 [[=welder::doc(R"(
150 Check the logical integrity contracts this object must satisfy to
151 LOAD in the client — across the main file AND every engaged
152 satellite — which write() deliberately never enforces. Call it
153 before writing when you want to know the files will load. An object
154 read from a client and left unmodified reports no errors.)"),
155 =welder::returns(R"(every violated contract, each with its member path
156 ("root..." / "occlusion..." / "lights..." /
157 "fogs..." / "particulates..."))")]]
158 ValidationReport validate() const;
159
160 [[nodiscard]]
161 [[=welder::doc("Validate and raise on the first error instead of "
162 "returning a report — the assert-style face of "
163 "validate()."),
164 =welder::returns("nothing; raises when validate() finds any error")]]
166
167 private:
168 // --- internal fs-I/O helpers (definitions at the bottom of this header) --
169
175 static std::string _satellitePath(std::string_view rootPath, std::string_view suffix);
176 };
177 }
178
182 template <ClientVersion V>
184}
185
186// --- fs-level read/write definitions -----------------------------------------
187// Inline in this header: the entities are templates, so the definitions must be
188// visible for implicit instantiation — the library ships NO explicit
189// instantiations; the bindings expand the full matrix in their own TUs.
190namespace wowlib::formats::wdt {
191 template <ClientVersion V>
192 std::string detail::WDT<V>::_satellitePath(std::string_view rootPath, std::string_view suffix) {
193 std::string_view stem = rootPath;
194 if (stem.ends_with(".wdt")) stem.remove_suffix(4);
195 return std::format("{}_{}.wdt", stem, suffix);
196 }
197
198 template <ClientVersion V>
200 const auto rootData = fs.readFile(key);
201 if (!rootData) return std::unexpected{rootData.error()};
202
203 *this = WDT{};
204
205 if (auto r = root.read(*rootData); !r) return std::unexpected{r.error()};
206 if (root.mver != WdtVersion18)
208 std::format("WDT MVER is {}, expected {}", root.mver, WdtVersion18));
209
210 if constexpr (requires { this->occlusion; }) {
211 // 8.1+ headers carry the satellite FileDataIDs; before that the files
212 // sit next to the main one under the "{map}_<suffix>.wdt" convention.
213 constexpr bool byFdid = requires { this->root.header.occFdid; };
214
215 std::string rootPath;
216 if constexpr (!byFdid) {
217 const FileKey resolved = fs.resolve(key);
218 if (!resolved.path)
220 "satellite files need the main path (pre-8.1 clients have no "
221 "satellite FileDataIDs and the main key has no resolvable path)");
222 rootPath = *resolved.path;
223 }
224
225 const auto load = [&](auto& satellite, std::uint32_t fdid, std::string_view suffix) -> Result<void> {
226 if constexpr (byFdid)
227 if (fdid == 0) return {}; // the map has no such satellite
228 // Not if-constexpr: both arms are well-formed either way, and gcc-16
229 // false-positives -Wreturn-type on constexpr-exhaustive lambdas.
230 const FileKey satelliteKey = byFdid ? FileKey{FileDataID{fdid}} : FileKey{_satellitePath(rootPath, suffix)};
231 if (!fs.exists(satelliteKey)) return {}; // absent satellite: stays default-empty
232 const auto data = fs.readFile(satelliteKey);
233 if (!data)
234 return makeError(data.error().code, std::format("_{} satellite: {}", suffix, data.error().message),
235 data.error().nativeError);
236 if (auto r = satellite.read(*data); !r)
237 return makeError(r.error().code, std::format("_{} satellite: {}", suffix, r.error().message),
238 r.error().nativeError);
239 return {};
240 };
241
242 const auto headerFdid = [&](auto pick) -> std::uint32_t {
243 if constexpr (byFdid) return pick(root.header);
244 else return 0;
245 };
246
247 if (auto r = load(this->occlusion, headerFdid([](const auto& h) { return h.occFdid; }), "occ"); !r) return r;
248 if (auto r = load(this->lights, headerFdid([](const auto& h) { return h.lgtFdid; }), "lgt"); !r) return r;
249 if constexpr (requires { this->fogs; })
250 if (auto r = load(this->fogs, headerFdid([](const auto& h) {
251 return h.fogsFdid;
252 }), "fogs"); !r)
253 return r;
254 if constexpr (requires { this->particulates; })
255 if (auto r = load(this->particulates, headerFdid([](const auto& h) { return h.mpvFdid; }), "mpv"); !r) return
256 r;
257 }
258 return {};
259 }
260
261 template <ClientVersion V>
262 ValidationReport detail::WDT<V>::validate() const {
263 ValidationReport report;
264 {
265 const std::size_t mark = report.size();
267 report.prefixFrom(mark, "root");
268 }
269 // Each engaged satellite validates under its member path; the version
270 // ranges the entity does not carry cost nothing to gate on.
271 const auto validatePart = [&report](const auto& part, std::string_view name) {
272 const std::size_t mark = report.size();
274 report.prefixFrom(mark, std::string{name});
275 };
276 if constexpr (requires { this->occlusion; }) validatePart(this->occlusion, "occlusion");
277 if constexpr (requires { this->lights; }) validatePart(this->lights, "lights");
278 if constexpr (requires { this->fogs; }) validatePart(this->fogs, "fogs");
279 if constexpr (requires { this->particulates; }) validatePart(this->particulates, "particulates");
280 return report;
281 }
282
283 template <ClientVersion V>
285 return validate().toResult();
286 }
287
288 template <ClientVersion V>
289 Result<void> detail::WDT<V>::write(fs::FileSystem& fs, const FileKey& key) const {
290 const FileKey resolved = fs.resolve(key);
291 if (!resolved.path)
292 return makeError(ErrorCode::PathNotResolvable, "saving a WDT needs a path for the main key");
293
294 const auto rootData = root.write();
295 if (!rootData) return std::unexpected{rootData.error()};
296 if (auto r = fs.addFile(*resolved.path, *rootData); !r) return std::unexpected{r.error()};
297
298 if constexpr (requires { this->occlusion; }) {
299 // a satellite writes only when engaged: read from a file (journaled) or
300 // holding user data — a default-empty one stays unwritten
301 const auto store = [&](const auto& satellite, std::string_view suffix) -> Result<void> {
302 if (!formats::detail::entityEngaged(satellite)) return {};
303 const auto data = satellite.write();
304 if (!data)
305 return makeError(data.error().code, std::format("_{} satellite: {}", suffix, data.error().message),
306 data.error().nativeError);
307 if (auto r = fs.addFile(_satellitePath(*resolved.path, suffix), *data); !r)
308 return makeError(r.error().code, std::format("_{} satellite: {}", suffix, r.error().message),
309 r.error().nativeError);
310 return {};
311 };
312
313 if (auto r = store(this->occlusion, "occ"); !r) return r;
314 if (auto r = store(this->lights, "lgt"); !r) return r;
315 if constexpr (requires { this->fogs; })
316 if (auto r = store(this->fogs, "fogs"); !r) return r;
317 if constexpr (requires { this->particulates; })
318 if (auto r = store(this->particulates, "mpv"); !r) return r;
319 }
320 return {};
321 }
322}
FileKey resolve(const FileKey &key) const
Result< FileBuffer > readFile(const FileKey &key)
Named ClientVersion constants for the exact client builds format features appeared (or vanished) at —...
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
File identity types: the strong FileDataID and the FileKey a read request travels as.
The runtime facade over the static compositions — the primary welder binding surface of the fs layer.
The _fogs.wdt volumetric-fog satellite entity (namespace wowlib::formats::wdt::fogs),...
Binding-language identities welder's core does not name.
#define WOWLIB_CS_FAMILY_SURFACE
The C# rod's family-surface opt-in, spellable in every build.
Definition lang.hpp:44
The _lgt.wdt lights satellite entity (namespace wowlib::formats::wdt::lights), WoD+: freely placed ma...
The _mpv.wdt particulate-volume satellite entity (namespace wowlib::formats::wdt::mpv),...
void validateEntity(const E &entity, ValidationReport &report)
Validate a whole entity — the engine behind every validate() method; see ChunkedFile::validate() for ...
bool entityEngaged(const E &entity)
Does a never-journaled nested entity hold anything worth a chunk?
The _fogs.wdt volumetric-fog satellite entity (Legion 7.2.5+).
Definition records.hpp:13
detail::WDTFogs< canonicalVersion(V, WdtFogsPivots, WdtFogsVersions)> WDTFogs
A _fogs.wdt satellite — the canonicalizing face of detail::WDTFogs: three instantiations (Legion; BfA...
Definition fogs.hpp:79
The _lgt.wdt lights satellite entity (WoD+).
Definition records.hpp:16
detail::WDTLights< canonicalVersion(V, WdtLightsPivots, WdtSatelliteVersions)> WDTLights
A _lgt.wdt satellite — the canonicalizing face of detail::WDTLights: three instantiations (WoD; Legio...
Definition lights.hpp:110
detail::WDTParticulates< canonicalVersion(V, WdtMpvPivots, WdtMpvVersions)> WDTParticulates
A _mpv.wdt satellite — the canonicalizing face of detail::WDTParticulates: stable since BfA (its reco...
Definition mpv.hpp:77
The _occ.wdt occlusion satellite entity (WoD+).
Definition records.hpp:10
detail::WDTOcclusion< canonicalVersion(V, WdtOcclusionPivots, WdtSatelliteVersions)> WDTOcclusion
A _occ.wdt satellite — the canonicalizing face of detail::WDTOcclusion: stable since WoD,...
Definition occlusion.hpp:56
The WDT main-file entity: WDTRoot and its per-version classes.
Definition header.hpp:20
root::detail::WDTRoot< canonicalVersion(V, WdtRootPivots, WdtVersions)> WDTRoot
A WDT main file — the canonicalizing face of detail::WDTRoot: every client version maps to its range'...
Definition root.hpp:145
constexpr std::array WdtVersions
The versions WDT is instantiated (and welded) for: every targeted last-minor-of-major release,...
constexpr std::uint32_t WdtVersion18
The WDT format version every supported client uses (the .wdt and _occ.wdt MVER payload; _lgt/_fogs/_m...
detail::WDT< canonicalVersion(V, WdtAssemblyPivots, WdtVersions)> WDT
A whole WDT — the canonicalizing face of detail::WDT: every client version maps to its range's first ...
Definition wdt.hpp:147
constexpr std::array WdtAssemblyPivots
The WDT assembly: the union of the root and satellite pivots plus each satellite file's introduction ...
constexpr ClientVersion canonicalVersion(ClientVersion v, std::span< const ClientVersion > pivots, std::span< const ClientVersion > grid)
The canonical version v collapses to: the FIRST grid version in v's range.
std::conditional_t<(V.formatLineage() >=Since &&V.formatLineage()< Until), Trait, Absent< Trait > > Slot
A version-gated base: the entity inherits Trait (flattening its chunk members in) iff Since <= V < Un...
constexpr welder::lang Cs
C#/.NET — the welder-csharp rod's identity (user-range slot 0), respelled for wowlib's annotation sit...
Definition lang.hpp:23
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
@ PathNotResolvable
No FileDataID is known for the given path (listfile miss).
Definition error.hpp:23
@ FormatVersionMismatch
The file's version chunk disagrees with the requested version.
Definition error.hpp:41
The _occ.wdt occlusion satellite entity (namespace wowlib::formats::wdt::occlusion),...
std::optional< std::string > path
The canonical client-internal path, if known.
Definition file_key.hpp:31
The version-agnostic root of every file-level entity (welded as "FileEntity").
The version-agnostic base of every WDT<V> (welded as "WDT").
Definition wdt.hpp:50
The BfA satellite: particulate volumes.
Definition wdt.hpp:76
mpv::WDTParticulates< V > particulates
The _mpv.wdt particulate-volume satellite (BfA+); default-empty when the file does not exist.
Definition wdt.hpp:78
The Legion 7.2.5 satellite: volumetric fogs.
Definition wdt.hpp:69
fogs::WDTFogs< V > fogs
The _fogs.wdt volumetric-fog satellite (Legion 7.2.5+); default-empty when the file does not exist.
Definition wdt.hpp:71
The WoD satellites: occlusion and lights.
Definition wdt.hpp:59
occlusion::WDTOcclusion< V > occlusion
The _occ.wdt occlusion satellite (WoD+); default-empty when the file does not exist.
Definition wdt.hpp:61
lights::WDTLights< V > lights
The _lgt.wdt lights satellite (WoD+); default-empty when the file does not exist.
Definition wdt.hpp:64
A whole WDT (map description) for one client version: the main file and its era's satellite files uni...
Definition wdt.hpp:102
Result< void > ensureValid() const
Validate and raise on the first error instead of returning a report — the assert-style face of valida...
Definition wdt.hpp:248
Result< void > write(fs::FileSystem &fs, const FileKey &key) const
Serialize and store the WDT (main file and every engaged satellite) through the filesystem's project ...
Definition wdt.hpp:253
Result< void > read(fs::FileSystem &fs, const FileKey &key)
Load the WDT and every satellite file present from a client filesystem, replacing this entity's conte...
Definition wdt.hpp:163
ValidationReport validate() const
Definition wdt.hpp:226
The conditional-base mechanism that gives a versioned chunked entity exactly the fields its client ve...
WDT version grids and per-family canonicalization pivots.
The WDT main-file entity (namespace wowlib::formats::wdt::root): the map header, the 64x64 tile table...