-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmpq_bindings.cpp
More file actions
197 lines (174 loc) · 14.4 KB
/
mpq_bindings.cpp
File metadata and controls
197 lines (174 loc) · 14.4 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
// 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 mpq --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/mpq/types.h>
#include <whiteout/storages/mpq/storage.h>
#include <whiteout/utils/mpq_file_system.h>
#include <whiteout/interfaces.h>
PYBIND11_MAKE_OPAQUE(std::vector<std::string>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::u8>);
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/operators.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
void bind_mpq(py::module_& m) {
py::enum_<whiteout::storages::mpq::FormatVersion>(m, "FormatVersion", R"doc(MPQ format version.)doc")
.value("V1", whiteout::storages::mpq::FormatVersion::V1, R"doc(Original format (up to 4 GB archives).)doc")
.value("V2", whiteout::storages::mpq::FormatVersion::V2, R"doc(Extended format (>4 GB archives, hi-block table).)doc")
;
py::enum_<whiteout::storages::mpq::Compression>(m, "Compression", R"doc(Compression algorithm for writing files.)doc")
.value("NONE", whiteout::storages::mpq::Compression::None, R"doc(No compression; data stored verbatim.)doc")
.value("HUFFMAN", whiteout::storages::mpq::Compression::Huffman, R"doc(Huffman coding (used for audio in older Blizzard games).)doc")
.value("ZLIB", whiteout::storages::mpq::Compression::Zlib, R"doc(zlib / DEFLATE compression (most common MPQ codec).)doc")
.value("P_KWARE", whiteout::storages::mpq::Compression::PKware, R"doc(PKware DCL (implode) compression.)doc")
.value("B_ZIP2", whiteout::storages::mpq::Compression::BZip2, R"doc(bzip2 compression.)doc")
.value("SPARSE", whiteout::storages::mpq::Compression::Sparse, R"doc(Sparse / RLE compression.)doc")
.value("ADPCM_MONO", whiteout::storages::mpq::Compression::AdpcmMono, R"doc(IMA ADPCM mono (used for mono audio).)doc")
.value("ADPCM_STEREO", whiteout::storages::mpq::Compression::AdpcmStereo, R"doc(IMA ADPCM stereo (used for stereo audio).)doc")
;
py::enum_<whiteout::storages::mpq::FileFlags>(m, "FileFlags")
.value("NONE", whiteout::storages::mpq::FileFlags::None)
.value("COMPRESSED", whiteout::storages::mpq::FileFlags::Compressed, R"doc(File uses sector compression.)doc")
.value("ENCRYPTED", whiteout::storages::mpq::FileFlags::Encrypted, R"doc(File data is encrypted.)doc")
.value("SINGLE_UNIT", whiteout::storages::mpq::FileFlags::SingleUnit, R"doc(File stored as a single unit (no sector splitting).)doc")
.value("EXISTS", whiteout::storages::mpq::FileFlags::Exists, R"doc(Slot is occupied by a real file.)doc")
;
py::class_<whiteout::storages::mpq::FileInfo>(m, "FileInfo", R"doc(Information about a single file in the archive.)doc")
.def(py::init<>())
.def_readwrite("name", &whiteout::storages::mpq::FileInfo::name, R"doc(Filename (from listfile or hash table lookup).)doc")
.def_readwrite("compressed_size", &whiteout::storages::mpq::FileInfo::compressedSize, R"doc(Compressed storage size in bytes.)doc")
.def_readwrite("uncompressed_size", &whiteout::storages::mpq::FileInfo::uncompressedSize, R"doc(Uncompressed (original) file size in bytes.)doc")
.def_readwrite("flags", &whiteout::storages::mpq::FileInfo::flags, R"doc(Block entry flags (see FileFlags enum).)doc")
.def_readwrite("locale", &whiteout::storages::mpq::FileInfo::locale, R"doc(Locale ID (typically Locale::Neutral).)doc")
;
py::class_<whiteout::storages::mpq::ArchiveInfo>(m, "ArchiveInfo", R"doc(Summary information about the archive.)doc")
.def(py::init<>())
.def(py::init([](whiteout::u16 format_version, whiteout::u32 hash_table_entries, whiteout::u32 block_table_entries, whiteout::u32 sector_size, whiteout::u64 archive_size) {
return whiteout::storages::mpq::ArchiveInfo{format_version, hash_table_entries, block_table_entries, sector_size, archive_size};
}), py::arg("format_version"), py::arg("hash_table_entries"), py::arg("block_table_entries"), py::arg("sector_size"), py::arg("archive_size"))
.def("__repr__", [](const whiteout::storages::mpq::ArchiveInfo& self) {
std::ostringstream oss;
oss << "ArchiveInfo(";
oss << "format_version=" << self.formatVersion << ", ";
oss << "hash_table_entries=" << self.hashTableEntries << ", ";
oss << "block_table_entries=" << self.blockTableEntries << ", ";
oss << "sector_size=" << self.sectorSize << ", ";
oss << "archive_size=" << self.archiveSize << ")";
return oss.str();
})
.def_readwrite("format_version", &whiteout::storages::mpq::ArchiveInfo::formatVersion, R"doc(0 = V1, 1 = V2.)doc")
.def_readwrite("hash_table_entries", &whiteout::storages::mpq::ArchiveInfo::hashTableEntries, R"doc(Hash table capacity (always a power of 2).)doc")
.def_readwrite("block_table_entries", &whiteout::storages::mpq::ArchiveInfo::blockTableEntries, R"doc(Number of occupied block table entries.)doc")
.def_readwrite("sector_size", &whiteout::storages::mpq::ArchiveInfo::sectorSize, R"doc(Sector size in bytes (512 << sectorSizeShift).)doc")
.def_readwrite("archive_size", &whiteout::storages::mpq::ArchiveInfo::archiveSize, R"doc(Total archive size in bytes.)doc")
;
py::class_<whiteout::storages::mpq::WriteOptions>(m, "WriteOptions", R"doc(Options for writing a file into the archive.)doc")
.def(py::init<>())
.def(py::init([](whiteout::storages::mpq::Compression compression, whiteout::u16 locale, bool encrypt, bool single_unit) {
return whiteout::storages::mpq::WriteOptions{compression, locale, encrypt, single_unit};
}), py::arg("compression"), py::arg("locale"), py::arg("encrypt"), py::arg("single_unit"))
.def("__repr__", [](const whiteout::storages::mpq::WriteOptions& self) {
std::ostringstream oss;
oss << "WriteOptions(";
oss << "compression=" << static_cast<int>(self.compression) << ", ";
oss << "locale=" << self.locale << ", ";
oss << "encrypt=" << self.encrypt << ", ";
oss << "single_unit=" << self.singleUnit << ")";
return oss.str();
})
.def_readwrite("compression", &whiteout::storages::mpq::WriteOptions::compression, R"doc(Compression algorithm to apply.)doc")
.def_readwrite("locale", &whiteout::storages::mpq::WriteOptions::locale, R"doc(Locale ID for the hash table slot.)doc")
.def_readwrite("encrypt", &whiteout::storages::mpq::WriteOptions::encrypt, R"doc(Encrypt file data with a derived key.)doc")
.def_readwrite("single_unit", &whiteout::storages::mpq::WriteOptions::singleUnit, R"doc(Store the file as a single unpartitioned unit.)doc")
;
py::class_<whiteout::storages::mpq::CreateOptions>(m, "CreateOptions", R"doc(Options for creating a new archive.)doc")
.def(py::init<>())
.def(py::init([](whiteout::storages::mpq::FormatVersion version, whiteout::u32 hash_table_size, whiteout::u16 sector_size_shift) {
return whiteout::storages::mpq::CreateOptions{version, hash_table_size, sector_size_shift};
}), py::arg("version"), py::arg("hash_table_size"), py::arg("sector_size_shift"))
.def("__repr__", [](const whiteout::storages::mpq::CreateOptions& self) {
std::ostringstream oss;
oss << "CreateOptions(";
oss << "version=" << static_cast<int>(self.version) << ", ";
oss << "hash_table_size=" << self.hashTableSize << ", ";
oss << "sector_size_shift=" << self.sectorSizeShift << ")";
return oss.str();
})
.def_readwrite("version", &whiteout::storages::mpq::CreateOptions::version, R"doc(Archive format version (V1 or V2).)doc")
.def_readwrite("hash_table_size", &whiteout::storages::mpq::CreateOptions::hashTableSize, R"doc(Initial hash table capacity; rounded up to the next power of 2.)doc")
.def_readwrite("sector_size_shift", &whiteout::storages::mpq::CreateOptions::sectorSizeShift, R"doc(Sector size = 512 << shift (default 3 → 4096 bytes).)doc")
;
py::class_<whiteout::storages::mpq::Storage>(m, "Storage", R"doc(RAII wrapper for MPQ archive access
Provides full CRUD operations on MPQ archives. Modifications are held in a virtual overlay until save() is called, which writes a complete new archive atomically (write to temp file, then rename).
All public methods are thread-safe: read operations acquire a shared lock; write and persist operations acquire an exclusive lock.)doc")
.def_static("open",
[](const std::string& path, whiteout::interfaces::WorkerPool* pool) {
return whiteout::storages::mpq::Storage::open(path, pool);
}, py::arg("path"), py::arg("pool") = nullptr, R"doc(Open an existing MPQ archive. Memory-maps the file and parses tables. @param path Path to the .mpq file. @param pool Optional WorkerPool for parallel compress/decompress (non-owning). @return A valid Storage, or std::nullopt on failure.)doc")
.def_static("create", &whiteout::storages::mpq::Storage::create, py::arg("opts") = whiteout::storages::mpq::CreateOptions{}, py::arg("pool") = nullptr, R"doc(Create a new empty archive in memory. No file on disk until save(path).)doc")
.def("close", &whiteout::storages::mpq::Storage::close, R"doc(Release all resources. Same effect as letting the Storage go out of scope.)doc")
.def("read_file",
[](whiteout::storages::mpq::Storage& self, const std::string& name) {
auto __r = self.readFile(name);
if (!__r) return py::object(py::none());
return py::object(py::bytes(
reinterpret_cast<const char*>(__r->data()), __r->size()));
}, py::arg("name"), R"doc(Read a file from the archive. Checks the overlay first, then the source archive. @return File contents, or std::nullopt if not found or deleted.)doc")
.def("read_file",
[](whiteout::storages::mpq::Storage& self, const std::string& name, whiteout::u16 locale) {
auto __r = self.readFile(name, locale);
if (!__r) return py::object(py::none());
return py::object(py::bytes(
reinterpret_cast<const char*>(__r->data()), __r->size()));
}, py::arg("name"), py::arg("locale"), R"doc(Read a file with a specific locale.)doc")
.def("file_exists", &whiteout::storages::mpq::Storage::fileExists, py::arg("name"), R"doc(Check if a file exists in the archive (including overlay).)doc")
.def("file_info",
[](whiteout::storages::mpq::Storage& self, const std::string& name) {
return self.fileInfo(name);
}, py::arg("name"), R"doc(Get information about a file.)doc")
.def("archive_info", &whiteout::storages::mpq::Storage::archiveInfo, R"doc(Get summary information about the archive.)doc")
.def("list_files", &whiteout::storages::mpq::Storage::listFiles, R"doc(List all known filenames (from listfile + overlay additions − deletions).)doc")
.def("write_file",
[](whiteout::storages::mpq::Storage& self, const std::string& name, py::bytes __py_bytes_1, whiteout::storages::mpq::WriteOptions opts) {
std::string __s_1 = __py_bytes_1;
std::span<const whiteout::u8> data(reinterpret_cast<const whiteout::u8*>(__s_1.data()), __s_1.size());
return self.writeFile(name, data, opts);
}, py::arg("name"), py::arg("data"), py::arg("opts") = whiteout::storages::mpq::WriteOptions{}, R"doc(Write or overwrite a file. Data is held in overlay until save(). @return true on success, false if the hash table is full.)doc")
.def("delete_file", &whiteout::storages::mpq::Storage::deleteFile, py::arg("name"), R"doc(Delete a file from the archive. @return true if the file was found (in source or overlay), false otherwise.)doc")
.def("save", py::overload_cast<>(&whiteout::storages::mpq::Storage::save), R"doc(Save the archive to its original path (temp file + atomic rename). @return false if this Storage was created via create() with no prior save(path).)doc")
.def("save", py::overload_cast<const std::string&>(&whiteout::storages::mpq::Storage::save), py::arg("path"), R"doc(Save the archive to a specific path. After saving, the new file becomes the source archive and the overlay is cleared.)doc")
;
py::class_<whiteout::utils::MpqFileSystem, whiteout::interfaces::VirtualPathFileSystem>(m, "FileSystem", R"doc(VirtualPathFileSystem implementation backed by an MPQ archive.
The Storage must outlive this object — MpqFileSystem holds a non-owning reference to it.
Path separators: both '/' and '\\' are accepted and treated identically. Filename comparison is case-insensitive, matching MPQ archive semantics.
Requires the `whiteout_mpq` CMake target.
Example: auto storage = mpq::Storage::open("War3.mpq"); utils::MpqFileSystem fs(*storage); auto data = fs.readFile("units\\orc\\grunt\\grunt.mdx");)doc")
.def(py::init<whiteout::storages::mpq::Storage&>(), py::arg("storage"))
.def("read_file",
[](whiteout::utils::MpqFileSystem& self, const std::string& path) {
auto __v = self.readFile(path);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("path"), R"doc(Read a file from the archive. Returns an empty vector if not found.)doc")
.def("write_file", &whiteout::utils::MpqFileSystem::writeFile, py::arg("path"), py::arg("data"), R"doc(Write a file into the archive overlay. Changes are not persisted to disk until storage.save() is called on the underlying Storage.)doc")
.def("file_exists", &whiteout::utils::MpqFileSystem::fileExists, py::arg("path"), R"doc(Check if a file exists in the archive (including the write overlay).)doc")
;
}