15 std::filesystem::path resolveDataDir(
const std::filesystem::path& clientPath) {
17 if (std::filesystem::is_directory(clientPath /
"Data", ec))
return clientPath /
"Data";
18 for (
const auto& entry : std::filesystem::directory_iterator{clientPath, ec}) {
19 const std::string name = entry.path().filename().string();
20 if (name.size() == 4 && entry.is_directory(ec) && std::tolower(
static_cast<unsigned char>(name[0])) ==
'd' &&
21 std::tolower(
static_cast<unsigned char>(name[1])) ==
'a' && std::tolower(
static_cast<unsigned char>(name[2]))
22 ==
't' && std::tolower(
static_cast<unsigned char>(name[3])) ==
'a')
return entry.path();
24 return clientPath /
"Data";
34 if (!install)
return std::unexpected(install.error());
37 .clientPath = std::move(install->path),
38 .version = install->version,
43 .cascProduct = std::move(install->cascProduct)
48 std::optional<ProjectDirectory> project;
51 if (!opened)
return std::unexpected(opened.error());
52 project = std::move(*opened);
57 .dataDir = resolveDataDir(settings.
clientPath),
61 if (!storage)
return std::unexpected(storage.error());
71 if (!loaded)
return std::unexpected(loaded.error());
72 listfile = std::move(*loaded);
77 .product = settings.
cascProduct.value_or(std::string{settings.
version.defaultCascProduct()}),
81 if (!storage)
return std::unexpected(storage.error());
89 std::unexpected<Error> closedError() {
93 template <
typename T>
concept IsComposition = !std::is_same_v<std::remove_cvref_t<T>, std::monostate>;
98 if constexpr (IsComposition<
decltype(
fs)>)
return fs.readFile(key);
99 else return closedError();
104 return std::visit([&](
auto&
fs) {
105 if constexpr (IsComposition<
decltype(
fs)>)
return fs.exists(key);
111 if (
auto* mpqFs = std::get_if<MpqFileSystem>(&_impl))
return mpqFs->backend().enumeratePaths();
113 if (
auto* cascFs = std::get_if<CascFileSystem>(&_impl)) {
117 auto fdids = cascFs->backend().enumerateFdids();
118 if (!fdids)
return std::unexpected(fdids.error());
120 std::vector<std::string> paths;
121 paths.reserve(fdids->size());
123 if (
auto path = cascFs->listfile().fdidToPath(fdid)) paths.push_back(std::move(*path));
124 std::ranges::sort(paths);
125 const auto duplicates = std::ranges::unique(paths);
126 paths.erase(duplicates.begin(), duplicates.end());
130 return closedError();
134 return std::visit([&](
const auto&
fs) ->
FileKey {
135 if constexpr (IsComposition<
decltype(
fs)>)
return fs.resolve(key);
142 if constexpr (IsComposition<
decltype(
fs)>)
return fs.addFile(path, content);
143 else return closedError();
static Result< CascStorage > open(Options options)
Open the local storage.
The static composition of one client's file access: storage backend + listfile database + optional pr...
The CSV-backed path<->FileDataID database ('fileDataId;filepath' per line, the community listfile for...
static Result< CsvListfile > load(const std::filesystem::path &csv, Options options={})
Load a listfile CSV; the file becomes the working database that registerPath appends to.
FileKey resolve(const FileKey &key) const
FileSystem(MpqFileSystem impl, ClientVersion version)
C++-only; target languages construct through open().
static Result< FileSystem > open(FileSystemSettings settings)
Result< FileBuffer > readFile(const FileKey &key)
Result< std::vector< std::string > > enumeratePaths()
bool exists(const FileKey &key)
Whether a file is reachable in the overlay or the storage.
Result< FileDataID > addFile(std::string_view path, std::span< const std::byte > content)
static Result< MpqStorage > open(Options options)
Expand the version's chain and open every archive present on disk.
static Result< ProjectDirectory > open(std::filesystem::path root)
Open (and index) a project directory; it is created if missing.
The runtime facade over the static compositions — the primary welder binding surface of the fs layer.
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.
@ StorageNotOpen
Operation on a closed or moved-from storage.
@ Mpq
Pre-WoD retail clients (< 6.0), StormLib.
std::uint32_t build
Exact client build number, e.g.
static Result< ClientInstall > detect(std::filesystem::path clientPath)
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/).
The no-database provider for clients that need none (MPQ-era).