wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
client_version.hpp
Go to the documentation of this file.
1#pragma once
2
8
9#include <array>
10#include <compare>
11#include <cstdint>
12#include <iosfwd>
13#include <optional>
14#include <string_view>
15#include <utility>
16
17#include <welder/vocabulary.hpp>
18
19namespace wowlib {
20 enum class [[
21 =welder::weld,
22 =welder::doc(R"(
23 Which storage technology a client generation uses: Mpq for pre-WoD retail
24 clients (StormLib), Casc for WoD+ and every Classic client (CascLib).)")
28 };
30 enum class [[
31 =welder::weld,
32 =welder::doc(R"(
33 Which product line a client belongs to. Retail is the ordinary
34 progression client, where the version number also tells you the engine
35 generation. Every other flavor is a MODERN-engine client wearing a
36 legacy version number: WoW Classic Era 1.15.9 is a Midnight-era client,
37 Cataclysm Classic 4.4.2 a War Within-era one. Their file formats follow
38 the build number, not the version tuple — see
39 ClientVersion.format_lineage.
40
41 The enumerator also names the client's default TACT product code
42 ('wow', 'wow_classic', 'wow_classic_era', 'wow_anniversary'); products
43 outside these four (PTR, beta, 'wow_classic_titan') pick the closest
44 flavor and pass their exact code to the filesystem explicitly.)")
45 ]] ClientFlavor {
46 Retail,
47 Classic,
51 };
52
53 struct [[
54 =welder::weld,
55 =welder::doc(R"(
56 A full client version tuple (major.minor.patch, build) plus the product
57 flavor it came from. Determines which storage backend and archive chain
58 a client uses, and — through format_lineage — which engine generation
59 its files are laid out for. The versions namespace provides constants
60 for the releases wowlib targets.
61
62 The version tuple alone does NOT identify an engine: the Classic
63 products reuse legacy version numbers on top of whatever retail branch
64 was current when they were built (Classic 3.4.x spans three retail
65 generations; 'wow_classic_titan' calls itself 3.80). The BUILD number
66 does — Blizzard's build counter is global across every product — so
67 every format decision keys on format_lineage, never on major/minor.)")
68 ]] ClientVersion {
69 [[=welder::doc("Expansion number, e.g. 3 for Wrath of the Lich King.")]]
70 std::uint16_t major = 0;
71
72 [[=welder::doc("Minor version within the expansion.")]]
73 std::uint16_t minor = 0;
74
75 [[=welder::doc("Patch version within the minor release.")]]
76 std::uint16_t patch = 0;
77
78 [[=welder::doc(
79 "Exact client build number, e.g. 12340 for 3.3.5a. Unique and "
80 "monotonic across ALL products, which is what makes it the "
81 "only reliable engine-generation key.")]]
82 std::uint32_t build = 0;
83
84 [[=welder::doc("Which product line this client is — Retail unless stated.")]
85 ]
87
88 [[=welder::getter,
89 =welder::doc("Whether this is a Classic-family client: a modern engine "
90 "wearing a legacy version number.")]]
91 constexpr bool isClassic() const { return flavor != ClientFlavor::Retail; }
92
93 [[=welder::getter,
94 =welder::doc("Which storage technology this client uses: Mpq for pre-WoD "
95 "(< 6.0) retail clients, Casc for everything else — every "
96 "Classic client is CASC no matter what its version says.")]]
97 constexpr StorageKind storageKind() const {
98 return !isClassic() && major < 6 ? StorageKind::Mpq : StorageKind::Casc;
99 }
100
101 [[=welder::getter,
102 =welder::doc(R"(
103 The RETAIL release whose file formats this client's files follow — the
104 version every format decision is actually made against.
105
106 For a retail client this is the version itself. For a Classic client
107 it is the retail branch that was the live client when this build was
108 produced, looked up by build number: Classic branches fork off the
109 current retail engine, so Cataclysm Classic 4.4.2 (build 60895) lays
110 its files out like The War Within, not like Cataclysm. Builds newer
111 than the newest release wowlib models clamp to it.)")]]
112 constexpr ClientVersion formatLineage() const;
113
114 [[=welder::getter,
115 =welder::doc(
116 "The TACT product code this flavor installs under by default "
117 "('wow', 'wow_classic', 'wow_classic_era', "
118 "'wow_anniversary'). PTR, beta and one-off products carry "
119 "their own code — pass it to the filesystem explicitly.")]]
120 constexpr std::string_view defaultCascProduct() const {
121 switch (flavor) {
122 case ClientFlavor::Retail: return "wow";
123 case ClientFlavor::Classic: return "wow_classic";
124 case ClientFlavor::ClassicEra: return "wow_classic_era";
125 case ClientFlavor::Anniversary: return "wow_anniversary";
127 std::unreachable();
128 }
129
130 constexpr auto operator<=>(const ClientVersion&) const = default;
131 };
132
133 namespace
134 [[=welder::doc(R"(
135 The releases wowlib targets: the last-minor-of-major of every finished
136 expansion (Midnight 12.x is ongoing and has no final build yet), plus the
137 newest build of each living Classic product line. Builds verified against
138 wago.tools.
139
140 The Classic constants are snapshots of a MOVING target — those products
141 ship new builds continuously, and a newer build can land on a newer engine
142 (Classic 4.4.0 is Dragonflight-era, 4.4.1 already War Within-era). Pin the
143 exact build you have, or let ClientInstall.detect read it off the install.)")
144 ]]
145 versions {
146 [[=welder::weld,
147 =welder::doc("Vanilla 1.12.1 (build 5875).")]]
148 inline constexpr ClientVersion Vanilla{1, 12, 1, 5875};
149
150 [[=welder::weld,
151 =welder::doc("The Burning Crusade 2.4.3 (build 8606).")]]
152 inline constexpr ClientVersion Tbc{2, 4, 3, 8606};
153
154 [[=welder::weld,
155 =welder::doc("Wrath of the Lich King 3.3.5a (build 12340).")]]
156 inline constexpr ClientVersion Wotlk{3, 3, 5, 12340};
157
158 [[=welder::weld,
159 =welder::doc("Cataclysm 4.3.4 (build 15595).")]]
160 inline constexpr ClientVersion Cata{4, 3, 4, 15595};
162 [[=welder::weld,
163 =welder::doc("Mists of Pandaria 5.4.8 (build 18414).")]]
164 inline constexpr ClientVersion Mop{5, 4, 8, 18414};
165
166 [[=welder::weld,
167 =welder::doc("Warlords of Draenor 6.2.4 (build 21742).")]]
168 inline constexpr ClientVersion Wod{6, 2, 4, 21742};
169
170 [[=welder::weld,
171 =welder::doc("Legion 7.3.5 (build 26972).")]]
172 inline constexpr ClientVersion Legion{7, 3, 5, 26972};
173
174 [[=welder::weld,
175 =welder::doc("Battle for Azeroth 8.3.7 (build 35662).")]]
176 inline constexpr ClientVersion Bfa{8, 3, 7, 35662};
177
178 [[=welder::weld,
179 =welder::doc("Shadowlands 9.2.7 (build 45745).")]]
180 inline constexpr ClientVersion Shadowlands{9, 2, 7, 45745};
181
182 [[=welder::weld,
183 =welder::doc("Dragonflight 10.2.7 (build 55664).")]]
184 inline constexpr ClientVersion Dragonflight{10, 2, 7, 55664};
185
186 [[=welder::weld,
187 =welder::doc("The War Within 11.2.7 (build 65299).")]]
188 inline constexpr ClientVersion Tww{11, 2, 7, 65299};
189
190 // --- Classic: legacy version numbers on modern engines -------------------
191 //
192 // Each is the newest build of its product line as of 2026-08-18. The
193 // formatLineage each maps onto is noted; it follows the build, so bumping
194 // a constant can legitimately move it to a newer engine.
195
196 [[=welder::weld,
197 =welder::doc("Classic Era 1.15.9 (build 69109) — a Midnight-era client "
198 "('wow_classic_era').")]]
199 inline constexpr ClientVersion ClassicEra{
200 1,
201 15,
202 9,
203 69109,
205 };
206
207 [[=welder::weld,
208 =welder::doc("Burning Crusade Classic 2.5.4 (build 44833) — a "
209 "Shadowlands-era client ('wow_classic').")]]
210 inline constexpr ClientVersion ClassicBcc{
211 2,
212 5,
213 4,
214 44833,
216 };
218 [[=welder::weld,
219 =welder::doc("Wrath of the Lich King Classic 3.4.5 (build 63697) — a War "
220 "Within-era client ('wow_classic').")]]
221 inline constexpr ClientVersion ClassicWotlk{
222 3,
223 4,
224 5,
225 63697,
227 };
228
229 [[=welder::weld,
230 =welder::doc("Cataclysm Classic 4.4.2 (build 60895) — a War Within-era "
231 "client ('wow_classic').")]]
232 inline constexpr ClientVersion ClassicCata{
233 4,
234 4,
235 2,
236 60895,
238 };
239
240 [[=welder::weld,
241 =welder::doc("Mists of Pandaria Classic 5.5.4 (build 69155) — a "
242 "Midnight-era client ('wow_classic').")]]
243 inline constexpr ClientVersion ClassicMop{
244 5,
245 5,
246 4,
247 69155,
249 };
250
251 [[=welder::weld,
252 =welder::doc(
253 "The Anniversary realms 2.5.6 (build 69110) — a Midnight-era "
254 "client ('wow_anniversary').")]]
255 inline constexpr ClientVersion Anniversary{
256 2,
257 5,
258 6,
259 69110,
261 };
262 }
263
264 namespace detail {
269 struct EngineGeneration {
270 std::uint32_t firstBuild;
271 ClientVersion release;
272 };
273
280 clamp onto The War Within. Adding a Midnight row here and to the format
281 grids fixes retail and Classic in one move. */
282 inline constexpr std::array EngineTimeline{
283 EngineGeneration{19034, versions::Wod},
284 // 6.0.2 prepatch
285 EngineGeneration{22248, versions::Legion},
286 // 7.0.3 prepatch
287 EngineGeneration{26926, versions::Bfa},
288 // 8.0.1
289 EngineGeneration{35917, versions::Shadowlands},
290 // 9.0.1
291 EngineGeneration{46181, versions::Dragonflight},
292 // 10.0.0 prepatch
293 EngineGeneration{55666, versions::Tww},
294 // 11.0.0 prepatch
295 };
298 constexpr ClientVersion ClientVersion::formatLineage() const {
299 if (!isClassic())
300 return *this;
301
302 ClientVersion out = detail::EngineTimeline.front().release;
303 for (const detail::EngineGeneration& generation : detail::EngineTimeline)
304 if (generation.firstBuild <= build)
305 out = generation.release;
306 return out;
307 }
308
316 std::ostream& operator<<(std::ostream& out, const ClientVersion& version);
317
318 enum class [[
319 =welder::weld,
320 =welder::doc(
321 "Game client locale; enumerators use the client's own four-letter "
322 "codes.")
323 ]] Locale {
324 enUS,
325 enGB,
326 deDE,
327 frFR,
328 ruRU,
329 esES,
330 esMX,
331 koKR,
332 zhCN,
333 zhTW,
334 ptBR,
335 itIT
336 };
337
342 std::string_view localeCode(Locale locale);
343
347 std::optional<Locale> localeFromCode(std::string_view code);
348
353 std::uint32_t cascLocaleFlag(Locale locale);
354}
constexpr std::array EngineTimeline
The retail engine timeline a Classic build is placed on.
constexpr ClientVersion ClassicBcc
constexpr ClientVersion Shadowlands
constexpr ClientVersion Tww
constexpr ClientVersion Bfa
constexpr ClientVersion Mop
constexpr ClientVersion Wod
constexpr ClientVersion Tbc
constexpr ClientVersion Vanilla
constexpr ClientVersion ClassicEra
constexpr ClientVersion Legion
constexpr ClientVersion ClassicCata
constexpr ClientVersion Dragonflight
constexpr ClientVersion Anniversary
constexpr ClientVersion Cata
constexpr ClientVersion ClassicWotlk
constexpr ClientVersion Wotlk
constexpr ClientVersion ClassicMop
std::string_view localeCode(Locale locale)
The four-letter code of locale ("enUS", ...) as used in MPQ locale directory and archive names.
@ Retail
The progression client ('wow').
@ Anniversary
The Anniversary realms ('wow_anniversary').
@ ClassicEra
Classic Era ('wow_classic_era'): the 1.13-1.15 vanilla realms.
@ Classic
The Classic progression line ('wow_classic'): BCC 2.5, WotLK 3.4, Cata 4.4, MoP 5....
std::optional< Locale > localeFromCode(std::string_view code)
Parse a four-letter locale code.
std::uint32_t cascLocaleFlag(Locale locale)
The CASC_LOCALE_* bit of locale for CascOpenStorage/CascOpenFile locale masks.
std::ostream & operator<<(std::ostream &out, const ClientVersion &version)
render version as "major.minor.patch.build", with the flavor appended when it is not Retail ("1....
@ Mpq
Pre-WoD retail clients (< 6.0), StormLib.
@ Casc
WoD+ retail and all Classic clients, CascLib.
std::uint16_t patch
Patch version within the minor release.
std::uint32_t build
Exact client build number, e.g.
std::uint16_t major
Expansion number, e.g.
std::uint16_t minor
Minor version within the expansion.
One retail engine generation on the global build counter: the build at which that major became the li...
ClientVersion release
The versions constant modelling it.
std::uint32_t firstBuild
First live/PTR build of the retail major.