wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
roundtrip_adt.cpp
Go to the documentation of this file.
1
14
15#include <array>
16#include <cstdint>
17#include <format>
18#include <string>
19#include <string_view>
20
25
26namespace {
27 using namespace wowlib;
28 using namespace wowlib::audit;
29 using namespace wowlib::formats;
30
38 return hasFlag(mphdFlags, AdtHasBigAlpha) ||
39 hasFlag(mphdFlags, AdtHasHeightTexturing)
42 }
43
49 std::string mapWdtOf(std::string_view tilePath) {
50 const auto lastSep = tilePath.rfind('\\');
51 if (lastSep == std::string_view::npos) return {};
52 const std::string_view dir = tilePath.substr(0, lastSep);
53 const auto prevSep = dir.rfind('\\');
54 const std::string_view map = prevSep == std::string_view::npos ? dir : dir.substr(prevSep + 1);
55 if (map.empty()) return {};
56 return std::format("{}\\{}.wdt", dir, map);
57 }
58
66 template <ClientVersion V>
67 std::optional<RoundtripReport> deriveAlpha(fs::FileSystem& fs,
68 const std::string& path,
69 RoundtripReport& report,
70 adt::AlphaFormat& alpha) {
71 const std::string wdtPath = mapWdtOf(path);
72 if (wdtPath.empty())
73 return audit::detail::failWith(report, "wdt locate",
74 "the tile path has no map directory to derive the WDT from");
75 const auto raw = fs.readFile(FileKey{wdtPath});
76 if (!raw || raw->empty()) {
77 // An unreadable or zero-byte map WDT is an addressing limitation, not a
78 // parse failure: community-listfile placeholder maps ("unkmaps/...")
79 // derive a synthetic WDT name no listfile entry or name hash resolves,
80 // and abandoned maps (2.4.3 development) ship an empty placeholder WDT.
81 // Without the WDT there is no alpha format, so the tile cannot be
82 // audited.
83 return audit::detail::skipped("no-map-wdt");
84 }
86 if (auto r = root.read(*raw); !r)
87 return audit::detail::failWith(report, "wdt parse", std::format("{}: {}", wdtPath, r.error().message));
88 alpha = alphaFormatOf(root.header.flags);
89 return std::nullopt;
90 }
91
99 template <ClientVersion V>
100 RoundtripReport roundtripAdt(fs::FileSystem& fs, const std::string& path) {
101 RoundtripReport report;
102 adt::AlphaFormat alpha{};
103 if (auto fail = deriveAlpha<V>(fs, path, report, alpha)) return *fail;
104
105 adt::ADT < V > tile;
106 if (auto r = tile.read(fs, FileKey{path}, alpha); !r) {
107 if (r.error().code == ErrorCode::EncryptedContent) return audit::detail::skipped("encrypted");
108 return audit::detail::failWith(report, "read", r.error().message);
109 }
110
111 const auto first = tile.writeFile(adt::FileKind::Monolithic, alpha);
112 if (!first) return audit::detail::failWith(report, "write", first.error().message);
113
114 adt::ADT < V > reparsed;
115 reparsed.alphaFormat = alpha;
116 if (auto r = reparsed.parseFile(*first, adt::FileKind::Monolithic); !r) return audit::detail::failWith(
117 report, "reparse", r.error().message);
118
119 const auto second = reparsed.writeFile(adt::FileKind::Monolithic, alpha);
120 if (!second)
121 return audit::detail::failWith(report, "rewrite", second.error().message);
122 if (auto divergence = audit::detail::firstDivergenceChunked(*first, *second)) return audit::detail::failWith(
123 report, "compare", *divergence);
124 return report;
125 }
126
135 template <ClientVersion V>
136 RoundtripReport roundtripAdtSplit(fs::FileSystem& fs, const std::string& path) {
137 RoundtripReport report;
138 adt::AlphaFormat alpha{};
139 if (auto fail = deriveAlpha<V>(fs, path, report, alpha)) return *fail;
140
141 adt::ADT < V > tile;
142 if (auto r = tile.read(fs, FileKey{path}, alpha); !r) {
143 if (r.error().code == ErrorCode::EncryptedContent) return audit::detail::skipped("encrypted");
144 return audit::detail::failWith(report, "read", r.error().message);
145 }
146
147 constexpr std::array kinds{adt::FileKind::Root, adt::FileKind::Tex0, adt::FileKind::Obj0};
148 constexpr std::array names{"root", "_tex0", "_obj0"};
149
150 adt::ADT < V > reparsed;
151 reparsed.alphaFormat = alpha;
152 reparsed.chunks.assign(256, adt::MapChunk < V > {});
153
154 std::array<FileBuffer, kinds.size()> firsts{};
155 for (std::size_t i = 0; i < kinds.size(); ++i) {
156 auto first = tile.writeFile(kinds[i], alpha);
157 if (!first)
158 return audit::detail::failWith(report, std::format("write ({})", names[i]), first.error().message);
159 if (auto r = reparsed.parseFile(*first, kinds[i]); !r)
160 return audit::detail::failWith(report, std::format("reparse ({})", names[i]), r.error().message);
161 firsts[i] = std::move(*first);
162 }
163 // _obj1/_lod are round-tripped verbatim by write(); mirror that here so
164 // the fresh entity is whole (they do not enter the per-kind compares).
165 if constexpr (requires { tile.obj1Data; }) {
166 reparsed.obj1Data = tile.obj1Data;
167 reparsed.lodData = tile.lodData;
168 }
169
170 for (std::size_t i = 0; i < kinds.size(); ++i) {
171 const auto second = reparsed.writeFile(kinds[i], alpha);
172 if (!second)
173 return audit::detail::failWith(report, std::format("rewrite ({})", names[i]), second.error().message);
174 if (auto divergence = audit::detail::firstDivergenceChunked(firsts[i], *second))
175 return audit::detail::failWith(report, std::format("compare ({})", names[i]), *divergence);
176 }
177 return report;
178 }
179}
180
181namespace wowlib::audit::detail {
183 RoundtripReport report = skipped("unsupported-version");
184 const bool matched = withVersion(version, [&]<ClientVersion V>() {
186 if constexpr (V < builds::Cata) report = guarded([&] { return roundtripAdt<V>(fs, path); });
187 else report = guarded([&] { return roundtripAdtSplit<V>(fs, path); });
188 }
189 });
190 if (!matched) return unrecognizedVersion(version);
191 return report;
192 }
193}
The ADT terrain-tile entity (namespace wowlib::formats::adt): ADT<V>, one map tile,...
static RoundtripReport adt(fs::FileSystem &fs, const std::string &path, ClientVersion version)
Stability round-trip of one terrain tile (monolithic pre-Cata, split Cata+), deriving the map's alpha...
Named ClientVersion constants for the exact client builds format features appeared (or vanished) at —...
Internals of the round-trip audit (namespace wowlib::audit::detail): the per-format driver class — on...
RoundtripReport failWith(RoundtripReport &report, std::string stage, std::string error)
Mark report failed in place and hand it back — keeps the unknown-chunk tally gathered before the fail...
Definition detail.hpp:57
bool withVersion(const ClientVersion v, F &&f)
Invoke f with the targeted release constant matching v as a compile-time template argument — the runt...
Definition detail.hpp:166
constexpr bool versionSupported(std::span< const ClientVersion > grid, const ClientVersion v)
Whether a format's instantiation grid carries v — the per-format if constexpr guard of the version di...
Definition detail.hpp:193
RoundtripReport skipped(std::string_view reason)
A skipped outcome — ok, tagged with the reason.
Definition detail.hpp:45
std::optional< std::string > firstDivergenceChunked(const FileBuffer &original, const FileBuffer &rewritten)
The first byte divergence between an original chunk stream and its rewrite, located within the enclos...
Definition detail.hpp:134
RoundtripReport guarded(F &&f)
Run one file's driver, converting any escaping exception into a failed outcome — one pathological fil...
Definition detail.hpp:100
RoundtripReport unrecognizedVersion(const ClientVersion v)
The dispatch-failure outcome for a version outside the targeted releases.
Definition detail.hpp:202
constexpr ClientVersion Cata
Cataclysm (any 4.x client).
constexpr std::array AdtVersions
The versions ADT is instantiated (and welded) for: every targeted last-minor-of-major release,...
detail::MapChunk< canonicalVersion(V, MapChunkPivots, AdtVersions)> MapChunk
A terrain chunk — the canonicalizing face of detail::MapChunk: every client version collapses to its ...
detail::ADT< canonicalVersion(V, AdtPivots, AdtVersions)> ADT
A terrain tile — the canonicalizing face of detail::ADT.
Definition adt.hpp:339
@ Monolithic
The single pre-Cataclysm .adt (every chunk).
Definition map_chunk.hpp:73
@ Obj0
The Cata+ _obj0.adt (doodad/object references and placements).
Definition map_chunk.hpp:76
@ Tex0
The Cata+ _tex0.adt (texture layers, alpha/shadow maps, materials).
Definition map_chunk.hpp:75
@ Root
The Cata+ root .adt (terrain heights/normals/colors, liquid, sounds).
Definition map_chunk.hpp:74
@ Highres8Bit
4096-byte 8-bit alpha maps (the WDT has adt_has_big_alpha or adt_has_height_texturing).
@ Lowres4Bit
2048-byte 4-bit alpha maps (the default when the WDT sets neither big-alpha flag).
MapHeaderFlags
Map header flag bits (SMMapHeader.flags).
Definition header.hpp:24
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 bool hasFlag(std::underlying_type_t< E > value, E flag)
Whether flag bit flag is set in the raw binary value value.
Definition flags.hpp:26
@ EncryptedContent
Content is behind an unknown TACT encryption key.
Definition error.hpp:31
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
Definition buffer.hpp:16
Result< void > read(std::span< const std::byte > data)
Deserialize file bytes into this entity, replacing its contents.
Result< FileBuffer > writeFile(FileKind kind, AlphaFormat alpha) const
Serialize one physical file of the tile (monolithic pre-Cata, or one of root/_tex0/_obj0),...
Definition adt.hpp:466
Result< void > parseFile(std::span< const std::byte > data, FileKind kind)
Parse one split file's chunk stream into this entity (merging), by reflecting over the chunk()-annota...
Definition adt.hpp:414
std::vector< adt::MapChunk< V > > chunks
The 256 terrain chunks (MCNK), row-major (index = y * 16 + x).
Definition adt.hpp:219
AlphaFormat alphaFormat
How this tile's alpha maps were laid out on disk, recorded from the AlphaFormat passed to read(); wri...
Definition adt.hpp:222
Result< void > read(fs::FileSystem &fs, const FileKey &key, AlphaFormat alpha)
Load the tile — every split file present — from a client filesystem, replacing this entity's contents...
Definition adt.hpp:573
SMMapHeader< V > header
The map header (MPHD): flags and, since 8.1, the satellite FileDataIDs.
Definition root.hpp:88
The WDT entity (namespace wowlib::formats::wdt): a map description — the main .wdt file plus its era'...