15#include <welder/vocabulary.hpp>
41 Everything needed to open a client filesystem: where the client is, which
42 version it is, and the optional listfile / project directory / locale
43 configuration. An immutable value, fully described at construction — a
44 settings object can never be half-edited into an inconsistent state. In
45 C++ it stays an aggregate (designated initializers; const members); the
46 scripting languages construct through the synthesized field constructor,
47 where everything after the version is optional.)")
50 "The client installation root (the directory containing Data/).")]]
54 "The client version; selects the storage backend and MPQ chain.")]]
58 Client locale. For MPQ clients its Data/{code}/ directory must exist; for
59 CASC clients it is the locale mask for content selection.)")]]
63 The project-directory overlay: the ultimate patch, where new files are
65 const std::optional<std::filesystem::path> projectDirectory{};
68 The working listfile CSV ('fileDataId;filepath'): read for path<->FileDataID
69 resolution and appended to when new files are added. Effectively mandatory
70 for modern CASC clients — without it files can only be requested by
72 const std::optional<std::filesystem::path> listfileCsv{};
75 First FileDataID handed to files added to the project; keep far above
76 Blizzard's ~6-7M so future official content never collides.)")]]
80 TACT product code for CASC storages ('wow', 'wow_classic_era', 'wowt',
81 ...). Left unset it is derived from the version's flavor
82 (ClientVersion.default_casc_product), which is right for every ordinary
83 install; set it explicitly for PTR, beta and one-off products
84 ('wow_classic_titan'), or let ClientInstall.detect read the exact code
85 off the installation.)")]]
86 const std::optional<std::string> cascProduct{};
89 Settings for a client whose version is read off the installation itself
90 (see ClientInstall.detect) instead of being spelled out — the reliable
91 way to open anything Classic, where the build number decides which
92 engine's file formats you get. The remaining arguments are the same
93 optional configuration the constructor takes.
97 settings = FileSystemSettings.detect("/Games/World of Warcraft/_classic_era_",
98 listfile_csv="listfile.csv")
99 with FileSystem.open(settings) as fs:
100 ... # formats resolve to the engine the install really is
102 =welder::returns("the settings, or the error ClientInstall.detect raised")
105 std::filesystem::path clientPath [[=welder::doc(
"the installation directory holding Data/")]],
107 std::optional<std::filesystem::path> projectDirectory [[=welder::doc(
"the project-directory overlay")]] = {},
108 std::optional<std::filesystem::path> listfileCsv [[=welder::doc(
"the working listfile CSV")]] = {},
114 =welder::policy::weld_protected,
116 The runtime gateway to one client's files. Picks the storage backend from
117 the client version and hides the static composition behind one bindable
118 type; C++ code that wants zero dispatch overhead can use
119 MpqFileSystem/CascFileSystem directly.
121 Lifetime: open() returns an open filesystem and destruction releases it —
122 in C++ that is the whole story (RAII; no public close, so no reachable
123 wrong state beyond a moved-from object). The scripting languages, where
124 finalization timing belongs to the runtime, additionally get explicit
125 control: close() and is_open (welded from the protected surface), and in
126 Python the with-statement.
129 Preferred: scope the storage with a `with` block — `open()` returns
130 the filesystem, `__enter__` hands it to the block, and `__exit__`
131 calls `close()` on the way out, exception or not:
134 from wowlib import versions
135 from wowlib.fs import FileSystem, FileSystemSettings
137 settings = FileSystemSettings("/Games/WoW 3.3.5a", versions.wotlk)
138 with FileSystem.open(settings) as fs:
139 data = fs.read_file("Interface/FrameXML/UIParent.lua")
142 Equivalent explicit form, when the lifetime cannot be a single block
143 (a viewer holding the storage across UI events):
146 fs = FileSystem.open(settings)
148 data = fs.read_file("Interface/FrameXML/UIParent.lua")
150 fs.close() # storage released now, not at GC time
155 Initialize a client filesystem: open the storage (the full MPQ chain or
156 the CASC storage), load the listfile if given, and attach the
157 project-directory overlay.)"),
158 =welder::returns("the opened filesystem")]]
162 Read a file by FileKey — the generic identity for version-independent
163 code: whichever half the key carries (path, FileDataID or both), the
164 backend uses what it can address and the listfile fills the gap.)"),
165 =welder::returns("the file bytes")]]
167 const FileKey& key [[=welder::doc(
"the file identity (path, id, or both)")
170 [[=welder::doc(
"Read a file by client-internal path."),
171 =welder::returns(
"the file bytes")]]
176 [[=welder::doc(
"Read a file by FileDataID."),
177 =welder::returns(
"the file bytes")]]
182 [[=welder::doc(
"Whether a file is reachable in the overlay or the storage.")
184 =welder::returns(
"true if a read would find it")]]
186 const FileKey& key [[=welder::doc(
"the file identity (path, id, or both)")
189 [[=welder::doc(
"Whether a path is reachable."),
190 =welder::returns(
"true if a read would find it")]]
191 bool exists(std::string_view path [[=welder::doc(
"the client-internal file path")]]) {
195 [[=welder::doc(
"Whether a FileDataID is reachable."),
196 =welder::returns(
"true if a read would find it")]]
197 bool exists(
FileDataID fdid [[=welder::doc(
"the numeric file identifier")]]) {
202 Enumerate every client-internal file path reachable in the storage:
203 the union of the MPQ chain's archive listings (loose directories
204 included), or — on CASC clients — every FileDataID the storage holds
205 that the loaded listfile can name (unnamed ids are skipped, and
206 without a listfile the listing is empty). Paths come back canonical,
207 deduplicated and sorted; the project-directory overlay is not
209 =welder::returns("the sorted canonical paths")]]
213 Fill the missing half of a key (path or FileDataID) from the listfile,
214 best-effort: present halves are preserved, unknown halves stay empty
215 (on MPQ-era clients there is no FileDataID space at all). Lets generic
216 tools learn a file's full identity without storage-specific code.)"),
217 =welder::returns("the completed key")]]
218 FileKey resolve(
const FileKey& key [[=welder::doc(
"the file identity to complete")]])
const;
221 Add (or overwrite) a file in the project directory; on CASC-era clients
222 new paths get a custom FileDataID, persisted in the working listfile.)")
224 =welder::returns("the file's FileDataID (0 on MPQ-era clients)")]]
226 std::string_view path [[=welder::doc(
227 "the client-internal path of the file")]],
228 std::span<const std::byte> content [[=welder::doc(
"the file contents")]]);
237 "Register TACT encryption keys (CASC only) from the community "
238 "'KeyName KeyHex' per-line text, so encrypted .db2 sections "
239 "decrypt and decode."),
240 =welder::returns(
"nothing; raises on an MPQ client or a malformed list")]]
241 Result<void> importKeys(std::string_view keyList [[=welder::doc(
"newline-separated 'KeyName KeyHex' lines")]]) {
242 if (
auto* casc = std::get_if<CascFileSystem>(&_impl))
return casc->backend().importKeys(keyList);
251 [[=welder::mark::exclude]]
252 Result<void> addEncryptionKey(std::uint64_t keyName, std::span<const std::byte, 16> key) {
253 if (
auto* casc = std::get_if<CascFileSystem>(&_impl))
return casc->backend().addEncryptionKey(keyName, key);
259 "Which storage technology backs this filesystem: Mpq or Casc. "
260 "A static fact of the opened client; remains valid after close().")]]
264 =welder::doc(
"The client version this filesystem was opened for — the "
265 "anchor of version-agnostic code (expansion_of, for_version, "
266 "Table.open). A static fact of the opened client; remains "
267 "valid after close().")]]
268 ClientVersion version()
const {
return _version; }
272 [[=welder::mark::exclude]]
273 MpqFileSystem* mpq() {
return std::get_if<MpqFileSystem>(&_impl); }
277 [[=welder::mark::exclude]]
278 CascFileSystem* casc() {
return std::get_if<CascFileSystem>(&_impl); }
283 [[=welder::mark::exclude]]
285 : _impl(std::move(impl)), _kind(
StorageKind::
Mpq), _version(version) {}
290 [[=welder::mark::exclude]]
292 : _impl(std::move(impl)), _kind(
StorageKind::
Casc), _version(version) {}
299 Release the client storage now — every MPQ archive handle or the CASC
300 storage handle — instead of waiting for garbage collection. Safe to call
301 repeatedly; afterwards reads and writes raise StorageNotOpen. In Python,
302 prefer the with-statement, which calls this on block exit.)")]]
303 void close() { _impl = std::monostate{}; }
307 "Whether the filesystem still holds its storage (false after "
309 bool isOpen()
const {
310 return !std::holds_alternative<std::monostate>(_impl);
316 std::variant<std::monostate, MpqFileSystem, CascFileSystem> _impl;
318 ClientVersion _version{};
The owning byte buffer file contents are read into.
The CascLib-backed storage for CASC-era clients.
The static composition of one client's file access: storage backend + listfile database + optional pr...
FileKey resolve(const FileKey &key) const
Result< FileBuffer > readFile(std::string_view path)
Read a file by client-internal path.
FileSystem(MpqFileSystem impl, ClientVersion version)
C++-only; target languages construct through open().
bool exists(std::string_view path)
Whether a path is reachable.
static Result< FileSystem > open(FileSystemSettings settings)
ClientVersion version() const
The client version this filesystem was opened for — the anchor of version-agnostic code (expansion_of...
Result< FileBuffer > readFile(const FileKey &key)
MpqFileSystem * mpq()
The MPQ composition, for C++ callers that want the static types.
Result< FileBuffer > readFile(FileDataID fdid)
Read a file by FileDataID.
bool exists(FileDataID fdid)
Whether a FileDataID is reachable.
CascFileSystem * casc()
The CASC composition, for C++ callers that want the static types.
Result< std::vector< std::string > > enumeratePaths()
StorageKind kind() const
Which storage technology backs this filesystem: Mpq or Casc.
bool exists(const FileKey &key)
Whether a file is reachable in the overlay or the storage.
Result< void > addEncryptionKey(std::uint64_t keyName, std::span< const std::byte, 16 > key)
Register one TACT encryption key (CASC clients only).
Result< FileDataID > addFile(std::string_view path, std::span< const std::byte > content)
Result< void > importKeys(std::string_view keyList)
Register TACT encryption keys (CASC clients only) so the storage can decrypt content behind them — in...
bool isOpen() const
Whether the filesystem still holds its storage (false after close()).
FileSystem(CascFileSystem impl, ClientVersion version)
C++-only; target languages construct through open().
The static composition of one client's file access.
Reading a client installation's own identity off disk: which product it is, which build,...
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
The CSV-backed listfile provider: one working file ('fileDataId;filepath' per line) that is both read...
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 StormLib-backed storage for MPQ-era clients.
constexpr FileDataID DefaultCustomFdidStart
The first FileDataID handed to project files by default — far above Blizzard's ~6-7M so future offici...
ClientFileSystem< CascStorage, CsvListfile > CascFileSystem
The concrete composition for CASC-era clients (listfile-resolved FileDataIDs).
ClientFileSystem< MpqStorage, NullListfile > MpqFileSystem
The concrete composition for MPQ-era clients (path-addressed, no listfile).
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
@ NotSupported
Operation not supported by this backend/provider.
@ Mpq
Pre-WoD retail clients (< 6.0), StormLib.
@ Casc
WoD+ retail and all Classic clients, CascLib.
const ClientVersion version
The client version; selects the storage backend and MPQ chain.
static Result< FileSystemSettings > detect(std::filesystem::path clientPath, Locale locale=Locale::enUS, std::optional< std::filesystem::path > projectDirectory={}, std::optional< std::filesystem::path > listfileCsv={}, FileDataID customFdidStart=DefaultCustomFdidStart)
const FileDataID customFdidStart
const std::optional< std::filesystem::path > listfileCsv
const std::optional< std::string > cascProduct
const std::optional< std::filesystem::path > projectDirectory
const std::filesystem::path clientPath
The client installation root (the directory containing Data/).