-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcasc_bindings.cpp
More file actions
219 lines (188 loc) · 17.8 KB
/
Copy pathcasc_bindings.cpp
File metadata and controls
219 lines (188 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow
//
// AUTOGENERATED by tools/codegen — do not edit by hand.
// Regenerate via: python -m tools.codegen.codegen casc --backend pybind11
//
// Source headers and `@bind` annotations live under include/whiteout/.
//
// Include order matters here:
// 1. pybind11/pybind11.h sets up the base library
// 2. project headers define whiteout::u32 et al used inside MAKE_OPAQUE
// 3. PYBIND11_MAKE_OPAQUE opts out of stl.h's auto-conversion for our vectors
// 4. pybind11/stl.h, stl_bind.h honor the opaque declarations
#include <pybind11/pybind11.h>
#include <array>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include <whiteout/storages/casc/types.h>
#include <whiteout/storages/casc/storage.h>
#include <whiteout/storages/casc/storage_writable.h>
#include <whiteout/interfaces.h>
PYBIND11_MAKE_OPAQUE(std::vector<std::string>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::u8>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::storages::casc::FindEntry>);
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/operators.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
void bind_casc(py::module_& m) {
py::enum_<whiteout::storages::casc::RootFormat>(m, "RootFormat", R"doc(Root manifest format.)doc")
.value("UNKNOWN", whiteout::storages::casc::RootFormat::Unknown, R"doc(Could not determine format.)doc")
.value("WOW", whiteout::storages::casc::RootFormat::Wow, R"doc(World of Warcraft root (FileDataId-based, legacy MFST).)doc")
.value("WOW_TVFS", whiteout::storages::casc::RootFormat::WowTvfs, R"doc(World of Warcraft root (FileDataId-based, TVFS-backed, 11.x+).)doc")
.value("DIABLO3", whiteout::storages::casc::RootFormat::Diablo3, R"doc(Diablo III root (hierarchical directory).)doc")
.value("DIABLO4", whiteout::storages::casc::RootFormat::Diablo4, R"doc(Diablo IV root (TVFS enriched with CoreTOC paths).)doc")
.value("TVFS", whiteout::storages::casc::RootFormat::Tvfs, R"doc(TVFS prefix-tree root (WC3 Reforged and general purpose).)doc")
.value("MNDX", whiteout::storages::casc::RootFormat::Mndx, R"doc(MNDX trie-based root (StarCraft II, Heroes of the Storm).)doc")
.value("OVERWATCH", whiteout::storages::casc::RootFormat::Overwatch, R"doc(Overwatch root (text manifest + CMF content manifests).)doc")
.value("AGENT", whiteout::storages::casc::RootFormat::Agent, R"doc(Agent/S1 text root (SC:R, Hearthstone, etc.).)doc")
;
py::enum_<whiteout::storages::casc::FileIdHint>(m, "FileIdHint", R"doc(Hint for disambiguating FileDataId-based lookups. In Diablo IV, a single SNO ID can map to multiple entries (child, meta, payload, etc.). The hint tells the root which variant to return. Roots that don't use sub-types (e.g. WoW) ignore the hint.)doc")
.value("NONE", whiteout::storages::casc::FileIdHint::None, R"doc(Default — return the primary entry (child/main content).)doc")
.value("META", whiteout::storages::casc::FileIdHint::Meta, R"doc(Metadata entry.)doc")
.value("PAYLOAD", whiteout::storages::casc::FileIdHint::Payload, R"doc(Full-resolution payload.)doc")
.value("PAYLOW", whiteout::storages::casc::FileIdHint::Paylow, R"doc(Low-resolution payload.)doc")
.value("PAYMED", whiteout::storages::casc::FileIdHint::Paymed, R"doc(Medium-resolution payload.)doc")
;
py::class_<whiteout::storages::casc::FindEntry>(m, "FindEntry", R"doc(Entry returned by enumerate/list operations.)doc")
.def(py::init<>())
.def_readwrite("file_size", &whiteout::storages::casc::FindEntry::fileSize)
.def_readwrite("locale_flags", &whiteout::storages::casc::FindEntry::localeFlags)
.def_readwrite("content_flags", &whiteout::storages::casc::FindEntry::contentFlags)
.def_readwrite("file_data_id", &whiteout::storages::casc::FindEntry::fileDataId)
.def_readwrite("path", &whiteout::storages::casc::FindEntry::path)
.def("get_c_key",
[](const whiteout::storages::casc::FindEntry& self) {
return std::vector<whiteout::u8>(self.cKey.begin(), self.cKey.end());
})
.def("set_c_key",
[](whiteout::storages::casc::FindEntry& self, const std::vector<whiteout::u8>& v) {
if (v.size() != self.cKey.size())
throw std::runtime_error("setter expected exactly "
+ std::to_string(self.cKey.size()) + " elements");
for (std::size_t i = 0; i < v.size(); ++i) self.cKey[i] = v[i];
})
;
py::class_<whiteout::storages::casc::CreateOptions>(m, "CreateOptions", R"doc(Options for creating a new empty CASC storage.)doc")
.def(py::init<>())
.def_readwrite("product", &whiteout::storages::casc::CreateOptions::product)
.def_readwrite("version", &whiteout::storages::casc::CreateOptions::version)
.def_readwrite("archive_max_size", &whiteout::storages::casc::CreateOptions::archiveMaxSize, R"doc(1 GB.)doc")
.def_readwrite("blte_frame_size", &whiteout::storages::casc::CreateOptions::blteFrameSize, R"doc(64 KB.)doc")
.def_readwrite("root_format", &whiteout::storages::casc::CreateOptions::rootFormat)
;
py::class_<whiteout::storages::casc::WriteOptions>(m, "WriteOptions", R"doc(Options for writing a file into a CASC storage.)doc")
.def(py::init<>())
.def(py::init([](whiteout::u32 locale_flags, whiteout::u32 content_flags, bool compress) {
return whiteout::storages::casc::WriteOptions{locale_flags, content_flags, compress};
}), py::arg("locale_flags"), py::arg("content_flags"), py::arg("compress"))
.def("__repr__", [](const whiteout::storages::casc::WriteOptions& self) {
std::ostringstream oss;
oss << "WriteOptions(";
oss << "locale_flags=" << self.localeFlags << ", ";
oss << "content_flags=" << self.contentFlags << ", ";
oss << "compress=" << self.compress << ")";
return oss.str();
})
.def_readwrite("locale_flags", &whiteout::storages::casc::WriteOptions::localeFlags)
.def_readwrite("content_flags", &whiteout::storages::casc::WriteOptions::contentFlags)
.def_readwrite("compress", &whiteout::storages::casc::WriteOptions::compress)
;
py::class_<whiteout::storages::casc::Storage>(m, "Storage", R"doc(Unified read-only CASC storage (local disk or CDN)
Storage is the primary entry point for reading CASC archives. Use `open()` for local disk, `openOnline()` for CDN-backed access. The same public read API works identically regardless of backing store.
All public methods are thread-safe: read operations acquire a shared lock.
Uses the PImpl (Pointer to Implementation) idiom to hide internals.
@see StorageWritable for write + persist operations.)doc")
.def_static("open",
[](const std::string& path, whiteout::interfaces::WorkerPool* pool) {
return whiteout::storages::casc::Storage::open(path, pool);
}, py::arg("path"), py::arg("pool") = nullptr, R"doc(Open an existing local CASC storage. @param path Path to the game's top-level directory (containing .build.info) or its Data subdirectory. @param pool Optional WorkerPool for parallel I/O (non-owning). @return A valid Storage, or std::nullopt on failure.)doc")
.def_static("open",
[](const std::string& path, whiteout::u32 localeMask, whiteout::interfaces::WorkerPool* pool) {
return whiteout::storages::casc::Storage::open(path, localeMask, pool);
}, py::arg("path"), py::arg("localeMask"), py::arg("pool") = nullptr, R"doc(@overload Open with locale mask.)doc")
.def_static("open",
[](const std::string& path, const std::string& product, whiteout::interfaces::WorkerPool* pool) {
return whiteout::storages::casc::Storage::open(path, product, pool);
}, py::arg("path"), py::arg("product"), py::arg("pool") = nullptr, R"doc(@overload Open a specific product from a multi-product `.build.info`. @param product Product code selecting the build, e.g. "w3" (Warcraft III retail) vs "w3t" (its PTR). Matched case-insensitively against the active builds; empty selects the first active build. See OpenOptions::product. Open fails if the product has no active build.)doc")
.def_static("open",
[](const whiteout::storages::casc::OpenOptions& opts) {
return whiteout::storages::casc::Storage::open(opts);
}, py::arg("opts"), R"doc(@overload Open with full options. OpenOptions has unsupported field shapes (std::span, std::function). Use the (path, localeMask, pool) overloads for now.)doc")
.def("close", &whiteout::storages::casc::Storage::close, R"doc(Release all resources and invalidate the storage.)doc")
.def("is_local", &whiteout::storages::casc::Storage::isLocal, R"doc(@return True if this storage reads from local disk.)doc")
.def("is_online", &whiteout::storages::casc::Storage::isOnline, R"doc(@return True if this storage reads from CDN.)doc")
.def("is_writable", &whiteout::storages::casc::Storage::isWritable, R"doc(@return True if this storage has a write overlay (StorageWritable).)doc")
.def("root_format", &whiteout::storages::casc::Storage::rootFormat, R"doc(@return The root manifest format, or RootFormat::Unknown.)doc")
.def("read_file",
[](whiteout::storages::casc::Storage& self, const std::string& cascPath) {
auto __r = self.readFile(cascPath);
if (!__r) return py::object(py::none());
return py::object(py::bytes(
reinterpret_cast<const char*>(__r->data()), __r->size()));
}, py::arg("cascPath"), R"doc(@return File contents, or std::nullopt if the path is not found.)doc")
.def("read_file",
[](whiteout::storages::casc::Storage& self, const std::string& cascPath, whiteout::u32 localeFlags, whiteout::u32 openFlags) {
auto __r = self.readFile(cascPath, localeFlags, openFlags);
if (!__r) return py::object(py::none());
return py::object(py::bytes(
reinterpret_cast<const char*>(__r->data()), __r->size()));
}, py::arg("cascPath"), py::arg("localeFlags"), py::arg("openFlags") = whiteout::u32{}, R"doc(@overload Read a file by path with locale and open flags.)doc")
.def("read_file",
[](whiteout::storages::casc::Storage& self, whiteout::i32 fileId, whiteout::storages::casc::FileIdHint hint) {
auto __r = self.readFile(fileId, hint);
if (!__r) return py::object(py::none());
return py::object(py::bytes(
reinterpret_cast<const char*>(__r->data()), __r->size()));
}, py::arg("fileId"), py::arg("hint") = whiteout::storages::casc::FileIdHint{}, R"doc(@overload Read a file by WoW-style FileDataId.)doc")
.def("read_file",
[](whiteout::storages::casc::Storage& self, whiteout::i32 fileId, whiteout::u32 localeFlags, whiteout::u32 openFlags, whiteout::storages::casc::FileIdHint hint) {
auto __r = self.readFile(fileId, localeFlags, openFlags, hint);
if (!__r) return py::object(py::none());
return py::object(py::bytes(
reinterpret_cast<const char*>(__r->data()), __r->size()));
}, py::arg("fileId"), py::arg("localeFlags"), py::arg("openFlags") = whiteout::u32{}, py::arg("hint") = whiteout::storages::casc::FileIdHint{}, R"doc(@overload Read a file by FileDataId with locale and open flags.)doc")
.def("file_exists", py::overload_cast<const std::string&>(&whiteout::storages::casc::Storage::fileExists, py::const_), py::arg("cascPath"), R"doc(@return True if the path resolves to a known file.)doc")
.def("file_exists", py::overload_cast<whiteout::i32, whiteout::storages::casc::FileIdHint>(&whiteout::storages::casc::Storage::fileExists, py::const_), py::arg("fileId"), py::arg("hint") = whiteout::storages::casc::FileIdHint{}, R"doc(@overload Check existence by FileDataId.)doc")
.def("file_size", py::overload_cast<const std::string&>(&whiteout::storages::casc::Storage::fileSize, py::const_), py::arg("cascPath"), R"doc(@return Uncompressed file size, or std::nullopt if not found.)doc")
.def("file_size", py::overload_cast<whiteout::i32, whiteout::storages::casc::FileIdHint>(&whiteout::storages::casc::Storage::fileSize, py::const_), py::arg("fileId"), py::arg("hint") = whiteout::storages::casc::FileIdHint{}, R"doc(@overload)doc")
.def("list_files", &whiteout::storages::casc::Storage::listFiles, R"doc(@return All known file paths.)doc")
.def("list_entries", &whiteout::storages::casc::Storage::listEntries, R"doc(@return All entries with metadata.)doc")
.def("total_file_count", &whiteout::storages::casc::Storage::totalFileCount, R"doc(@return Total number of files in the root manifest.)doc")
.def("import_keys_from_string", &whiteout::storages::casc::Storage::importKeysFromString, py::arg("keyList"), R"doc(Import encryption keys from a formatted string (one per line).)doc")
.def("import_keys_from_file", &whiteout::storages::casc::Storage::importKeysFromFile, py::arg("keyFilePath"), R"doc(Import encryption keys from a file.)doc")
.def("set_zero_fill_encrypted", &whiteout::storages::casc::Storage::setZeroFillEncrypted, py::arg("on"), R"doc(Substitute zeros for any frame whose encryption key is unavailable, instead of failing the read. Off by default.
Unreleased content ships encrypted with keys that are not published, and a single such frame otherwise takes a whole file with it — a client database that is 99% readable is worth more than none of it. CascLib spells this CASC_OVERCOME_ENCRYPTED. Turn it on only where a partly blank file is more useful than no file.)doc")
.def("find_encryption_key", &whiteout::storages::casc::Storage::findEncryptionKey, py::arg("keyName"), R"doc(@return The encryption key for @p keyName, or std::nullopt if not found.)doc")
.def("flush_cache", &whiteout::storages::casc::Storage::flushCache, R"doc(Clear the in-memory decoded-data cache (container cache).)doc")
.def("prefetch", &whiteout::storages::casc::Storage::prefetch, R"doc(Force every deferred load (encoding, root, VFS, index files, orphan bitvector) to resolve. Idempotent.)doc")
.def_static("last_error", &whiteout::storages::casc::Storage::lastError, R"doc(@return Last error code (thread-local).)doc")
;
py::class_<whiteout::storages::casc::StorageWritable>(m, "StorageWritable", R"doc(Writable CASC storage (read + write + save)
Inherits all read operations from Storage. Adds write overlay and persist-to-disk support.
Only local-backed storages can be writable (CDN is read-only).
extends=whiteout::storages::casc::Storage)doc")
.def_static("create", &whiteout::storages::casc::StorageWritable::create, py::arg("opts") = whiteout::storages::casc::CreateOptions{}, py::arg("pool") = nullptr, R"doc(Create a new empty storage in memory.
No file is written to disk until save() is called.
@param opts Creation options (product name, version, root format). @param pool Optional WorkerPool for parallel I/O. @return A valid empty StorageWritable ready for writeFile() calls.)doc")
.def("reserve_file_id", &whiteout::storages::casc::StorageWritable::reserveFileId, py::arg("name"), R"doc(Reserve a file-data-ID for a named asset.
Allocates the next available file-data-ID and associates it with @p name. The interpretation of @p name depends on the root format:
- **WoW / WoWTvfs**: @p name is a full CASC path (e.g. `"Base\\creatures\\beast\\beast.m2"`). - **Diablo 3 / Diablo 4 / TVFS**: @p name is `"asset_name.ext"`, where the extension determines the SNO group. A CoreTOC entry is created automatically.
Returns @c std::nullopt if the name already exists in the root or in a previous reservation.
@code auto id = storage.reserveFileId("my_beast.app"); if (id) storage.writeFile(*id, data); @endcode)doc")
.def("write_file", py::overload_cast<const std::string&, const std::vector<whiteout::u8>&, whiteout::storages::casc::WriteOptions>(&whiteout::storages::casc::StorageWritable::writeFile), py::arg("path"), py::arg("data"), py::arg("opts") = whiteout::storages::casc::WriteOptions{}, R"doc(Write a file by path.
Data is stored in an in-memory overlay until save() is called.
@param path CASC path for the new or updated file. @param data File contents. @param opts Write options (locale, content flags, compression). @return True on success.)doc")
.def("write_file", py::overload_cast<whiteout::i32, const std::vector<whiteout::u8>&, whiteout::storages::casc::WriteOptions, whiteout::storages::casc::FileIdHint>(&whiteout::storages::casc::StorageWritable::writeFile), py::arg("fileId"), py::arg("data"), py::arg("opts") = whiteout::storages::casc::WriteOptions{}, py::arg("hint") = whiteout::storages::casc::FileIdHint{}, R"doc(@overload Write a file by FileDataId.)doc")
.def("delete_file", py::overload_cast<const std::string&>(&whiteout::storages::casc::StorageWritable::deleteFile), py::arg("path"), R"doc(Mark a file for deletion (effective on next save).)doc")
.def("delete_file", py::overload_cast<whiteout::i32, whiteout::storages::casc::FileIdHint>(&whiteout::storages::casc::StorageWritable::deleteFile), py::arg("fileId"), py::arg("hint") = whiteout::storages::casc::FileIdHint{}, R"doc(@overload)doc")
.def("save", py::overload_cast<>(&whiteout::storages::casc::StorageWritable::save), R"doc(Persist all pending changes to disk (writes to the original location).)doc")
.def("save", py::overload_cast<const std::string&>(&whiteout::storages::casc::StorageWritable::save), py::arg("path"), R"doc(@overload Persist to a specific output path.)doc")
;
py::bind_vector<std::vector<whiteout::storages::casc::FindEntry>>(m, "VectorCascFindEntry");
}