wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
blp.hpp
Go to the documentation of this file.
1#pragma once
2
22
23#include <algorithm>
24#include <array>
25#include <cstddef>
26#include <cstdint>
27#include <span>
28#include <type_traits>
29#include <vector>
30
31#include <welder/vocabulary.hpp>
32
34#include <wowlib/core/error.hpp>
41
42namespace wowlib::formats::blp {
45 inline constexpr std::uint32_t BlpMagic = fourcc("BLP2", FourCCEndian::Forward);
47 inline constexpr std::uint32_t BlpVersion1 = 1;
49 inline constexpr std::size_t BlpMaxMips = 16;
51 inline constexpr std::size_t BlpPaletteSize = 256;
53 inline constexpr std::size_t BlpHeaderBytes = 0x494;
54
55 enum class [[
56 =welder::weld,
57 =welder::doc("How a BLP's pixel payload is encoded (the header's "
58 "colorEncoding byte).")
59 ]] ColorEncoding : std::uint8_t {
60 Jpeg [[=welder::doc("JPEG-compressed content (Warcraft III heritage; never "
61 "shipped in WoW clients — wowlib preserves but cannot "
62 "decode it).")]] = 0,
63 Palettized [[=welder::doc("256-color palette indices, one byte per pixel, "
64 "followed by a separate alpha plane of "
65 "alphaDepth bits per pixel.")]] = 1,
66 Dxt [[=welder::doc("DXT/S3TC block compression; the variant (BC1/BC2/BC3/"
67 "BC5) follows from preferred_format and "
68 "alphaDepth.")]] = 2,
69 Bgra [[=welder::doc("Raw 32-bit BGRA pixels (Cataclysm+; terrain cube "
70 "maps).")]] = 3,
71 BgraAlt [[=welder::doc("Raw 32-bit BGRA under a different client-side "
72 "PIXEL_FORMAT; identical file content to "
73 "Bgra.")]] = 4,
74 };
76 enum class [[
77 =welder::weld,
78 =welder::doc("The client-side pixel format hint (the header's "
79 "preferredFormat byte). For DXT-encoded files it selects the "
80 "block format: Dxt1 -> BC1, Dxt3 -> BC2, Dxt5 -> BC3, "
81 "Bc5 -> BC5.")
82 ]] PixelFormat : std::uint8_t {
83 Dxt1 [[=welder::doc("BC1: 8-byte blocks, optional 1-bit punch-through "
84 "alpha.")]] = 0,
85 Dxt3 [[=welder::doc("BC2: 16-byte blocks, explicit 4-bit alpha.")]] = 1,
86 Argb8888 [[=welder::doc("Raw 32-bit upload hint (used by Bgra-encoded "
87 "files).")]] = 2,
88 Argb1555 [[=welder::doc("16-bit 1555 upload hint; never a file "
89 "content layout.")]] = 3,
90 Argb4444 [[=welder::doc("16-bit 4444 upload hint; never a file "
91 "content layout.")]] = 4,
92 Rgb565 [[=welder::doc("16-bit 565 upload hint; never a file content "
93 "layout.")]] = 5,
94 A8 [[=welder::doc("Alpha-only upload hint; never a file content "
95 "layout.")]] = 6,
96 Dxt5 [[=welder::doc("BC3: 16-byte blocks, interpolated 8-bit alpha.")]] = 7,
97 Unspecified [[=welder::doc("No preference recorded (typical for palettized "
98 "files).")]] = 8,
99 Argb2565 [[=welder::doc("Component-texture upload hint; never a file "
100 "content layout.")]] = 9,
101 Bc5 [[=welder::doc("BC5: two interpolated channels (normal maps, later "
102 "clients).")]] = 11,
103 };
104
105 namespace detail {
107 palette follows it). The entity decomposes these fields into welded
108 members; this struct exists for layout-exact serialization. */
109 struct BLPHeader {
110 std::uint32_t magic = BlpMagic;
111 std::uint32_t version = BlpVersion1;
112 std::uint8_t colorEncoding = 2;
113 std::uint8_t alphaDepth = 0;
114 std::uint8_t preferredFormat = 0;
115 std::uint8_t mipFlags = 0;
116 std::uint32_t width = 0;
117 std::uint32_t height = 0;
118 std::array<std::uint32_t, BlpMaxMips> mipOffsets{};
120 std::array<std::uint32_t, BlpMaxMips> mipSizes{};
122 };
124 static_assert(sizeof(BLPHeader) == 0x94);
125 static_assert(std::is_trivially_copyable_v<BLPHeader>);
126
127
129 bytes, non-contiguous or shared mip placement) still round-trip
130 byte-perfectly. Disengaged (fresh entity, or payload sizes changed):
131 write() lays the file out canonically — header, then the levels
132 back-to-back in level order. */
133 struct StoredLayout {
136 struct Run {
137 std::uint32_t offset = 0;
140 bool operator==(const Run&) const = default;
141 };
143 std::array<std::uint32_t, BlpMaxMips> offsets{};
145 std::array<std::uint32_t, BlpMaxMips> sizes{};
147 std::uint32_t fileSize = 0;
148 std::vector<Run> gaps;
149 bool engaged = false;
151 bool operator==(const StoredLayout&) const = default;
152 };
154
155 struct [[
156 =welder::weld,
157 =welder::doc(R"(
158 A decoded texture surface: 8-bit RGBA pixels in row-major order, row 0
159 at the top. pixels holds width * height * 4 bytes (r, g, b, a per
160 pixel) and maps to NumPy zero-copy; reshape to (height, width, 4).)")
161 ]] Image {
162 [[=welder::doc("The width in pixels.")]]
163 std::uint32_t width = 0;
164
165 [[=welder::doc("The height in pixels.")]]
166 std::uint32_t height = 0;
167
168 [[=welder::mark::no_reassign,
169 =welder::doc("The RGBA8 pixel bytes, width * height * 4, row-major from "
170 "the top-left.")]]
171 std::vector<std::uint8_t> pixels;
173 bool operator==(const Image&) const = default;
174 };
176 struct [[
177 =welder::weld,
178 =welder::doc(R"(
179 How encode() should build the file. The defaults produce what the
180 client ships most: DXT compression with the block format chosen from
181 the alpha depth (0 -> BC1, 1 -> BC1 punch-through, 4 -> BC2,
182 8 -> BC3), with a full generated mip chain.)")
183 ]] EncodeSettings {
184 [[=welder::doc("The payload encoding to produce (Palettized, Dxt or "
185 "Bgra; Jpeg is not supported).")]]
187
188 [[=welder::doc("The DXT block format for Dxt encoding (Dxt1, Dxt3, Dxt5 "
189 "or Bc5). Unspecified picks from alphaDepth: 0/1 -> Dxt1, "
190 "4 -> Dxt3, 8 -> Dxt5. Ignored for Palettized/Bgra.")]]
192
193 [[=welder::doc("Alpha bits per pixel: 0, 1, 4 or 8. Selects the alpha "
194 "plane depth for Palettized files and the block format for "
195 "Dxt when format is Unspecified.")]]
196 std::uint8_t alphaDepth = 8;
197
198 [[=welder::doc("Whether to generate the full mip chain down to 1x1 "
199 "(box-filtered). Off: the file holds only level 0.")]]
200 bool mipmaps = true;
201
202 bool operator==(const EncodeSettings&) const = default;
203 };
204
205 struct [[
206 =welder::weld,
207 // The dotnet style would coerce the all-caps identifier to Blp; the
208 // format acronym is the name (round-1 API feedback). Python already
209 // spells BLP, so the rename is cs-scoped.
210 =welder::weld_as(wowlib::lang::Cs, "BLP"),
211 =welder::doc(R"(
212 A BLP2 texture file — every WoW client release reads the same layout,
213 so the class carries no client-version axis. read()/write() move the
214 file whole with a byte-perfect round-trip while the mip payloads are
215 unmodified; decode(level) produces an RGBA8 Image from a stored level
216 (palettized, DXT1/3/5, BC5 and raw BGRA all decode); encode(image,
217 settings) rebuilds the palette/compression/mip chain from one. Raw
218 payload access goes through mip()/set_mip(). See
219 https://wowdev.wiki/BLP.)")
220 ]] BLP : FileEntityBase {
221 [[=welder::doc("The header version field; 1 in every shipped file.")]]
222 std::uint32_t version = BlpVersion1;
223
224 [[=welder::doc("How the pixel payload is encoded.")]]
225 ColorEncoding colorEncoding = ColorEncoding::Dxt;
226
227 [[=welder::doc("Alpha bits per pixel (0, 1, 4 or 8): the alpha plane "
228 "depth for Palettized files, and a selection hint for "
229 "DXT.")]]
230 std::uint8_t alphaDepth = 8;
231
232 [[=welder::doc("The client-side pixel format hint; selects the DXT block "
233 "format for Dxt-encoded files.")]]
234 PixelFormat preferredFormat = PixelFormat::Dxt5;
235
236 [[=welder::doc("The header's mip byte: 0 = level 0 only, 1 = generated "
237 "mips, 2 = handmade mips (plus rare high flag bits, "
238 "preserved verbatim).")]]
239 std::uint8_t mipFlags = 1;
241 [[=welder::doc("The level-0 width in pixels.")]]
242 std::uint32_t width = 0;
243
244 [[=welder::doc("The level-0 height in pixels.")]]
245 std::uint32_t height = 0;
246
247 [[=welder::doc("The 256-entry color table of Palettized files (b, g, r + "
248 "a padding byte, preserved verbatim). Present but unused "
249 "for Dxt/Bgra files.")]]
250 std::array<CImVector, BlpPaletteSize> palette{};
251
255 [[=welder::mark::exclude]]
256 std::vector<FileBuffer> mips;
257
258
259 [[=welder::mark::exclude]]
262 // --- serialization --------------------------------------------------------
263
264 [[=welder::doc("Parse a BLP2 file from memory, replacing this entity's "
265 "contents.")]]
266 Result<void> read(std::span<const std::byte> data [[=welder::doc("the complete file bytes")]]);
267
268 [[nodiscard]]
269 [[=welder::doc("Serialize this entity. While the mip payloads are "
270 "unmodified the original file's exact layout is replayed, "
271 "so an unmodified read rewrites byte-for-byte."),
272 =welder::returns("the file bytes")]]
275 [[=welder::doc("Load the BLP from a client filesystem, replacing this "
276 "entity's contents.")]]
277 Result<void> read(fs::FileSystem& fs [[=welder::doc("the filesystem gateway")]],
278 const FileKey& key [[=welder::doc("the file identity (path and/or FileDataID)")]]);
279
280 [[=welder::doc("Serialize and store the BLP through the filesystem's "
281 "project overlay.")]]
282 Result<void> write(fs::FileSystem& fs [[=welder::doc("the filesystem gateway")]],
283 const FileKey& key [[=welder::doc("the file identity; must resolve to a path")]]) const;
284
285 // --- the image surface ----------------------------------------------------
286
287 [[nodiscard]]
288 [[=welder::doc("Decode mip level 0 to an RGBA8 Image."),
289 =welder::returns("the decoded image")]]
290 Result<Image> decode() const;
291
292 [[nodiscard]]
293 [[=welder::doc("Decode one stored mip level to an RGBA8 Image."),
294 =welder::returns("the decoded image")]]
295 Result<Image> decode(std::uint32_t level [[=welder::doc("the mip level to decode")]]) const;
296
297 [[=welder::doc("Rebuild the whole texture from an RGBA8 image with the "
298 "default settings (DXT, alpha depth 8 -> BC3, full mip "
299 "chain).")]]
300 Result<void> encode(const Image& image
301 [[=welder::doc("the level-0 image; width * height * 4 "
302 "pixel bytes")]]);
303
304 [[=welder::doc("Rebuild the whole texture from an RGBA8 image: sets the "
305 "header fields, quantizes/compresses every level and "
306 "generates the mip chain per the settings.")]]
307 Result<void> encode(const Image& image
308 [[=welder::doc("the level-0 image; width * height * 4 "
309 "pixel bytes")]],
310 const EncodeSettings& settings
311 [[=welder::doc("encoding, block format, alpha depth "
312 "and mip generation choices")]]);
313
314 // --- raw mip access -------------------------------------------------------
315
316 [[=welder::getter,
317 =welder::doc("The number of stored mip levels (level indices 0 .. "
318 "count - 1).")]]
319 std::size_t mipCount() const { return mips.size(); }
320
321 [[nodiscard]]
322 [[=welder::doc("One level's raw payload bytes (palette indices + alpha "
323 "plane, DXT blocks, or BGRA pixels, per colorEncoding)."),
324 =welder::returns("a copy of the payload bytes")]]
325 Result<FileBuffer> mip(std::uint32_t level [[=welder::doc("the mip level")]]) const;
326
327 [[=welder::doc("Replace one level's raw payload bytes verbatim. The "
328 "caller owns their consistency with the header fields; "
329 "changing a payload's size switches write() to the "
330 "canonical contiguous layout.")]]
331 Result<void> setMip(std::uint32_t level [[=welder::doc("the mip level")]],
332 std::span<const std::byte> data [[=welder::doc("the payload bytes")]]);
333
334 [[nodiscard]]
335 [[=welder::doc("The pixel width of a mip level (level 0 halves per step, "
336 "floored at 1).")]]
337 std::uint32_t mipWidth(std::uint32_t level [[=welder::doc("the mip level")]]) const {
338 return std::max<std::uint32_t>(1, width >> level);
339 }
340
341 [[nodiscard]]
342 [[=welder::doc("The pixel height of a mip level (level 0 halves per step, "
343 "floored at 1).")]]
344 std::uint32_t mipHeight(std::uint32_t level [[=welder::doc("the mip level")]]) const {
345 return std::max<std::uint32_t>(1, height >> level);
346 }
347
348 [[nodiscard]]
349 [[=welder::doc(R"(
350 Check the logical integrity contracts this texture must satisfy for the
351 client to decode it — the base level's presence, the dimensions, and
352 every stored level covering the pixels its size implies. write() never
353 runs this; call it before writing when you want to know the result will
354 load.)"),
355 =welder::returns("every violated contract, in level order")]]
356 ValidationReport validate() const;
357
358 [[nodiscard]]
359 [[=welder::doc("Validate and raise on the first error instead of returning "
360 "a report — the assert-style face of validate()."),
361 =welder::returns("nothing; raises when validate() finds any error")]]
362 Result<void> ensureValid() const { return validate().toResult(); }
363
364 bool operator==(const BLP&) const = default;
365 };
366}
The owning byte buffer file contents are read into.
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 runtime facade over the static compositions — the primary welder binding surface of the fs layer.
FourCC chunk identifiers: compile-time conversion of the four-letter codes to the host integers chunk...
constexpr std::size_t BlpHeaderBytes
The full header region: 148-byte header + 1024-byte palette.
Definition blp.hpp:53
constexpr std::uint32_t BlpVersion1
The BLP2 header version field; always 1 in every shipped client.
Definition blp.hpp:47
constexpr std::size_t BlpPaletteSize
The palette entry count (one byte-sized index space).
Definition blp.hpp:51
constexpr std::uint32_t BlpMagic
The BLP2 magic: the literal bytes "BLP2" (a forward FourCC, unlike the reversed chunk ids).
Definition blp.hpp:45
PixelFormat
The client-side pixel format hint (the header's preferredFormat byte).
Definition blp.hpp:65
@ Dxt1
BC1: 8-byte blocks, optional 1-bit punch-through alpha.
Definition blp.hpp:66
@ Argb1555
16-bit 1555 upload hint; never a file content layout.
Definition blp.hpp:69
@ Argb8888
Raw 32-bit upload hint (used by Bgra-encoded files).
Definition blp.hpp:68
@ Argb2565
Component-texture upload hint; never a file content layout.
Definition blp.hpp:75
@ Unspecified
No preference recorded (typical for palettized files).
Definition blp.hpp:74
@ Rgb565
16-bit 565 upload hint; never a file content layout.
Definition blp.hpp:71
@ Dxt5
BC3: 16-byte blocks, interpolated 8-bit alpha.
Definition blp.hpp:73
@ Dxt3
BC2: 16-byte blocks, explicit 4-bit alpha.
Definition blp.hpp:67
@ Argb4444
16-bit 4444 upload hint; never a file content layout.
Definition blp.hpp:70
@ Bc5
BC5: two interpolated channels (normal maps, later clients).
Definition blp.hpp:76
@ A8
Alpha-only upload hint; never a file content layout.
Definition blp.hpp:72
constexpr std::size_t BlpMaxMips
The mip table capacity: a BLP addresses at most 16 levels.
Definition blp.hpp:49
ColorEncoding
How a BLP's pixel payload is encoded (the header's colorEncoding byte).
Definition blp.hpp:56
@ Jpeg
JPEG-compressed content (Warcraft III heritage; never shipped in WoW clients — wowlib preserves but c...
Definition blp.hpp:57
@ Bgra
Raw 32-bit BGRA pixels (Cataclysm+; terrain cube maps).
Definition blp.hpp:60
@ Dxt
DXT/S3TC block compression; the variant (BC1/BC2/BC3/BC5) follows from preferred_format and alphaDept...
Definition blp.hpp:59
@ Palettized
256-color palette indices, one byte per pixel, followed by a separate alpha plane of alphaDepth bits ...
Definition blp.hpp:58
@ BgraAlt
Raw 32-bit BGRA under a different client-side PIXEL_FORMAT; identical file content to Bgra.
Definition blp.hpp:61
constexpr std::uint32_t fourcc(const char(&cc)[5], FourCCEndian endian=FourCCEndian::Reversed)
The host integer a scanned chunk id compares equal to for code cc.
Definition fourcc.hpp:33
@ Forward
The characters are stored as written: 'AFID' appears in the file as the bytes "AFID".
Definition fourcc.hpp:22
constexpr welder::lang Cs
C#/.NET — the welder-csharp rod's identity (user-range slot 0), respelled for wowlib's annotation sit...
Definition lang.hpp:23
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
Definition buffer.hpp:16
The version-agnostic root of every file-level entity (welded as "FileEntity").
std::array< CImVector, BlpPaletteSize > palette
The 256-entry color table of Palettized files (b, g, r + a padding byte, preserved verbatim).
Definition blp.hpp:181
ColorEncoding colorEncoding
How the pixel payload is encoded.
Definition blp.hpp:163
std::vector< FileBuffer > mips
The raw mip payloads, indexed by level (empty vector = level absent).
Definition blp.hpp:186
PixelFormat preferredFormat
The client-side pixel format hint; selects the DXT block format for Dxt-encoded files.
Definition blp.hpp:169
Result< Image > decode() const
Decode mip level 0 to an RGBA8 Image.
Definition blp.cpp:744
Result< void > ensureValid() const
Validate and raise on the first error instead of returning a report — the assert-style face of valida...
Definition blp.hpp:274
detail::StoredLayout storedLayout
The read-recorded on-disk placement (see detail::StoredLayout).
Definition blp.hpp:189
ValidationReport validate() const
Definition blp.cpp:868
std::uint32_t height
The level-0 height in pixels.
Definition blp.hpp:178
std::uint32_t width
The level-0 width in pixels.
Definition blp.hpp:175
bool operator==(const BLP &) const =default
std::uint8_t alphaDepth
Alpha bits per pixel (0, 1, 4 or 8): the alpha plane depth for Palettized files, and a selection hint...
Definition blp.hpp:166
Result< void > read(std::span< const std::byte > data)
Parse a BLP2 file from memory, replacing this entity's contents.
Definition blp.cpp:596
std::size_t mipCount() const
The number of stored mip levels (level indices 0 .
Definition blp.hpp:240
std::uint32_t version
The header version field; 1 in every shipped file.
Definition blp.hpp:160
std::uint8_t mipFlags
The header's mip byte: 0 = level 0 only, 1 = generated mips, 2 = handmade mips (plus rare high flag b...
Definition blp.hpp:172
std::uint32_t mipHeight(std::uint32_t level) const
The pixel height of a mip level (level 0 halves per step, floored at 1).
Definition blp.hpp:261
Result< void > encode(const Image &image)
Rebuild the whole texture from an RGBA8 image with the default settings (DXT, alpha depth 8 -> BC3,...
Definition blp.cpp:777
std::uint32_t mipWidth(std::uint32_t level) const
The pixel width of a mip level (level 0 halves per step, floored at 1).
Definition blp.hpp:255
Result< FileBuffer > mip(std::uint32_t level) const
One level's raw payload bytes (palette indices + alpha plane, DXT blocks, or BGRA pixels,...
Definition blp.cpp:849
Result< void > setMip(std::uint32_t level, std::span< const std::byte > data)
Replace one level's raw payload bytes verbatim.
Definition blp.cpp:856
Result< FileBuffer > write() const
Serialize this entity.
Definition blp.cpp:668
bool operator==(const EncodeSettings &) const =default
PixelFormat format
The DXT block format for Dxt encoding (Dxt1, Dxt3, Dxt5 or Bc5).
Definition blp.hpp:147
ColorEncoding encoding
The payload encoding to produce (Palettized, Dxt or Bgra; Jpeg is not supported).
Definition blp.hpp:144
bool mipmaps
Whether to generate the full mip chain down to 1x1 (box-filtered).
Definition blp.hpp:153
std::uint8_t alphaDepth
Alpha bits per pixel: 0, 1, 4 or 8.
Definition blp.hpp:150
std::vector< std::uint8_t > pixels
The RGBA8 pixel bytes, width * height * 4, row-major from the top-left.
Definition blp.hpp:137
std::uint32_t width
The width in pixels.
Definition blp.hpp:131
bool operator==(const Image &) const =default
std::uint32_t height
The height in pixels.
Definition blp.hpp:134
The 148-byte BLP2 file header, exactly as on disk (the 1024-byte palette follows it).
Definition blp.hpp:83
std::array< std::uint32_t, BlpMaxMips > mipSizes
Payload byte sizes, 0 = unused.
Definition blp.hpp:94
std::uint8_t alphaDepth
Alpha bits per pixel: 0/1/4/8.
Definition blp.hpp:87
std::uint32_t version
Always 1.
Definition blp.hpp:85
std::uint32_t width
Level-0 width in pixels.
Definition blp.hpp:90
std::uint32_t height
Level-0 height in pixels.
Definition blp.hpp:91
std::array< std::uint32_t, BlpMaxMips > mipOffsets
Absolute file offsets, 0 = unused.
Definition blp.hpp:92
std::uint8_t colorEncoding
ColorEncoding byte.
Definition blp.hpp:86
std::uint8_t preferredFormat
PixelFormat byte.
Definition blp.hpp:88
std::uint8_t mipFlags
0 = no mips, 1 = generated, 2 = handmade.
Definition blp.hpp:89
One run of file bytes covered by neither the header region nor any mip payload (an inter-mip gap or a...
Definition blp.hpp:110
std::uint32_t offset
Absolute file offset of the run.
Definition blp.hpp:111
bool operator==(const Run &) const =default
FileBuffer bytes
The run's verbatim bytes.
Definition blp.hpp:112
The on-disk placement a read recorded, replayed by write() while the payloads are unmodified so unusu...
Definition blp.hpp:107
std::vector< Run > gaps
Uncovered byte runs, verbatim.
Definition blp.hpp:122
bool operator==(const StoredLayout &) const =default
std::uint32_t fileSize
The original total file size.
Definition blp.hpp:121
std::array< std::uint32_t, BlpMaxMips > sizes
The header's size table, verbatim.
Definition blp.hpp:119
std::array< std::uint32_t, BlpMaxMips > offsets
The header's offset table, verbatim.
Definition blp.hpp:117
bool engaged
Whether a read recorded this layout.
Definition blp.hpp:123
The binary-level math and color primitives shared across WoW file formats (wowdev....
The validation vocabulary: the severity scale, the single finding and the report validate() fills.