Beta: WhiteoutLib is in beta. The public API is stabilising and most modules are exercised by real consumers, but breaking changes are still possible between minor versions.
WhiteoutLib is a C++ library for reading and writing the 3D model, texture, and storage formats used by Blizzard Entertainment games. It is inspired by StormLib but is a fresh, modern implementation: pure C++20 internals, no third-party compression or codec dependencies, and a public API designed for language bindings.
- Pure C++ CASC implementation. CASC reading and writing are implemented in-house. To our knowledge, WhiteoutLib is the only open-source library that ships a from-scratch C++ CASC writer with full BLTE encoding, encoding-table, and root-handler support.
- Zero external codec dependencies. Every codec the library needs — BCn (BC1/2/3/4/5/6H/7), JPEG baseline (for BLP1), PNG, Wu colour quantisation, mipmap filters — is implemented in-house. There is no zlib, bzip2, libjpeg, libpng, or stb_image link dependency, so the library composes cleanly with any host project's own copies of those libraries.
- Buffer-first parser API. Every parser accepts an in-memory
std::span<const u8>. That makes it trivial to compose with any storage backend (CASC, MPQ, loose files, network, in-memory archives) without per-format plumbing. std::optional-based error model. Parsers returnstd::optional<T>by default and offer an opt-in strict mode that throws. No surprise exceptions from the happy path, and lenient parsers surface issues via an inspectable issue list instead of aborting.- Multithreaded by default where it counts. CASC indexing, file enumeration, and bulk operations use a worker pool and timeline semaphores; measured throughput beats CascLib on most CASC paths we have benchmarked.
- PImpl public surface. Every heavy type (
Storage,Texture,models::*::Parser/Writer) hides its implementation behind astd::unique_ptr<Impl>. This keeps the ABI surface small and stable, and makes language bindings practical. - C++20 internals, C++11-compatible public headers. The implementation
uses C++20 freely; the public headers fall back to compatibility shims
(
std::optional/std::spanpolyfills) so binding generators that target older standards still work. - Round-trip read/write for most formats. MDX, M3, BLP, DDS, TEX, PNG, JPEG, BMP, TGA all support both directions. The texture pipeline is exercised in production by WhiteoutTex, which uses it for viewing, conversion, mipmap generation, and Blizzard texture workflows.
- Textures and Storage are the most mature modules and have seen thorough testing. The texture module is exercised extensively by WhiteoutTex.
- Models is mixed:
- MDX has had the heaviest testing.
- M3 can round-trip read and write StarCraft II and Heroes of the Storm models.
- M2 is still experimental — missing version-specific parsing for older
than Mists of Pandaria, and no
.phys/.bonesupport yet.
- WEM is an experimental intermediate model format intended to become the bridge between the other model formats.
- SNO is currently the weakest module:
- SNO is a binary JSON-style data type system used by Diablo III/IV.
- Diablo IV support still requires more reverse engineering for some payload data types.
- Diablo III support is currently quite weak.
- Warcraft III (
.mdx) — Classic and Reforged - World of Warcraft (
.m2) — experimental; partial, see above - StarCraft II / Heroes of the Storm (
.m3/.m3a) - Diablo III & IV (
.acr,.app,.ani,.ans,.mat,.prt, SNO format family) - WEM — experimental intermediate format
- Warcraft III & World of Warcraft (
.blp) — both BLP1 (Warcraft III Classic) and BLP2 (World of Warcraft) - Direct3D Surface (
.dds) - Diablo III & IV (
.tex) - Standard formats —
.jpeg,.bmp,.png,.tga, plus.gif(write only) - Mipmap generation for PBR and legacy pipelines, with texture-type and channel-semantics awareness
- CASC — pure C++ in-house implementation, read + write, with improved handling for Diablo III/IV root formats
- MPQ — optional Warcraft III archive reader/writer
WhiteoutLib ships three first-class language bindings:
| Language | Runtime | Location |
|---|---|---|
| JavaScript / TypeScript | WebAssembly (browser + Node.js) | packages/js-ts/ |
| Python | CPython 3.10+ extension module | bindings/python/ |
| Java | JDK 22+ via Foreign Function & Memory API | bindings/java/ |
A flat C ABI also exists under bindings/c/, but it is not intended for direct use — it exists purely as a stable bridge that the Java FFM layer (and any future non-C++ language) can call into. End users should pick one of the three high-level bindings above.
All bindings are produced by an in-house Python codegen tool (tools/codegen/) that parses annotated C++ headers via libclang into a backend-neutral IR, then emits backend-specific source for each target:
- Embind (
.cpp+.d.ts) for the WASM bindings - pybind11 (
.cpp+.pyi) for the Python bindings - Java + C ABI (
.java+extern "C"shim) for the FFM bindings
The same @bind annotations on C++ declarations drive every backend, so
adding or modifying a binding is a single-source change in the C++ header
followed by a codegen run — never a hand-edit of generated files.
The bindings are intentionally not a thin 1:1 mirror of the C++ API.
Each backend reshapes the surface to match the target language's
conventions: snake_case fields in Python, camelCase in JS/TS, JavaBeans
getters and AutoCloseable lifetime in Java, Optional<T> for fallible
parsers, native flag enums where each language has them, and per-language
math-type ergonomics. The goal is that idiomatic code in each language
reads as if the library had been written for that language directly.
Both Python and JavaScript bindings expose the underlying C++ std::vector
storage as a native buffer view in the host language — no copy, mutations
write through.
Primitive vectors and the standard math types (Vector2f/3f/4f,
Quaternion, ColorBGRA) implement the buffer protocol, so
np.asarray(...) returns a view aliased to the C++ memory:
import numpy as np
import whiteout as w
model = w.mdx.Parser().parse_buffer_format(
open("model.mdx", "rb").read(), w.mdx.MDLXFormat.MDX)
# Primitive vector → 1D array
keys = np.asarray(track.keys) # dtype=float32, shape=(N,)
keys[:] *= 2.0 # writes through to C++
# Math-struct vector → 2D array
pivots = np.asarray(model.pivot_points) # shape=(N, 3), dtype=float32
tangents = np.asarray(geo.tangents) # shape=(N, 4), dtype=float32
pivots[:, 1] += 1.0 # bulk Y-offset, no copyThe WASM bindings expose a matching .view() method on every primitive
and math-struct vector that returns a JS TypedArray aliased to the WASM
heap:
import { Whiteout } from "whiteout-wasm";
const w = await Whiteout();
const model = w.mdx.parse(mdxBytes);
// Primitive vector → 1D TypedArray
const keys: Float32Array = model.globalSequences.get(0).keys.view();
keys[1] = 99.0; // writes through
// Math-struct vector → flat TypedArray, laid out [x0,y0,z0, x1,y1,z1, …]
const pivots: Float32Array = model.pivotPoints.view();
const y1 = pivots[1 * 3 + 1];Element types map to the natural TypedArray for the target
(u8→Uint8Array, f32/Vector3f/Quaternion→Float32Array, …).
Caveat for both backends: any operation that may reallocate the
underlying C++ vector (append, push_back, resize, …) invalidates the
view. Re-acquire (np.asarray(...) / .view()) after a size change, or
copy out (np.array(...) / new Float32Array(view)) if you need a stable
snapshot.
Each binding has its own README with the full API tour, build instructions, and per-language gotchas:
The WASM and Python bindings currently cover the model, texture, MPQ, CASC, and host-extras surfaces. The Java binding tracks the same surface and is generated from the same IR.
See docs/ for per-format specifications.
CMake-based:
cmake -S . -B build
cmake --build build --config ReleaseUseful options:
| Option | Default | What it does |
|---|---|---|
WHITEOUT_ENABLE_CASC |
OFF |
Build the whiteout_casc static library |
WHITEOUT_ENABLE_MPQ |
OFF |
Build the whiteout_mpq static library |
WHITEOUT_BUILD_EXAMPLES |
OFF |
Build the example programs under examples/ |
WHITEOUT_BUILD_TESTS |
OFF |
Build the test suite |
WHITEOUT_BUILD_WASM_BINDINGS |
ON if Emscripten |
Build the WebAssembly bindings |
WHITEOUT_BUILD_PYTHON_BINDINGS |
OFF |
Build the Python bindings |
WHITEOUT_ENABLE_CLANG_TIDY |
OFF |
Run clang-tidy during the build |
WHITEOUT_WARNINGS_AS_ERRORS |
ON for master project |
Treat warnings as errors |
WHITEOUT_INSTALL |
ON |
Generate install + export rules for find_package(WhiteoutLib) |
After cmake --install build --prefix <prefix>, downstream CMake projects can pull WhiteoutLib in with:
find_package(WhiteoutLib 1.0 REQUIRED COMPONENTS casc mpq)
target_link_libraries(myapp PRIVATE
WhiteoutLib::whiteout_lib
WhiteoutLib::whiteout_casc
WhiteoutLib::whiteout_mpq
)COMPONENTS casc / mpq are optional; ask for them only if the install was built with the matching -DWHITEOUT_ENABLE_*=ON flag (the config script will fail loudly otherwise).
A vcpkg overlay port lives under ports/whiteoutlib/. To install into a vcpkg environment:
vcpkg install --overlay-ports=path/to/WhiteoutLib/ports whiteoutlib[casc,mpq]Then in your consuming project's CMakeLists.txt:
find_package(WhiteoutLib CONFIG REQUIRED COMPONENTS casc mpq)
target_link_libraries(app PRIVATE
WhiteoutLib::whiteout_lib WhiteoutLib::whiteout_casc WhiteoutLib::whiteout_mpq)Features map directly to CMake flags: whiteoutlib[casc] ⇒ WHITEOUT_ENABLE_CASC=ON, whiteoutlib[mpq] ⇒ WHITEOUT_ENABLE_MPQ=ON, whiteoutlib[curl] ⇒ WHITEOUT_ENABLE_CURL_HTTP=ON + pulls curl from vcpkg (no-op on Windows, which always uses WinHTTP).
When publishing the port to a custom vcpkg registry, bump version in ports/whiteoutlib/vcpkg.json and replace the SHA512 0 placeholder in portfile.cmake with the real SHA — vcpkg install will print the expected value on first run.
.clang-tidy at the repo root configures checks. CMake always emits
compile_commands.json next to the build outputs, which both clang-tidy
and clangd consume directly.
# Single file
clang-tidy -p build src/whiteout/storages/casc/codec/crypto.cpp
# Whole tree (parallel)
run-clang-tidy -p build -header-filter='(include|src)[/\\]whiteout[/\\]'
# Convenience wrapper (PowerShell)
pwsh tools/run-clang-tidy.ps1 -BuildDir build-DWHITEOUT_ENABLE_CLANG_TIDY=ON hooks clang-tidy into the build itself
(via CMAKE_CXX_CLANG_TIDY). This works for clang/gcc; the option is a
no-op for clang-cl on Windows (CMake's wrapper feeds clang-tidy a mangled
invocation there — use the standalone flow above).
Example programs live in examples/, covering loading and writing each supported format.
BSD 3-Clause. See LICENSE.
This project includes and/or references third-party components. See THIRD-PARTY-NOTICES.md for details.
This project is not affiliated with or endorsed by Blizzard Entertainment.