SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
VTF.cpp
Go to the documentation of this file.
1// ReSharper disable CppDFATimeOver
2// ReSharper disable CppRedundantParentheses
3// ReSharper disable CppRedundantQualifier
4// ReSharper disable CppUseStructuredBinding
5
6#include <vtfpp/VTF.h>
7
8#include <algorithm>
9#include <cstring>
10#include <ranges>
11#include <unordered_map>
12#include <utility>
13
14#ifdef SOURCEPP_BUILD_WITH_TBB
15#include <execution>
16#endif
17
18#ifdef SOURCEPP_BUILD_WITH_THREADS
19#include <future>
20#include <thread>
21#endif
22
23#include <BufferStream.h>
24#include <miniz.h>
25#include <zstd.h>
26
29#include <vtfpp/ImagePixel.h>
30#include <vtfpp/ImageQuantize.h>
31
32using namespace sourcepp;
33using namespace vtfpp;
34
35namespace {
36
37[[nodiscard]] std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level, CompressionMethod method) {
38 switch (method) {
39 using enum CompressionMethod;
40 case DEFLATE: {
41 mz_ulong compressedSize = mz_compressBound(data.size());
42 std::vector<std::byte> out(compressedSize);
43
44 int status = MZ_OK;
45 while ((status = mz_compress2(reinterpret_cast<unsigned char*>(out.data()), &compressedSize, reinterpret_cast<const unsigned char*>(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
46 compressedSize *= 2;
47 out.resize(compressedSize);
48 }
49
50 if (status != MZ_OK) {
51 return {};
52 }
53 out.resize(compressedSize);
54 return out;
55 }
56 case ZSTD: {
57 if (level < 0) {
58 level = 6;
59 }
60
61 const auto expectedSize = ZSTD_compressBound(data.size());
62 std::vector<std::byte> out(expectedSize);
63
64 const auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
65 if (ZSTD_isError(compressedSize)) {
66 return {};
67 }
68
69 out.resize(compressedSize);
70 return out;
71 }
72 case CONSOLE_LZMA: {
73 if (const auto out = compression::compressValveLZMA(data, level)) {
74 return *out;
75 }
76 return {};
77 }
78 }
79 return {};
80}
81
82template<std::unsigned_integral T, bool ExistingDataIsSwizzled>
83constexpr void swizzleUncompressedImageData(std::span<std::byte> inputData, std::span<std::byte> outputData, ImageFormat format, uint16_t width, uint16_t height, uint16_t depth) {
84 width *= ImageFormatDetails::bpp(format) / (sizeof(T) * 8);
85
86 const auto zIndex = [
87 widthL2 = static_cast<int>(math::log2ceil(width)),
88 heightL2 = static_cast<int>(math::log2ceil(height)),
89 depthL2 = static_cast<int>(math::log2ceil(depth))
90 ](uint32_t x, uint32_t y, uint32_t z) {
91 auto widthL2m = widthL2;
92 auto heightL2m = heightL2;
93 auto depthL2m = depthL2;
94 uint32_t offset = 0;
95 uint32_t shiftCount = 0;
96 do {
97 if (depthL2m --> 0) {
98 offset |= (z & 1) << shiftCount++;
99 z >>= 1;
100 }
101 if (heightL2m --> 0) {
102 offset |= (y & 1) << shiftCount++;
103 y >>= 1;
104 }
105 if (widthL2m --> 0) {
106 offset |= (x & 1) << shiftCount++;
107 x >>= 1;
108 }
109 } while (x || y || z);
110 return offset;
111 };
112
113 const auto* inputPtr = reinterpret_cast<const T*>(inputData.data());
114 auto* outputPtr = reinterpret_cast<T*>(outputData.data());
115
116 for (uint16_t x = 0; x < width; x++) {
117 for (uint16_t y = 0; y < height; y++) {
118 for (uint16_t z = 0; z < depth; z++) {
119 if constexpr (ExistingDataIsSwizzled) {
120 *outputPtr++ = reinterpret_cast<const T*>(inputData.data())[zIndex(x, y, z)];
121 } else {
122 reinterpret_cast<T*>(outputData.data())[zIndex(x, y, z)] = *inputPtr++;
123 }
124 }
125 }
126 }
127}
128
129template<bool ExistingDataIsSwizzled>
130void swizzleUncompressedImageDataXBOX(std::span<std::byte> inputData, std::span<std::byte> outputData, ImageFormat format, uint16_t width, uint16_t height, uint16_t depth) {
131 const auto zIndex = [
132 widthL2 = static_cast<int>(math::log2ceil(width)),
133 heightL2 = static_cast<int>(math::log2ceil(height)),
134 depthL2 = static_cast<int>(math::log2ceil(depth))
135 ](int32_t x, int32_t y, int32_t z) {
136 int widthL2m = widthL2;
137 int heightL2m = heightL2;
138 int depthL2m = depthL2;
139 uint32_t offset = 0;
140 uint32_t shiftCount = 0;
141
142 while (widthL2m > 0 || heightL2m > 0 || depthL2m > 0) {
143 if (widthL2m > 0) {
144 offset |= (x & 1) << shiftCount++;
145 x >>= 1;
146 widthL2m--;
147 }
148 if (heightL2m > 0) {
149 offset |= (y & 1) << shiftCount++;
150 y >>= 1;
151 heightL2m--;
152 }
153 if (depthL2m > 0) {
154 offset |= (z & 1) << shiftCount++;
155 z >>= 1;
156 depthL2m--;
157 }
158 }
159 return offset;
160 };
161
162 const auto stride = ImageFormatDetails::bpp(format) / 8;
163
164 uint32_t linearIndex = 0;
165 for (uint16_t z = 0; z < depth; z++) {
166 for (uint16_t y = 0; y < height; y++) {
167 for (uint16_t x = 0; x < width; x++) {
168 const auto codedIndex = zIndex(x, y, z);
169 for (uint32_t b = 0; b < stride; b++) {
170 if constexpr (ExistingDataIsSwizzled) {
171 outputData[linearIndex * stride + b] = inputData[codedIndex * stride + b];
172 } else {
173 outputData[codedIndex * stride + b] = inputData[linearIndex * stride + b];
174 }
175 }
176 linearIndex++;
177 }
178 }
179 }
180}
181
182template<bool ConvertingFromSource>
183void swapImageDataEndianForConsole(std::span<std::byte> imageData, ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth, VTF::Platform platform) {
184 if (imageData.empty() || format == ImageFormat::EMPTY || platform == VTF::PLATFORM_PC) {
185 return;
186 }
187
188 if (platform == VTF::PLATFORM_X360) {
189 switch (format) {
190 using enum ImageFormat;
191 case BGRA8888:
192 case BGRX8888:
193 case UVWQ8888:
194 case UVLX8888: {
195 const auto newData = ImageConversion::convertSeveralImageDataToFormat(imageData, ARGB8888, BGRA8888, mipCount, frameCount, faceCount, width, height, depth);
196 std::ranges::copy(newData, imageData.begin());
197 break;
198 }
199 case DXT1:
201 case DXT3:
202 case DXT5:
203 case UV88: {
204 std::span dxtData{reinterpret_cast<uint16_t*>(imageData.data()), imageData.size() / sizeof(uint16_t)};
205 std::for_each(
206#ifdef SOURCEPP_BUILD_WITH_TBB
207 std::execution::par_unseq,
208#endif
209 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
210 BufferStream::swap_endian(&value);
211 });
212 break;
213 }
214 default:
215 break;
216 }
217 }
218
219 // todo(vtfpp): should we enable 16-bit wide and 8-bit wide formats outside XBOX?
220 if ((platform == VTF::PLATFORM_XBOX || platform == VTF::PLATFORM_PS3_ORANGEBOX || platform == VTF::PLATFORM_PS3_PORTAL2) && !ImageFormatDetails::compressed(format) && (ImageFormatDetails::bpp(format) % 32 == 0 || (platform == VTF::PLATFORM_XBOX && (ImageFormatDetails::bpp(format) % 16 == 0 || ImageFormatDetails::bpp(format) % 8 == 0)))) {
221 const bool compressed = ImageFormatDetails::compressed(format);
222 std::vector<std::byte> out(imageData.size());
223 for(int mip = mipCount - 1; mip >= 0; mip--) {
224 const auto [mipWidth, mipHeight, mipDepth] = ImageDimensions::getMipDims(mip, width, height, depth, compressed);
225 for (int frame = 0; frame < frameCount; frame++) {
226 for (int face = 0; face < faceCount; face++) {
227 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height)) {
228 std::span imageDataSpan{imageData.data() + offset, length * mipDepth};
229 std::span outSpan{out.data() + offset, length * mipDepth};
230 if (platform == VTF::PLATFORM_XBOX) {
231 ::swizzleUncompressedImageDataXBOX<ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
232 } else if (ImageFormatDetails::bpp(format) % 32 == 0) {
233 ::swizzleUncompressedImageData<uint32_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
234 } else if (ImageFormatDetails::bpp(format) % 16 == 0) {
235 ::swizzleUncompressedImageData<uint16_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
236 } else /*if (ImageFormatDetails::bpp(format) % 8 == 0)*/ {
237 ::swizzleUncompressedImageData<uint8_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
238 }
239 }
240 }
241 }
242 }
243 std::memcpy(imageData.data(), out.data(), out.size());
244 }
245}
246
247template<bool ConvertingFromDDS>
248[[nodiscard]] std::vector<std::byte> convertBetweenDDSAndVTFMipOrderForXBOX(bool padded, std::span<const std::byte> imageData, ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth, bool& ok) {
249 std::vector<std::byte> reorderedImageData;
250 reorderedImageData.resize(ImageFormatDetails::getDataLengthXBOX(padded, format, mipCount, frameCount, faceCount, width, height, depth));
251 BufferStream reorderedStream{reorderedImageData};
252
253 if constexpr (ConvertingFromDDS) {
254 for (int i = mipCount - 1; i >= 0; i--) {
255 const auto mipDepth = ImageDimensions::getMipDim(i, depth);
256 for (int j = 0; j < frameCount; j++) {
257 for (int k = 0; k < faceCount; k++) {
258 for (int l = 0; l < mipDepth; l++) {
259 uint32_t oldOffset, length;
260 if (!ImageFormatDetails::getDataPositionXBOX(oldOffset, length, padded, format, i, mipCount, j, frameCount, k, faceCount, width, height, l, depth)) {
261 ok = false;
262 return {};
263 }
264 reorderedStream << imageData.subspan(oldOffset, length);
265 }
266 }
267 }
268 }
269 } else {
270 for (int j = 0; j < frameCount; j++) {
271 for (int k = 0; k < faceCount; k++) {
272 for (int i = 0; i < mipCount; i++) {
273 const auto mipDepth = ImageDimensions::getMipDim(i, depth);
274 for (int l = 0; l < mipDepth; l++) {
275 uint32_t oldOffset, length;
276 if (!ImageFormatDetails::getDataPosition(oldOffset, length, format, i, mipCount, j, frameCount, k, faceCount, width, height, l, depth)) {
277 ok = false;
278 return {};
279 }
280 reorderedStream << imageData.subspan(oldOffset, length);
281 }
282 }
283 }
284 if (padded && j + 1 != frameCount && reorderedStream.tell() > 512) {
285 reorderedStream.pad(math::paddingForAlignment(512, reorderedStream.tell()));
286 }
287 }
288 }
289
290 ok = true;
291 return reorderedImageData;
292}
293
294} // namespace
295
297 switch (this->type) {
299 if (this->data.size() <= sizeof(uint32_t)) {
300 return {};
301 }
302 return SHT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
303 case TYPE_CRC:
306 if (this->data.size() != sizeof(uint32_t)) {
307 return {};
308 }
309 return *reinterpret_cast<const uint32_t*>(this->data.data());
311 if (this->data.size() != sizeof(uint32_t)) {
312 return {};
313 }
314 return std::make_tuple(
315 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 0),
316 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 1),
317 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 2),
318 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 3));
320 case TYPE_AUTHOR_INFO:
321 if (this->data.size() <= sizeof(uint32_t)) {
322 return "";
323 }
324 return std::string(reinterpret_cast<const char*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data()));
326 if (this->data.size() <= sizeof(uint32_t)) {
327 return {};
328 }
329 return HOT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
331 if (this->data.size() != sizeof(uint32_t) + sizeof(PCC)) {
332 return {};
333 }
334 BufferStreamReadOnly stream{this->data};
335 if (stream.read<uint32_t>() != sizeof(PCC)) {
336 return {};
337 }
338 PCC pcc{};
339 stream
340 >> pcc.origin[0] >> pcc.origin[1] >> pcc.origin[2] >> pcc.origin[3]
341 >> pcc.inverseTransform[0][0] >> pcc.inverseTransform[0][1] >> pcc.inverseTransform[0][2] >> pcc.inverseTransform[0][3]
342 >> pcc.inverseTransform[1][0] >> pcc.inverseTransform[1][1] >> pcc.inverseTransform[1][2] >> pcc.inverseTransform[1][3]
343 >> pcc.inverseTransform[2][0] >> pcc.inverseTransform[2][1] >> pcc.inverseTransform[2][2] >> pcc.inverseTransform[2][3]
344 >> pcc.inverseTransform[3][0] >> pcc.inverseTransform[3][1] >> pcc.inverseTransform[3][2] >> pcc.inverseTransform[3][3];
345 return pcc;
346 }
347 default:
348 break;
349 }
350 return {};
351}
352
353std::vector<std::byte> Resource::getDataAsPalette(uint16_t frame) const {
354 static constexpr auto PALETTE_FRAME_SIZE = 256 * sizeof(ImagePixel::BGRA8888);
355 if (this->data.size() % PALETTE_FRAME_SIZE != 0 || PALETTE_FRAME_SIZE * frame > this->data.size()) {
356 return {};
357 }
358 return {this->data.data() + PALETTE_FRAME_SIZE * frame, this->data.data() + PALETTE_FRAME_SIZE * (frame + 1)};
359}
360
362 return std::get<SHT>(this->convertData());
363}
364
366 return std::get<PCC>(this->convertData());
367}
368
369uint32_t Resource::getDataAsCRC() const {
370 return std::get<uint32_t>(this->convertData());
371}
372
373uint32_t Resource::getDataAsFlags() const {
374 return std::get<uint32_t>(this->convertData());
375}
376
377std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Resource::getDataAsLODControlInfo() const {
378 return std::get<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>>(this->convertData());
379}
380
382 return std::get<std::string>(this->convertData());
383}
384
385std::string Resource::getDataAsAuthorInfo() const {
386 return std::get<std::string>(this->convertData());
387}
388
390 return std::get<HOT>(this->convertData());
391}
392
394 if (this->data.size() < sizeof(uint32_t) * 2) {
395 return 0;
396 }
397 return static_cast<int16_t>(BufferStream{this->data}.skip<uint32_t>().read<uint32_t>() & 0xffff);
398}
399
401 if (this->data.size() < sizeof(uint32_t) * 2) {
403 }
404 const auto method = static_cast<int16_t>((BufferStream{this->data}.skip<uint32_t>().read<uint32_t>() & 0xffff0000) >> 16);
405 if (method <= 0) {
407 }
408 return static_cast<CompressionMethod>(method);
409}
410
411uint32_t Resource::getDataAsAuxCompressionLength(uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint16_t face, uint16_t faceCount) const {
412 if (this->data.size() < ((mipCount - 1 - mip) * frameCount * faceCount + frame * faceCount + face + 2) * sizeof(uint32_t)) {
413 return 0;
414 }
415 return BufferStream{this->data}.skip<uint32_t>((mipCount - 1 - mip) * frameCount * faceCount + frame * faceCount + face + 2).read<uint32_t>();
416}
417
419 this->opened = true;
420}
421
422VTF::VTF(std::vector<std::byte>&& vtfData, bool parseHeaderOnly, bool hdr)
423 : data(std::move(vtfData)) {
424 BufferStreamReadOnly stream{this->data};
425
426 if (const auto signature = stream.read<uint32_t>(); signature == VTF_SIGNATURE) {
427 stream >> this->platform;
428 if (this->platform != PLATFORM_PC) {
429 return;
430 }
431 stream >> this->version;
432 if (this->version > 6) {
433 return;
434 }
435 } else if (signature == VTFX_SIGNATURE || signature == VTF3_SIGNATURE) {
436 stream.set_big_endian(true);
437 stream >> this->platform;
438 if (this->platform != PLATFORM_X360 && this->platform != PLATFORM_PS3_ORANGEBOX && this->platform != PLATFORM_PS3_PORTAL2) {
439 return;
440 }
441 stream >> this->version;
442 if (this->version != 8) {
443 return;
444 }
445 // Now fix up the actual version as it would be on PC
446 if (signature == VTF3_SIGNATURE) {
447 this->platform = PLATFORM_PS3_PORTAL2;
448 this->version = 5;
449 } else {
450 this->version = 4;
451 }
452 } else if (signature == XTF_SIGNATURE) {
453 stream >> this->platform;
454 if (this->platform != PLATFORM_XBOX) {
455 return;
456 }
457 stream >> this->version;
458 if (this->version != 0) {
459 return;
460 }
461 // Now fix up the actual version as it would be on PC
462 this->version = 2;
463 } else {
464 return;
465 }
466
467 const auto headerSize = stream.read<uint32_t>();
468
469 const auto readResources = [this, &stream](uint32_t resourceCount) {
470 // Read resource header info
471 this->resources.reserve(resourceCount);
472 for (int i = 0; i < resourceCount; i++) {
473 auto& [type, flags_, data_] = this->resources.emplace_back();
474
475 auto typeAndFlags = stream.read<uint32_t>();
476 if (stream.is_big_endian()) {
477 // This field is little-endian
478 BufferStream::swap_endian(&typeAndFlags);
479 }
480 type = static_cast<Resource::Type>(typeAndFlags & 0xffffff); // last 3 bytes
481 flags_ = static_cast<Resource::Flags>(typeAndFlags >> 24); // first byte
482 data_ = stream.read_span<std::byte>(4);
483
484 if (stream.is_big_endian() && !(flags_ & Resource::FLAG_LOCAL_DATA)) {
485 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(data_.data()));
486 }
487 }
488
489 // Sort resources by their offset, in case certain VTFs are written
490 // weirdly and have resource data written out of order. So far I have
491 // found only one VTF in an official Valve game where this is the case.
492 // UPDATE: We do this intentionally to write image data at the end now!
493 // It fixes mip skipping issues, and should still work fine in every branch.
494 std::ranges::sort(this->resources, [](const Resource& lhs, const Resource& rhs) {
496 return lhs.type < rhs.type;
497 }
499 return true;
500 }
502 return false;
503 }
504 return *reinterpret_cast<uint32_t*>(lhs.data.data()) < *reinterpret_cast<uint32_t*>(rhs.data.data());
505 });
506
507 // Fix up data spans to point to the actual data
508 Resource* lastResource = nullptr;
509 for (auto& resource : this->resources) {
510 if (!(resource.flags & Resource::FLAG_LOCAL_DATA)) {
511 if (lastResource) {
512 const auto lastOffset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
513 const auto currentOffset = *reinterpret_cast<uint32_t*>(resource.data.data());
514 const auto curPos = stream.tell();
515 stream.seek(lastOffset);
516 lastResource->data = stream.read_span<std::byte>(currentOffset - lastOffset);
517 stream.seek(static_cast<int64_t>(curPos));
518 }
519 lastResource = &resource;
520 }
521 }
522 if (lastResource) {
523 const auto offset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
524 const auto curPos = stream.tell();
525 stream.seek(offset);
526 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
527 stream.seek(static_cast<int64_t>(curPos));
528 }
529 };
530
531 // A couple tweaks to fix engine bugs or branch differences
532 const auto postHeaderReadTransform = [this, hdr] {
533 // Change the format to DXT1_ONE_BIT_ALPHA to get compressonator to recognize it.
534 // No source game recognizes this format, so we will do additional transform in bake back to DXT1.
535 // We also need to check MULTI_BIT_ALPHA flag because stupid third party tools will sometimes set it???
536 if (this->format == ImageFormat::DXT1 && this->flags & (FLAG_V0_ONE_BIT_ALPHA | FLAG_V0_MULTI_BIT_ALPHA)) {
537 this->format = ImageFormat::DXT1_ONE_BIT_ALPHA;
538 }
539 // If the version is below 7.5, NV_NULL / ATI2N / ATI1N are at different positions in the enum.
540 // It is safe to do this because these formats didn't exist before v7.5.
541 if (this->version < 5 && (this->format == ImageFormat::RGBA1010102 || this->format == ImageFormat::BGRA1010102 || this->format == ImageFormat::R16F)) {
542 this->format = static_cast<ImageFormat>(static_cast<int32_t>(this->format) - 3);
543 }
544 // If this is an HDR VTF, and it has one of the following formats, it's actually compressed.
545 if (hdr) {
546 if (this->format == ImageFormat::BGRA8888) {
548 } else if (this->format == ImageFormat::RGBA16161616) {
549 if (this->platform == PLATFORM_PC) {
551 } else {
553 }
554 }
555 }
556 // We need to apply this transform because of the broken Borealis skybox in HL2.
557 // Thanks Valve! If this transform isn't applied it breaks conversion to console formats.
558 if (this->flags & FLAG_V0_NO_MIP && this->mipCount > 1) {
559 this->removeFlags(FLAG_V0_NO_MIP);
560 }
561 };
562
563 switch (this->platform) {
564 case PLATFORM_UNKNOWN:
565 return;
566 case PLATFORM_PC: {
567 stream
568 .read(this->width)
569 .read(this->height)
570 .read(this->flags)
571 .read(this->frameCount)
572 .read(this->startFrame)
573 .skip(4)
574 .read(this->reflectivity[0])
575 .read(this->reflectivity[1])
576 .read(this->reflectivity[2])
577 .skip(4)
578 .read(this->bumpMapScale)
579 .read(this->format)
580 .read(this->mipCount);
581
582 postHeaderReadTransform();
583
584 // This will always be DXT1
585 stream.skip<ImageFormat>();
586 stream >> this->thumbnailWidth >> this->thumbnailHeight;
587 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
588 this->thumbnailFormat = ImageFormat::EMPTY;
589 } else {
590 this->thumbnailFormat = ImageFormat::DXT1;
591 }
592
593 if (this->version < 2) {
594 this->depth = 1;
595 } else {
596 stream.read(this->depth);
597 }
598
599 if (parseHeaderOnly) {
600 this->opened = true;
601 return;
602 }
603
604 if (this->version >= 3) {
605 stream.skip(3);
606 auto resourceCount = stream.read<uint32_t>();
607 stream.skip(8);
608 readResources(resourceCount);
609
610 this->opened = stream.tell() == headerSize;
611
612 if (this->opened && this->version >= 6) {
613 const auto* auxResource = this->getResource(Resource::TYPE_AUX_COMPRESSION);
614 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); auxResource && imageResource) {
615 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
616 const auto faceCount = this->getFaceCount();
617 std::vector<std::byte> decompressedImageData(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth));
618 uint32_t oldOffset = 0;
619 for (int i = this->mipCount - 1; i >= 0; i--) {
620 for (int j = 0; j < this->frameCount; j++) {
621 for (int k = 0; k < faceCount; k++) {
622 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
623 if (uint32_t newOffset, newLength; ImageFormatDetails::getDataPosition(newOffset, newLength, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->getDepth())) {
624 // Keep in mind that slices are compressed together
625 mz_ulong decompressedImageDataSize = newLength * this->depth;
626 switch (auxResource->getDataAsAuxCompressionMethod()) {
627 using enum CompressionMethod;
628 case DEFLATE:
629 if (mz_uncompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
630 this->opened = false;
631 return;
632 }
633 break;
634 case ZSTD:
635 if (const auto decompressedSize = ZSTD_decompress(decompressedImageData.data() + newOffset, decompressedImageDataSize, imageResource->data.data() + oldOffset, oldLength); ZSTD_isError(decompressedSize) || decompressedSize != decompressedImageDataSize) {
636 this->opened = false;
637 return;
638 }
639 break;
640 case CONSOLE_LZMA:
641 // Shouldn't be here!
643 break;
644 }
645 }
646 oldOffset += oldLength;
647 }
648 }
649 }
650 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, decompressedImageData);
651 }
652 }
653 }
654 } else {
655 stream.skip(math::paddingForAlignment(16, stream.tell()));
656 this->opened = stream.tell() == headerSize;
657
658 this->resources.reserve(2);
659
660 if (this->hasThumbnailData()) {
661 this->resources.push_back({
663 .flags = Resource::FLAG_NONE,
664 .data = stream.read_span<std::byte>(ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight)),
665 });
666 }
667 if (this->hasImageData()) {
668 this->resources.push_back({
670 .flags = Resource::FLAG_NONE,
671 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
672 });
673 }
674 }
675
676 if (const auto* resource = this->getResource(Resource::TYPE_SOURCEPP_FLAGS)) {
677 this->flagsExtra = resource->getDataAsFlags();
678 this->removeResourceInternal(Resource::TYPE_SOURCEPP_FLAGS);
679 }
680 if (const auto* resource = this->getResource(Resource::TYPE_AUX_COMPRESSION)) {
681 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
682 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
683 this->removeResourceInternal(Resource::TYPE_AUX_COMPRESSION);
684 }
685 return;
686 }
687 case PLATFORM_XBOX: {
688 if (this->platform == PLATFORM_XBOX) {
689 uint16_t preloadSize = 0, imageOffset = 0;
690 stream
691 .read(this->flags)
692 .read(this->width)
693 .read(this->height)
694 .read(this->depth)
695 .read(this->frameCount)
696 .read(preloadSize)
697 .read(imageOffset)
698 .read(this->reflectivity[0])
699 .read(this->reflectivity[1])
700 .read(this->reflectivity[2])
701 .read(this->bumpMapScale)
702 .read(this->format)
703 .read(this->thumbnailWidth)
704 .read(this->thumbnailHeight)
705 .read(this->fallbackWidth)
706 .read(this->fallbackHeight)
707 .read(this->consoleMipScale)
708 .skip<uint8_t>(); // padding
709
710 const bool headerSizeIsAccurate = stream.tell() == headerSize;
711
712 this->mipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth);
713 this->fallbackMipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->fallbackWidth, this->fallbackHeight);
714
715 postHeaderReadTransform();
716
717 // Can't use VTF::getFaceCount yet because there's no image data
718 const auto faceCount = (this->flags & FLAG_V0_ENVMAP) ? 6 : 1;
719
720 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
721 this->thumbnailFormat = ImageFormat::EMPTY;
722 } else {
723 this->thumbnailFormat = ImageFormat::RGB888;
724 const auto thumbnailSize = ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight);
725 if (!parseHeaderOnly) {
726 this->resources.push_back({
728 .flags = Resource::FLAG_NONE,
729 .data = stream.read_span<std::byte>(thumbnailSize),
730 });
731 } else {
732 stream.skip(thumbnailSize);
733 }
734 }
735
736 if (this->format == ImageFormat::P8) {
737 const auto paletteSize = 256 * sizeof(ImagePixel::BGRA8888) * this->frameCount;
738 if (!parseHeaderOnly) {
739 this->resources.push_back({
741 .flags = Resource::FLAG_NONE,
742 .data = stream.read_span<std::byte>(paletteSize),
743 });
744 } else {
745 stream.skip_u(paletteSize);
746 }
747 }
748
749 bool ok;
750 auto fallbackSize = ImageFormatDetails::getDataLengthXBOX(false, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight);
751 std::vector<std::byte> reorderedFallbackData;
752 if (this->hasFallbackData()) {
753 if (stream.tell() + fallbackSize != preloadSize) {
754 // A couple XTFs that shipped with HL2 are missing the NO_MIP flag. We can detect them by checking the size of the fallback
755 fallbackSize = ImageFormatDetails::getDataLengthXBOX(false, this->format, 1, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight);
756 if (stream.tell() + fallbackSize != preloadSize) {
757 this->opened = false;
758 return;
759 }
760 this->fallbackMipCount = 1;
761 this->mipCount = 1;
762 this->flags |= FLAG_V0_NO_MIP;
763 }
764 reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(false, stream.read_span<std::byte>(fallbackSize), this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, ok);
765 if (!ok) {
766 this->opened = false;
767 return;
768 }
769 ::swapImageDataEndianForConsole<true>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, this->platform);
770 }
771
772 this->opened = headerSizeIsAccurate;
773 if (parseHeaderOnly) {
774 return;
775 }
776
777 const auto imageSize = ImageFormatDetails::getDataLengthXBOX(true, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth);
778 std::vector<std::byte> reorderedImageData;
779 if (this->hasImageData()) {
780 reorderedImageData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(true, stream.seek(imageOffset).read_span<std::byte>(imageSize), this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, ok);
781 if (!ok) {
782 this->opened = false;
783 return;
784 }
785 ::swapImageDataEndianForConsole<true>(reorderedImageData, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, this->platform);
786 }
787
788 // By this point we cannot use spans over data, it will change here
789 if (this->hasFallbackData()) {
790 this->setResourceInternal(Resource::TYPE_FALLBACK_DATA, reorderedFallbackData);
791 }
792 if (this->hasImageData()) {
793 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, reorderedImageData);
794 }
795 return;
796 }
797 }
798 case PLATFORM_X360:
799 case PLATFORM_PS3_ORANGEBOX:
800 case PLATFORM_PS3_PORTAL2: {
801 uint8_t resourceCount;
802 stream
803 .read(this->flags)
804 .read(this->width)
805 .read(this->height)
806 .read(this->depth)
807 .read(this->frameCount)
808 .skip<uint16_t>() // preload
809 .read(this->consoleMipScale)
810 .read(resourceCount)
811 .read(this->reflectivity[0])
812 .read(this->reflectivity[1])
813 .read(this->reflectivity[2])
814 .read(this->bumpMapScale)
815 .read(this->format)
816 .skip<math::Vec4ui8>() // lowResImageSample (replacement for thumbnail resource, linear color pixel)
817 .skip<uint32_t>(); // compressedLength
818
819 postHeaderReadTransform();
820
821 // Align to 16 bytes
822 if (this->platform == PLATFORM_PS3_PORTAL2) {
823 stream.skip<uint32_t>();
824 }
825
826 this->mipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth);
827
828 if (parseHeaderOnly) {
829 this->opened = true;
830 return;
831 }
832
833 this->resources.reserve(resourceCount);
834 readResources(resourceCount);
835
836 this->opened = stream.tell() == headerSize;
837
838 // The resources vector isn't modified by setResourceInternal when we're not adding a new one, so this is fine
839 for (const auto& resource : this->resources) {
840 // Decompress LZMA resources
841 if (BufferStreamReadOnly rsrcStream{resource.data}; rsrcStream.read<uint32_t>() == compression::VALVE_LZMA_SIGNATURE) {
842 if (auto decompressedData = compression::decompressValveLZMA(resource.data)) {
843 this->setResourceInternal(resource.type, *decompressedData);
844
845 if (resource.type == Resource::TYPE_IMAGE_DATA) {
846 // Do this here because compressionLength in header can be garbage on PS3 orange box
847 this->compressionMethod = CompressionMethod::CONSOLE_LZMA;
848 }
849 }
850 }
851
852 if (resource.type == Resource::TYPE_THUMBNAIL_DATA) {
853 ::swapImageDataEndianForConsole<true>(resource.data, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
854 } else if (resource.type == Resource::TYPE_IMAGE_DATA) {
855 if (this->platform == PLATFORM_PS3_ORANGEBOX) {
856 bool ok;
857 const auto reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(false, resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
858 if (!ok || reorderedFallbackData.size() != resource.data.size()) {
859 this->opened = false;
860 return;
861 }
862 std::memcpy(resource.data.data(), reorderedFallbackData.data(), resource.data.size());
863 }
864 ::swapImageDataEndianForConsole<true>(resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
865 } else if (!(resource.flags & Resource::FLAG_LOCAL_DATA) && resource.data.size() >= sizeof(uint32_t)) {
866 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resource.data.data()));
867 }
868 }
869 return;
870 }
871 }
872}
873
874VTF::VTF(std::span<const std::byte> vtfData, bool parseHeaderOnly, bool hdr)
875 : VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly, hdr) {}
876
877VTF::VTF(const std::filesystem::path& vtfPath, bool parseHeaderOnly)
878 : VTF(fs::readFileBuffer(vtfPath), parseHeaderOnly, [](std::string path) {
880 return path.ends_with(".hdr.vtf") || path.ends_with(".hdr.360.vtf") || path.ends_with(".hdr.ps3.vtf");
881 }(vtfPath.filename().string())) {}
882
883VTF::VTF(const VTF& other) {
884 *this = other;
885}
886
887VTF& VTF::operator=(const VTF& other) {
888 this->opened = other.opened;
889 this->data = other.data;
890 this->version = other.version;
891 this->width = other.width;
892 this->height = other.height;
893 this->flags = other.flags;
894 this->frameCount = other.frameCount;
895 this->startFrame = other.startFrame;
896 this->reflectivity = other.reflectivity;
897 this->bumpMapScale = other.bumpMapScale;
898 this->format = other.format;
899 this->mipCount = other.mipCount;
900 this->thumbnailFormat = other.thumbnailFormat;
901 this->thumbnailWidth = other.thumbnailWidth;
902 this->thumbnailHeight = other.thumbnailHeight;
903 this->fallbackWidth = other.fallbackWidth;
904 this->fallbackHeight = other.fallbackHeight;
906 this->consoleMipScale = other.consoleMipScale;
907 this->depth = other.depth;
908
909 this->resources.clear();
910 for (const auto& [otherType, otherFlags, otherData] : other.resources) {
911 auto& [type, flags_, data_] = this->resources.emplace_back();
912 type = otherType;
913 flags_ = otherFlags;
914 data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
915 }
916
917 this->platform = other.platform;
922
923 return *this;
924}
925
926VTF::operator bool() const {
927 return this->opened;
928}
929
931 bool out = true;
932 if (writer.hasImageData() && (options.invertGreenChannel || options.gammaCorrection != 1.f)) {
933 for (int i = 0; i < writer.mipCount; i++) {
934 for (int j = 0; j < writer.frameCount; j++) {
935 for (int k = 0; k < writer.getFaceCount(); k++) {
936 for (int l = 0; l < writer.depth; l++) {
937 if (options.invertGreenChannel) {
938 ImageConversion::invertGreenChannelForImageData(writer.getImageDataRaw(i, j, k, l), writer.getFormat(), writer.getWidth(i), writer.getHeight(i));
939 }
940 if (options.gammaCorrection != 1.f) {
941 ImageConversion::gammaCorrectImageData(writer.getImageDataRaw(i, j, k, l), writer.getFormat(), writer.getWidth(i), writer.getHeight(i), options.gammaCorrection);
942 }
943 }
944 }
945 }
946 }
947 }
948 writer.setPlatform(options.platform);
949 if (options.computeReflectivity) {
950 writer.computeReflectivity();
951 }
952 if (options.initialFrameCount > 1 || options.isCubeMap || options.initialDepth > 1) {
953 if (!writer.setFrameFaceAndDepth(options.initialFrameCount, options.isCubeMap, options.initialDepth)) {
954 out = false;
955 }
956 }
957 writer.setStartFrame(options.startFrame);
958 writer.setBumpMapScale(options.bumpMapScale);
959 if (options.computeThumbnail) {
960 writer.computeThumbnail();
961 }
962 if (options.outputFormat == FORMAT_UNCHANGED) {
963 options.outputFormat = writer.getFormat();
964 } else if (options.outputFormat == FORMAT_DEFAULT) {
965 options.outputFormat = VTF::getDefaultCompressedFormat(writer.getFormat(), writer.getVersion(), options.isCubeMap);
966 }
967 if (options.computeMips) {
968 if (const auto maxMipCount = ImageDimensions::getMaximumMipCount(writer.getWidth(), writer.getHeight(), writer.getDepth()); maxMipCount > 1) {
969 if (!writer.setMipCount(maxMipCount)) {
970 out = false;
971 }
972 writer.computeMips(options.filter);
973 }
974 } else {
975 // Already has NO_MIP
976 writer.addFlags(FLAG_V0_NO_LOD);
977 }
979 if (options.computeTransparencyFlags) {
981 }
984 writer.setConsoleMipScale(options.consoleMipScale);
985 return out;
986}
987
988bool VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path& vtfPath, const CreationOptions& options) {
989 VTF writer;
990 writer.setVersion(options.version);
991 writer.addFlags(options.flags);
992 writer.addFlagsExtra(options.flagsExtra);
994 if (const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height); requestedResizeWidth != width || requestedResizeHeight != height) {
995 const auto imageDataResized = ImageConversion::resizeImageData(imageData, format, width, requestedResizeWidth, height, requestedResizeHeight, !ImageFormatDetails::large(format), writer.getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, options.resizeBounds.resizeFilter, options.resizeBounds.resizeEdge);
996 if (!writer.setImage(imageDataResized, format, requestedResizeWidth, requestedResizeHeight, options.filter)) {
997 return false;
998 }
999 } else if (!writer.setImage(imageData, format, width, height, options.filter)) {
1000 return false;
1001 }
1002 if (!createInternal(writer, options)) {
1003 return false;
1004 }
1005 return writer.bake(vtfPath);
1006}
1007
1008bool VTF::create(ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path& vtfPath, const CreationOptions& options) {
1009 std::vector<std::byte> imageData;
1010 const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height);
1011 imageData.resize(static_cast<uint32_t>(requestedResizeWidth) * requestedResizeHeight * ImageFormatDetails::bpp(format) / 8);
1012 return create(imageData, format, requestedResizeWidth, requestedResizeHeight, vtfPath, options);
1013}
1014
1015VTF VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
1016 VTF writer;
1017 writer.setVersion(options.version);
1018 writer.addFlags(options.flags);
1019 writer.addFlagsExtra(options.flagsExtra);
1021 if (const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height); requestedResizeWidth != width || requestedResizeHeight != height) {
1022 const auto imageDataResized = ImageConversion::resizeImageData(imageData, format, width, requestedResizeWidth, height, requestedResizeHeight, !ImageFormatDetails::large(format), writer.getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, options.resizeBounds.resizeFilter, options.resizeBounds.resizeEdge);
1023 if (!writer.setImage(imageDataResized, format, requestedResizeWidth, requestedResizeHeight, options.filter)) {
1024 writer.opened = false;
1025 return writer;
1026 }
1027 } else if (!writer.setImage(imageData, format, width, height, options.filter)) {
1028 writer.opened = false;
1029 return writer;
1030 }
1031 if (!createInternal(writer, options)) {
1032 writer.opened = false;
1033 }
1034 return writer;
1035}
1036
1037VTF VTF::create(ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
1038 std::vector<std::byte> imageData;
1039 const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height);
1040 imageData.resize(static_cast<uint32_t>(requestedResizeWidth) * requestedResizeHeight * ImageFormatDetails::bpp(format) / 8);
1041 return create(imageData, format, requestedResizeWidth, requestedResizeHeight, options);
1042}
1043
1044bool VTF::create(const std::filesystem::path& imagePath, const std::filesystem::path& vtfPath, const CreationOptions& options) {
1045 VTF writer;
1046 writer.setVersion(options.version);
1047 writer.addFlags(options.flags);
1048 writer.addFlagsExtra(options.flagsExtra);
1050 if (!writer.setImage(imagePath, options.filter, 0, 0, 0, 0, options.compressedFormatQuality, options.resizeBounds)) {
1051 return false;
1052 }
1053 if (!createInternal(writer, options)) {
1054 return false;
1055 }
1056 return writer.bake(vtfPath);
1057}
1058
1059VTF VTF::create(const std::filesystem::path& imagePath, const CreationOptions& options) {
1060 VTF writer;
1061 writer.setVersion(options.version);
1062 writer.addFlags(options.flags);
1063 writer.addFlagsExtra(options.flagsExtra);
1065 if (!writer.setImage(imagePath, options.filter, 0, 0, 0, 0, options.compressedFormatQuality, options.resizeBounds)) {
1066 writer.opened = false;
1067 return writer;
1068 }
1069 if (!createInternal(writer, options)) {
1070 writer.opened = false;
1071 }
1072 return writer;
1073}
1074
1076 return this->platform;
1077}
1078
1079void VTF::setPlatform(Platform newPlatform) {
1080 if (this->platform == newPlatform) {
1081 return;
1082 }
1083
1084 // hack to allow VTF::setVersion to work
1085 const auto oldPlatform = this->platform;
1086 this->platform = PLATFORM_PC;
1087 switch (newPlatform) {
1088 case PLATFORM_UNKNOWN:
1089 return;
1090 case PLATFORM_PC:
1091 switch (oldPlatform) {
1092 case PLATFORM_UNKNOWN:
1093 case PLATFORM_PC:
1094 return;
1095 case PLATFORM_XBOX:
1096 this->setVersion(2);
1097 break;
1098 case PLATFORM_X360:
1100 this->setVersion(4);
1101 break;
1103 this->setVersion(5);
1104 break;
1105 }
1106 break;
1107 case PLATFORM_XBOX:
1108 this->setVersion(2);
1109 break;
1110 case PLATFORM_X360:
1112 this->setVersion(4);
1113 break;
1115 this->setVersion(5);
1116 break;
1117 }
1118 this->platform = newPlatform;
1119
1120 // Remove spheremap if on console (VTF::setVersion has already added it back on PC)
1121 if (newPlatform != PLATFORM_PC && this->hasImageData() && (this->flags & FLAG_V0_ENVMAP)) {
1122 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, 6, this->depth);
1123 }
1124
1125 // Update flags
1126 if (this->platform == PLATFORM_XBOX || newPlatform == PLATFORM_XBOX) {
1128 }
1129
1130 // XBOX stores thumbnail as single RGB888 pixel, but we assume thumbnail is DXT1 on other platforms
1131 if (this->hasThumbnailData()) {
1132 if (this->platform == PLATFORM_XBOX) {
1133 this->computeThumbnail();
1134 } else if (oldPlatform == PLATFORM_XBOX) {
1136 this->thumbnailWidth = 0;
1137 this->thumbnailHeight = 0;
1138 }
1139 }
1140
1141 // Add/remove fallback data for XBOX
1142 if (this->platform != PLATFORM_XBOX && this->hasFallbackData()) {
1143 this->removeFallback();
1144 } else if (this->platform == PLATFORM_XBOX) {
1145 this->computeFallback();
1146 }
1147
1149
1150 if (this->platform != PLATFORM_PC) {
1151 if (const auto maxMipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth); this->mipCount != maxMipCount) {
1152 this->setMipCount(maxMipCount);
1153 }
1154 }
1155}
1156
1157uint32_t VTF::getVersion() const {
1158 return this->version;
1159}
1160
1161void VTF::setVersion(uint32_t newVersion) {
1162 if (this->platform != PLATFORM_PC) {
1163 return;
1164 }
1165 if (this->hasImageData()) {
1166 const auto faceCount = (this->flags & FLAG_V0_ENVMAP) ? (newVersion < 1 || newVersion > 4 ? 6 : 7) : 1;
1167 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, faceCount, this->depth);
1168 }
1169
1170 // Fix up flags
1171 const bool srgb = this->isSRGB();
1172 if ((this->version < 1 && newVersion >= 1) || (this->version >= 1 && newVersion < 1)) {
1174 }
1175 if ((this->version < 2 && newVersion >= 2) || (this->version >= 2 && newVersion < 2)) {
1177 }
1178 if ((this->version < 3 && newVersion >= 3) || (this->version >= 3 && newVersion < 3)) {
1180 }
1181 if ((this->version < 4 && newVersion >= 4) || (this->version >= 4 && newVersion < 4)) {
1183 }
1184 if ((this->version < 5 && newVersion >= 5) || (this->version >= 5 && newVersion < 5)) {
1186 }
1187
1188 this->version = newVersion;
1189 this->setSRGB(srgb);
1190}
1191
1195
1199
1200void VTF::setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_) {
1201 this->imageWidthResizeMethod = imageWidthResizeMethod_;
1202 this->imageHeightResizeMethod = imageHeightResizeMethod_;
1203}
1204
1206 this->imageWidthResizeMethod = imageWidthResizeMethod_;
1207}
1208
1210 this->imageHeightResizeMethod = imageHeightResizeMethod_;
1211}
1212
1213uint16_t VTF::getWidth(uint8_t mip) const {
1214 return ImageDimensions::getMipDim(mip, this->width);
1215}
1216
1217uint16_t VTF::getPaddedWidth(uint8_t mip) const {
1218 return ImageDimensions::getMipDim(mip, this->width, ImageFormatDetails::compressed(this->format));
1219}
1220
1221uint16_t VTF::getHeight(uint8_t mip) const {
1222 return ImageDimensions::getMipDim(mip, this->height);
1223}
1224
1225uint16_t VTF::getPaddedHeight(uint8_t mip) const {
1226 return ImageDimensions::getMipDim(mip, this->height, ImageFormatDetails::compressed(this->format));
1227}
1228
1229void VTF::setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter) {
1230 if (newWidth == 0 || newHeight == 0) {
1231 this->format = ImageFormat::EMPTY;
1232 this->width = 0;
1233 this->height = 0;
1235 return;
1236 }
1237
1239 if (this->hasImageData()) {
1240 if (this->width == newWidth && this->height == newHeight) {
1241 return;
1242 }
1243 auto newMipCount = this->mipCount;
1244 if (const auto maxMipCount = ImageDimensions::getMaximumMipCount(newWidth, newHeight, this->depth); this->platform != PLATFORM_PC || newMipCount > maxMipCount) {
1245 newMipCount = maxMipCount;
1246 }
1247 this->regenerateImageData(this->format, newWidth, newHeight, newMipCount, this->frameCount, this->getFaceCount(), this->depth, filter);
1248 } else {
1249 this->format = ImageFormat::RGBA8888;
1250 this->mipCount = 1;
1251 this->frameCount = 1;
1252 this->flags &= ~FLAG_V0_ENVMAP;
1253 this->width = newWidth;
1254 this->height = newHeight;
1255 this->depth = 1;
1256 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, std::vector<std::byte>(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 1, this->width, this->height, this->depth)));
1257 }
1258}
1259
1260uint32_t VTF::getFlags() const {
1261 return this->flags;
1262}
1263
1264void VTF::setFlags(uint32_t flags_) {
1265 this->flags = (this->flags & FLAG_MASK_INTERNAL) | (flags_ & ~FLAG_MASK_INTERNAL);
1266}
1267
1268void VTF::addFlags(uint32_t flags_) {
1269 this->flags |= flags_ & ~FLAG_MASK_INTERNAL;
1270}
1271
1272void VTF::removeFlags(uint32_t flags_) {
1273 this->flags &= ~flags_ | FLAG_MASK_INTERNAL;
1274}
1275
1276uint32_t VTF::getFlagsExtra() const {
1277 return this->flagsExtra;
1278}
1279
1280void VTF::setFlagsExtra(uint32_t flags_) {
1281 this->flagsExtra = flags_;
1282}
1283
1284void VTF::addFlagsExtra(uint32_t flags_) {
1285 this->flagsExtra |= flags_;
1286}
1287
1288void VTF::removeFlagsExtra(uint32_t flags_) {
1289 this->flagsExtra &= ~flags_;
1290}
1291
1292bool VTF::isSRGB() const {
1293 return !ImageFormatDetails::large(this->format) && (this->version < 4 ? false : this->version < 5 ? this->flags & FLAG_V4_SRGB : this->flags & FLAG_V5_PWL_CORRECTED || this->flags & FLAG_V5_SRGB);
1294}
1295
1296void VTF::setSRGB(bool srgb) {
1297 if (srgb) {
1298 if (this->version >= 5) {
1299 this->addFlags(FLAG_V5_SRGB);
1300 } else if (this->version == 4) {
1301 this->addFlags(FLAG_V4_SRGB);
1302 }
1303 } else {
1304 if (this->version >= 5) {
1306 } else if (this->version == 4) {
1308 }
1309 }
1310}
1311
1313 if (ImageFormatDetails::transparent(this->format)) {
1314 if (ImageFormatDetails::decompressedAlpha(this->format) > 1) {
1317 } else {
1320 }
1321 } else {
1323 }
1324}
1325
1326ImageFormat VTF::getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap) {
1327 if (version >= 6) {
1328 if (isCubeMap) {
1330 }
1332 }
1333 if (ImageFormatDetails::decompressedAlpha(inputFormat) > 0) {
1334 return ImageFormat::DXT5;
1335 }
1336 return ImageFormat::DXT1;
1337}
1338
1340 return this->format;
1341}
1342
1343void VTF::setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter, float quality) {
1344 if (newFormat == FORMAT_UNCHANGED || newFormat == this->format) {
1345 return;
1346 }
1347 if (newFormat == FORMAT_DEFAULT) {
1348 newFormat = VTF::getDefaultCompressedFormat(this->format, this->version, this->getFaceCount() > 1);
1349 }
1350 if (!this->hasImageData()) {
1351 this->format = newFormat;
1352 return;
1353 }
1354 const auto oldFormat = this->format;
1355 if (ImageFormatDetails::compressed(newFormat)) {
1356 this->regenerateImageData(newFormat, this->width + math::paddingForAlignment(4, this->width), this->height + math::paddingForAlignment(4, this->height), this->mipCount, this->frameCount, this->getFaceCount(), this->depth, filter, quality);
1357 } else {
1358 this->regenerateImageData(newFormat, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), this->depth, filter, quality);
1359 }
1360
1361 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
1362 const auto fallbackConverted = ImageConversion::convertSeveralImageDataToFormat(fallbackResource->data, oldFormat, this->format, ImageDimensions::getMaximumMipCount(this->fallbackWidth, this->fallbackHeight), this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, quality);
1363 this->setResourceInternal(Resource::TYPE_FALLBACK_DATA, fallbackConverted);
1364 }
1365}
1366
1367uint8_t VTF::getMipCount() const {
1368 return this->mipCount;
1369}
1370
1371bool VTF::setMipCount(uint8_t newMipCount) {
1372 if (!this->hasImageData()) {
1373 return false;
1374 }
1375
1376 if (newMipCount < 1) {
1377 newMipCount = 1;
1378 } else if (const auto maxMipCount = ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth); (this->platform != PLATFORM_PC && newMipCount > 1) || (this->platform == PLATFORM_PC && newMipCount > maxMipCount)) {
1379 newMipCount = maxMipCount;
1380 }
1381 this->regenerateImageData(this->format, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->depth);
1382 return true;
1383}
1384
1386 // Something we could do here for v5 and up is check if FLAG_V5_LOAD_MOST_MIPS is set and change mip count accordingly
1387 // (eliminate anything below 32x32). Noting we can't do that check for v4 and down because TF2 branch added small mip
1388 // loading, didn't bother adding the most mips flag, and doesn't handle VTFs missing lower mips correctly. Thanks Valve!
1389 return this->setMipCount(ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth));
1390}
1391
1393 auto* imageResource = this->getResourceInternal(Resource::TYPE_IMAGE_DATA);
1394 if (!imageResource || !this->hasImageData()) {
1395 return;
1396 }
1397
1398 if (this->mipCount <= 1) {
1399 if (!this->setRecommendedMipCount() || this->mipCount <= 1) {
1400 return;
1401 }
1402 }
1403
1404 auto* outputDataPtr = imageResource->data.data();
1405 const auto faceCount = this->getFaceCount();
1406
1407#ifdef SOURCEPP_BUILD_WITH_THREADS
1408 std::vector<std::future<void>> futures;
1409 futures.reserve(this->frameCount * faceCount * this->depth);
1410#endif
1411 for (int j = 0; j < this->frameCount; j++) {
1412 for (int k = 0; k < faceCount; k++) {
1413#ifdef SOURCEPP_BUILD_WITH_THREADS
1414 futures.push_back(std::async(std::launch::async, [this, filter, outputDataPtr, faceCount, j, k] {
1415#endif
1416 for (int i = 1; i < this->mipCount; i++) {
1417 const auto [mipWidth, mipHeight, mipDepth] = ImageDimensions::getMipDims(i, this->width, this->height, this->depth);
1418 const auto [mipWidthM1, mipHeightM1] = ImageDimensions::getMipDims(i - 1, this->width, this->height);
1419 for (int l = 0; l < mipDepth; l++) {
1420 auto mip = ImageConversion::resizeImageData(this->getImageDataRaw(i - 1, j, k, l), this->format, mipWidthM1, mipWidth, mipHeightM1, mipHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
1421 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, l, this->depth) && mip.size() == length) {
1422 std::memcpy(outputDataPtr + offset, mip.data(), length);
1423 }
1424 }
1425 }
1426#ifdef SOURCEPP_BUILD_WITH_THREADS
1427 }));
1428 if (futures.size() >= std::thread::hardware_concurrency()) {
1429 for (auto& future : futures) {
1430 future.get();
1431 }
1432 futures.clear();
1433 }
1434#endif
1435 }
1436 }
1437#ifdef SOURCEPP_BUILD_WITH_THREADS
1438 for (auto& future : futures) {
1439 future.get();
1440 }
1441#endif
1442}
1443
1444uint16_t VTF::getFrameCount() const {
1445 return this->frameCount;
1446}
1447
1448bool VTF::setFrameCount(uint16_t newFrameCount) {
1449 if (!this->hasImageData()) {
1450 return false;
1451 }
1452 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, this->getFaceCount(), this->depth);
1453 return true;
1454}
1455
1456uint8_t VTF::getFaceCount() const {
1457 if (!this->hasImageData()) {
1458 return 0;
1459 }
1460 const auto* image = this->getResource(Resource::TYPE_IMAGE_DATA);
1461 if (!image) {
1462 return 0;
1463 }
1464 if (!(this->flags & FLAG_V0_ENVMAP)) {
1465 return 1;
1466 }
1467 if (this->platform != PLATFORM_PC || this->version >= 6) {
1468 // All v7.6 VTFs are sane, and we need this special case to fix a bug in the parser where
1469 // it won't recognize cubemaps as cubemaps because the image resource is compressed!
1470 return 6;
1471 }
1472 const auto expectedLength = ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 6, this->width, this->height, this->depth);
1473 if (this->version >= 1 && this->version <= 4 && expectedLength < image->data.size()) {
1474 return 7;
1475 }
1476 if (expectedLength == image->data.size()) {
1477 return 6;
1478 }
1479 return 1;
1480}
1481
1482bool VTF::setFaceCount(bool isCubeMap) {
1483 if (!this->hasImageData()) {
1484 return false;
1485 }
1486 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, isCubeMap ? (this->version < 1 || this->version > 4 ? 6 : 7) : 1, this->depth);
1487 return true;
1488}
1489
1490uint16_t VTF::getDepth(uint8_t mip) const {
1491 return ImageDimensions::getMipDim(mip, this->depth);
1492}
1493
1494bool VTF::setDepth(uint16_t newDepth) {
1495 if (!this->hasImageData()) {
1496 return false;
1497 }
1498 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), newDepth);
1499 return true;
1500}
1501
1502bool VTF::setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubeMap, uint16_t newDepth) {
1503 if (!this->hasImageData()) {
1504 return false;
1505 }
1506 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, isCubeMap ? (this->version < 1 || this->version > 4 ? 6 : 7) : 1, newDepth);
1507 return true;
1508}
1509
1510uint16_t VTF::getStartFrame() const {
1511 return this->startFrame;
1512}
1513
1514void VTF::setStartFrame(uint16_t newStartFrame) {
1515 this->startFrame = newStartFrame;
1516}
1517
1518math::Vec3f VTF::getReflectivity() const {
1519 return this->reflectivity;
1520}
1521
1522void VTF::setReflectivity(math::Vec3f newReflectivity) {
1523 this->reflectivity = newReflectivity;
1524}
1525
1527 static constexpr auto getReflectivityForImage = [](const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1528 static constexpr auto getReflectivityForPixel = [](const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1529 // http://markjstock.org/doc/gsd_talk_11_notes.pdf page 11
1530 math::Vec3f ref{static_cast<float>(pixel->r()), static_cast<float>(pixel->g()), static_cast<float>(pixel->b())};
1531 // I tweaked it a bit to produce 0.85 reflectivity at pure white
1532 ref = ref / 255.f * 0.922f;
1533 ref[0] *= ref[0];
1534 ref[1] *= ref[1];
1535 ref[2] *= ref[2];
1536 return ref;
1537 };
1538
1539 auto rgba8888Data = vtf.getImageDataAsRGBA8888(0, frame, face, slice);
1540 math::Vec3f out{};
1541 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1542 out += getReflectivityForPixel(reinterpret_cast<ImagePixel::RGBA8888*>(rgba8888Data.data() + i));
1543 }
1544 return out / (rgba8888Data.size() / sizeof(ImagePixel::RGBA8888));
1545 };
1546
1547 const auto faceCount = this->getFaceCount();
1548
1549#ifdef SOURCEPP_BUILD_WITH_THREADS
1550 if (this->frameCount > 1 || faceCount > 1 || this->depth > 1) {
1551 std::vector<std::future<math::Vec3f>> futures;
1552 futures.reserve(this->frameCount * faceCount * this->depth);
1553
1554 this->reflectivity = {};
1555 for (int j = 0; j < this->frameCount; j++) {
1556 for (int k = 0; k < faceCount; k++) {
1557 for (int l = 0; l < this->depth; l++) {
1558 futures.push_back(std::async(std::launch::async, [this, j, k, l] {
1559 return getReflectivityForImage(*this, j, k, l);
1560 }));
1561 if (futures.size() >= std::thread::hardware_concurrency()) {
1562 for (auto& future : futures) {
1563 this->reflectivity += future.get();
1564 }
1565 futures.clear();
1566 }
1567 }
1568 }
1569 }
1570
1571 for (auto& future : futures) {
1572 this->reflectivity += future.get();
1573 }
1574 this->reflectivity /= this->frameCount * faceCount * this->depth;
1575 } else {
1576 this->reflectivity = getReflectivityForImage(*this, 0, 0, 0);
1577 }
1578#else
1579 this->reflectivity = {};
1580 for (int j = 0; j < this->frameCount; j++) {
1581 for (int k = 0; k < faceCount; k++) {
1582 for (int l = 0; l < this->depth; l++) {
1583 this->reflectivity += getReflectivityForImage(*this, j, k, l);
1584 }
1585 }
1586 }
1587 this->reflectivity /= this->frameCount * faceCount * this->depth;
1588#endif
1589}
1590
1592 return this->bumpMapScale;
1593}
1594
1595void VTF::setBumpMapScale(float newBumpMapScale) {
1596 this->bumpMapScale = newBumpMapScale;
1597}
1598
1600 return this->thumbnailFormat;
1601}
1602
1603uint8_t VTF::getThumbnailWidth() const {
1604 return this->thumbnailWidth;
1605}
1606
1608 return this->thumbnailHeight;
1609}
1610
1611uint8_t VTF::getFallbackWidth(uint8_t mip) const {
1612 return ImageDimensions::getMipDim(mip, this->fallbackWidth);
1613}
1614
1615uint8_t VTF::getPaddedFallbackWidth(uint8_t mip) const {
1617}
1618
1619uint8_t VTF::getFallbackHeight(uint8_t mip) const {
1620 return ImageDimensions::getMipDim(mip, this->fallbackHeight);
1621}
1622
1623uint8_t VTF::getPaddedFallbackHeight(uint8_t mip) const {
1625}
1626
1628 return this->fallbackMipCount;
1629}
1630
1632 return this->version >= 3;
1633}
1634
1635const std::vector<Resource>& VTF::getResources() const {
1636 return this->resources;
1637}
1638
1640 for (const auto& resource : this->resources) {
1641 if (resource.type == type) {
1642 return &resource;
1643 }
1644 }
1645 return nullptr;
1646}
1647
1649 for (auto& resource : this->resources) {
1650 if (resource.type == type) {
1651 return &resource;
1652 }
1653 }
1654 return nullptr;
1655}
1656
1657void VTF::setResourceInternal(Resource::Type type, std::span<const std::byte> data_) {
1658 if (const auto* resource = this->getResource(type); resource && resource->data.size() == data_.size()) {
1659 std::memcpy(resource->data.data(), data_.data(), data_.size());
1660 return;
1661 }
1662
1663 // Store resource data
1664 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1665 for (const auto& [type_, flags_, dataSpan] : this->resources) {
1666 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1667 }
1668
1669 // Set new resource
1670 if (data_.empty()) {
1671 resourceData.erase(type);
1672 } else {
1673 resourceData[type] = {{data_.begin(), data_.end()}, 0};
1674 }
1675
1676 // Save the data
1677 this->data.clear();
1678 BufferStream writer{this->data};
1679
1680 for (auto resourceType : resourceData | std::views::keys) {
1681 if (!resourceData.contains(resourceType)) {
1682 continue;
1683 }
1684 auto& [specificResourceData, offset] = resourceData[resourceType];
1685 if (resourceType == type) {
1686 if (!this->data.data()) {
1687 this->data.reserve(offset + specificResourceData.size());
1688 }
1689 Resource newResource{
1690 .type = type,
1691 .flags = type != Resource::TYPE_IMAGE_DATA && specificResourceData.size() <= sizeof(uint32_t) ? Resource::FLAG_LOCAL_DATA : Resource::FLAG_NONE,
1692 .data = {this->data.data() + offset, specificResourceData.size()},
1693 };
1694 if (auto* resourcePtr = this->getResourceInternal(type)) {
1695 *resourcePtr = newResource;
1696 } else {
1697 this->resources.push_back(newResource);
1698 }
1699 } else if (!resourceData.contains(resourceType)) {
1700 continue;
1701 }
1702 offset = writer.tell();
1703 writer.write(specificResourceData);
1704 }
1705 this->data.resize(writer.size());
1706
1707 for (auto& [type_, flags_, dataSpan] : this->resources) {
1708 if (resourceData.contains(type_)) {
1709 const auto& [specificResourceData, offset] = resourceData[type_];
1710 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1711 }
1712 }
1713}
1714
1716 std::erase_if(this->resources, [type](const Resource& resource) { return resource.type == type; });
1717}
1718
1719void VTF::regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newDepth, ImageConversion::ResizeFilter filter, float quality) {
1720 if (!newWidth) newWidth = 1;
1721 if (!newHeight) newHeight = 1;
1722 if (!newMipCount) newMipCount = 1;
1723 if (!newFrameCount) newFrameCount = 1;
1724 if (!newFaceCount) newFaceCount = 1;
1725 if (!newDepth) newDepth = 1;
1726
1727 if (newMipCount > 1) {
1728 // Valve code doesn't like it when you have compressed mips with padding unless they're lower than 4 on a given dimension!
1729 // We could do some extra work to calculate this better, but I don't care, sorry
1730 if (ImageFormatDetails::compressed(newFormat) && ((newWidth > 4 && !std::has_single_bit(newWidth)) || (newHeight > 4 && !std::has_single_bit(newHeight)))) {
1731 newMipCount = 1;
1732 }
1733
1734 this->flags &= ~FLAG_V0_NO_MIP;
1735 } else {
1736 this->flags |= FLAG_V0_NO_MIP;
1737 }
1738
1739 const auto faceCount = this->getFaceCount();
1740 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1741 return;
1742 }
1743
1744 std::vector<std::byte> newImageData;
1745 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1746 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1747 newImageData = ImageConversion::convertSeveralImageDataToFormat(imageResource->data, this->format, newFormat, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, quality);
1748 } else {
1749 newImageData.resize(ImageFormatDetails::getDataLength(this->format, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth));
1750 for (int i = newMipCount - 1; i >= 0; i--) {
1751 const auto [newMipWidth, newMipHeight, newMipDepth] = ImageDimensions::getMipDims(i, newWidth, newHeight, newDepth);
1752
1753 int sourceMipIndex = 0;
1754 for (int mip = 0, bestScore = std::numeric_limits<int>::max(); mip < this->mipCount; mip++) {
1755 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height, false);
1756 if (mipWidth == newMipWidth && mipHeight == newMipHeight) {
1757 break;
1758 }
1759 const auto widthDiff = static_cast<int>(mipWidth) - static_cast<int>(newMipWidth);
1760 const auto heightDiff = static_cast<int>(mipHeight) - static_cast<int>(newMipHeight);
1761 if (widthDiff < 0 || heightDiff < 0) {
1762 continue;
1763 }
1764 if (const auto score = widthDiff + heightDiff; score < bestScore) {
1765 bestScore = score;
1766 sourceMipIndex = mip;
1767 }
1768 }
1769 const auto [sourceMipWidth, sourceMipHeight] = ImageDimensions::getMipDims(sourceMipIndex, this->width, this->height);
1770
1771 for (int j = 0; j < newFrameCount; j++) {
1772 for (int k = 0; k < newFaceCount; k++) {
1773 for (int l = 0; l < newMipDepth; l++) {
1774 if (j < this->frameCount && k < faceCount && l < this->depth) {
1775 auto imageSpan = this->getImageDataRaw(sourceMipIndex, j, k, l);
1776 std::vector<std::byte> imageBacking;
1777 if (sourceMipWidth != newMipWidth || sourceMipHeight != newMipHeight) {
1778 imageBacking = ImageConversion::resizeImageData(imageSpan, this->format, sourceMipWidth, newMipWidth, sourceMipHeight, newMipHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
1779 imageSpan = imageBacking;
1780 }
1781 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newDepth) && imageSpan.size() == length) {
1782 std::memcpy(newImageData.data() + offset, imageSpan.data(), length);
1783 }
1784 }
1785 }
1786 }
1787 }
1788 }
1789 if (this->format != newFormat) {
1790 newImageData = ImageConversion::convertSeveralImageDataToFormat(newImageData, this->format, newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth, quality);
1791 }
1792 }
1793 } else {
1794 newImageData.resize(ImageFormatDetails::getDataLength(newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth));
1795 }
1796
1797 this->format = newFormat;
1798 this->width = newWidth;
1799 this->height = newHeight;
1800 this->mipCount = newMipCount;
1801 this->frameCount = newFrameCount;
1802 if (newFaceCount > 1) {
1803 this->flags |= FLAG_V0_ENVMAP;
1804 } else {
1805 this->flags &= ~FLAG_V0_ENVMAP;
1806 }
1807 this->depth = newDepth;
1808
1810
1811 // Fix up XBOX resources that depend on image resource
1812 if (const auto* palette = this->getResource(Resource::TYPE_PALETTE_DATA)) {
1813 if (const auto targetSize = 256 * sizeof(ImagePixel::BGRA8888) * this->frameCount; palette->data.size() != targetSize) {
1814 std::vector<std::byte> paletteData{palette->data.begin(), palette->data.end()};
1815 paletteData.resize(targetSize);
1817 }
1818 }
1819 if (this->hasFallbackData()) {
1820 this->removeFallback();
1821 this->computeFallback();
1822 }
1823}
1824
1825std::vector<std::byte> VTF::getPaletteResourceFrame(uint16_t frame) const {
1826 if (const auto* palette = this->getResource(Resource::TYPE_PALETTE_DATA)) {
1827 return palette->getDataAsPalette(frame);
1828 }
1829 return {};
1830}
1831
1832std::vector<std::byte> VTF::getParticleSheetFrameDataRaw(uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1833 spriteWidth = 0;
1834 spriteHeight = 0;
1835
1836 const auto shtResource = this->getResource(Resource::TYPE_PARTICLE_SHEET_DATA);
1837 if (!shtResource) {
1838 return {};
1839 }
1840
1841 auto sht = shtResource->getDataAsParticleSheet();
1842 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1843 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1844 return {};
1845 }
1846
1847 // These values are likely slightly too large thanks to float magic, use a
1848 // shader that scales UVs instead of this function if precision is a concern
1849 // This will also break if any of the bounds are above 1 or below 0, but that
1850 // hasn't been observed in official textures
1851 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1852 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 * static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1853 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 * static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1854 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 * static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1855 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 * static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1856
1857 if (x1 > x2) [[unlikely]] {
1858 std::swap(x1, x2);
1859 }
1860 if (y1 > y2) [[unlikely]] {
1861 std::swap(y1, y2);
1862 }
1863 spriteWidth = x2 - x1;
1864 spriteWidth = y2 - y1;
1865
1866 const auto out = ImageConversion::cropImageData(this->getImageDataRaw(mip, frame, face, slice), this->getFormat(), this->getWidth(mip), spriteWidth, x1, this->getHeight(mip), spriteHeight, y1);
1867 if (out.empty()) {
1868 spriteWidth = 0;
1869 spriteHeight = 0;
1870 }
1871 return out;
1872}
1873
1874std::vector<std::byte> VTF::getParticleSheetFrameDataAs(ImageFormat newFormat, uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1875 return ImageConversion::convertImageDataToFormat(this->getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->getFormat(), newFormat, spriteWidth, spriteHeight);
1876}
1877
1878std::vector<std::byte> VTF::getParticleSheetFrameDataAsRGBA8888(uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1879 return this->getParticleSheetFrameDataAs(ImageFormat::RGBA8888, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice);
1880}
1881
1883 std::vector<std::byte> particleSheetData;
1884 BufferStream writer{particleSheetData};
1885
1886 const auto bakedSheet = value.bake();
1887 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1888 particleSheetData.resize(writer.size());
1889
1891}
1892
1896
1898 std::vector<std::byte> pccData;
1899 BufferStream writer{pccData};
1900
1901 writer.write<uint32_t>(sizeof(Resource::PCC));
1902 writer
1903 << value.origin[0] << value.origin[1] << value.origin[2] << value.origin[3]
1904 << value.inverseTransform[0][0] << value.inverseTransform[0][1] << value.inverseTransform[0][2] << value.inverseTransform[0][3]
1905 << value.inverseTransform[1][0] << value.inverseTransform[1][1] << value.inverseTransform[1][2] << value.inverseTransform[1][3]
1906 << value.inverseTransform[2][0] << value.inverseTransform[2][1] << value.inverseTransform[2][2] << value.inverseTransform[2][3]
1907 << value.inverseTransform[3][0] << value.inverseTransform[3][1] << value.inverseTransform[3][2] << value.inverseTransform[3][3];
1908 pccData.resize(writer.size());
1909
1911}
1912
1916
1917void VTF::setCRCResource(uint32_t value) {
1918 this->setResourceInternal(Resource::TYPE_CRC, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1919}
1920
1924
1925void VTF::setLODResource(uint8_t u, uint8_t v, uint8_t u360, uint8_t v360) {
1926 uint32_t lodData;
1927 BufferStream writer{&lodData, sizeof(lodData)};
1928
1929 writer << u << v << u360 << v360;
1930
1931 this->setResourceInternal(Resource::TYPE_LOD_CONTROL_INFO, {reinterpret_cast<const std::byte*>(&lodData), sizeof(lodData)});
1932}
1933
1937
1938void VTF::setExtendedFlagsResource(uint32_t value) {
1939 this->setResourceInternal(Resource::TYPE_EXTENDED_FLAGS, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1940}
1941
1945
1946void VTF::setKeyValuesDataResource(std::string_view value) {
1947 std::vector<std::byte> keyValuesData;
1948 BufferStream writer{keyValuesData};
1949
1950 writer.write<uint32_t>(value.size()).write(value, false);
1951 keyValuesData.resize(writer.size());
1952
1954}
1955
1959
1960void VTF::setAuthorInfoResource(std::string_view value) {
1961 std::vector<std::byte> authorInfo;
1962 BufferStream writer{authorInfo};
1963
1964 writer.write<uint32_t>(value.size()).write(value, false);
1965 authorInfo.resize(writer.size());
1966
1968}
1969
1973
1975 std::vector<std::byte> hotspotData;
1976 BufferStream writer{hotspotData};
1977
1978 const auto bakedHotspotData = value.bake();
1979 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1980 hotspotData.resize(writer.size());
1981
1983}
1984
1988
1990 return this->compressionLevel;
1991}
1992
1993void VTF::setCompressionLevel(int16_t newCompressionLevel) {
1994 if (newCompressionLevel < 0) {
1995 this->compressionLevel = 6;
1996 } else {
1997 this->compressionLevel = newCompressionLevel;
1998 }
1999}
2000
2004
2006 if (newCompressionMethod == CompressionMethod::CONSOLE_LZMA && this->platform == PLATFORM_PC) {
2008 } else if (newCompressionMethod != CompressionMethod::CONSOLE_LZMA && this->platform != PLATFORM_PC) {
2010 } else {
2011 this->compressionMethod = newCompressionMethod;
2012 }
2013}
2014
2015bool VTF::hasImageData() const {
2016 return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0;
2017}
2018
2019std::span<const std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
2020 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
2021 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, this->getFaceCount(), this->width, this->height, slice, this->depth)) {
2022 return imageResource->data.subspan(offset, length);
2023 }
2024 }
2025 return {};
2026}
2027
2028std::span<std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) {
2029 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
2030 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, this->getFaceCount(), this->width, this->height, slice, this->depth)) {
2031 return imageResource->data.subspan(offset, length);
2032 }
2033 }
2034 return {};
2035}
2036
2037std::vector<std::byte> VTF::getImageDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
2038 const auto rawImageData = this->getImageDataRaw(mip, frame, face, slice);
2039 if (rawImageData.empty()) {
2040 return {};
2041 }
2042 if (this->format == ImageFormat::P8) {
2043 if (const auto paletteData = this->getPaletteResourceFrame(frame); !paletteData.empty()) {
2044 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height);
2045 return ImageConversion::convertImageDataToFormat(ImageQuantize::convertP8ImageDataToBGRA8888(paletteData, rawImageData), ImageFormat::BGRA8888, newFormat, mipWidth, mipHeight);
2046 }
2047 }
2048 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height);
2049 return ImageConversion::convertImageDataToFormat(rawImageData, this->format, newFormat, mipWidth, mipHeight);
2050}
2051
2052std::vector<std::byte> VTF::getImageDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
2053 return this->getImageDataAs(ImageFormat::RGBA8888, mip, frame, face, slice);
2054}
2055
2056bool VTF::setImage(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, float quality) {
2057 if (imageData_.empty()) {
2058 return false;
2059 }
2060
2061 if (!this->hasImageData()) {
2062 uint16_t resizedWidth = width_, resizedHeight = height_;
2063 ImageConversion::setResizedDims(resizedWidth, this->imageWidthResizeMethod, resizedHeight, this->imageHeightResizeMethod);
2064 if (ImageFormatDetails::compressed(format_)) {
2065 resizedWidth += math::paddingForAlignment(4, resizedWidth);
2066 resizedHeight += math::paddingForAlignment(4, resizedHeight);
2067 }
2068 if (const auto newMipCount = ImageDimensions::getMaximumMipCount(resizedWidth, resizedHeight, this->depth); newMipCount <= mip) {
2069 mip = newMipCount - 1;
2070 }
2071 if (face > 6 || (face == 6 && (this->platform != PLATFORM_PC || this->version < 1 || this->version > 4))) {
2072 return false;
2073 }
2074 this->regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 6 ? 6 : face) : 0, slice + 1);
2075 }
2076
2077 const auto faceCount = this->getFaceCount();
2078 if (this->mipCount <= mip || this->frameCount <= frame || faceCount <= face || this->depth <= slice) {
2079 return false;
2080 }
2081
2082 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
2083 if (!imageResource) {
2084 return false;
2085 }
2086 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, faceCount, this->width, this->height, slice, this->depth)) {
2087 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
2088 const auto [newWidth, newHeight] = ImageDimensions::getMipDims(mip, this->width, this->height);
2089 if (width_ != newWidth || height_ != newHeight) {
2090 image = ImageConversion::resizeImageData(image, format_, width_, newWidth, height_, newHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
2091 }
2092 if (format_ != this->format) {
2093 image = ImageConversion::convertImageDataToFormat(image, format_, this->format, newWidth, newHeight, quality);
2094 }
2095 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
2096 }
2097 return true;
2098}
2099
2100bool VTF::setImage(const std::filesystem::path& imagePath, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, float quality, ImageConversion::ResizeBounds resizeBounds) {
2101 ImageFormat inputFormat;
2102 int inputWidth, inputHeight, inputFrameCount;
2103 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
2104
2105 // Unable to decode file
2106 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
2107 return false;
2108 }
2109
2110 // Image dimension overrides
2111 auto [requestedResizeWidth, requestedResizeHeight] = resizeBounds.clamp(inputWidth, inputHeight);
2112
2113 // One frame (normal)
2114 if (inputFrameCount == 1) {
2115 if (requestedResizeWidth != inputWidth || requestedResizeHeight != inputHeight) {
2116 const auto imageDataResized = ImageConversion::resizeImageData(imageData_, inputFormat, inputWidth, requestedResizeWidth, inputHeight, requestedResizeHeight, !ImageFormatDetails::large(inputFormat), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, resizeBounds.resizeFilter, resizeBounds.resizeEdge);
2117 return this->setImage(imageDataResized, inputFormat, requestedResizeWidth, requestedResizeHeight, filter, mip, frame, face, slice, quality);
2118 }
2119 return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice, quality);
2120 }
2121
2122 // Multiple frames (GIF)
2123 bool allSuccess = true;
2124 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
2125 for (int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
2126 std::span currentFrameData{imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize};
2127 if (requestedResizeWidth != inputWidth || requestedResizeHeight != inputHeight) {
2128 const auto currentFrameResized = ImageConversion::resizeImageData(currentFrameData, inputFormat, inputWidth, requestedResizeWidth, inputHeight, requestedResizeHeight, !ImageFormatDetails::large(inputFormat), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, resizeBounds.resizeFilter, resizeBounds.resizeEdge);
2129 if (!this->setImage(currentFrameResized, inputFormat, requestedResizeWidth, requestedResizeHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
2130 allSuccess = false;
2131 }
2132 } else if (!this->setImage(currentFrameData, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
2133 allSuccess = false;
2134 }
2135 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
2136 // Call this after setting the first image, this function is a no-op if no image data is present yet
2137 this->setFrameCount(frame + inputFrameCount);
2138 }
2139 }
2140 return allSuccess;
2141}
2142
2143std::vector<std::byte> VTF::saveImageToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
2144 return ImageConversion::convertImageDataToFile(this->getImageDataRaw(mip, frame, face, slice), this->format, this->getWidth(mip), this->getHeight(mip), fileFormat);
2145}
2146
2147bool VTF::saveImageToFile(const std::filesystem::path& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
2148 if (auto data_ = this->saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
2149 return fs::writeFileBuffer(imagePath, data_);
2150 }
2151 return false;
2152}
2153
2155 return this->thumbnailFormat != ImageFormat::EMPTY && this->thumbnailWidth > 0 && this->thumbnailHeight > 0;
2156}
2157
2158std::span<const std::byte> VTF::getThumbnailDataRaw() const {
2159 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
2160 return thumbnailResource->data;
2161 }
2162 return {};
2163}
2164
2165std::span<std::byte> VTF::getThumbnailDataRaw() { // NOLINT(*-make-member-function-const)
2166 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
2167 return thumbnailResource->data;
2168 }
2169 return {};
2170}
2171
2172std::vector<std::byte> VTF::getThumbnailDataAs(ImageFormat newFormat) const {
2173 const auto rawThumbnailData = this->getThumbnailDataRaw();
2174 if (rawThumbnailData.empty()) {
2175 return {};
2176 }
2177 return ImageConversion::convertImageDataToFormat(rawThumbnailData, this->thumbnailFormat, newFormat, this->thumbnailWidth, this->thumbnailHeight);
2178}
2179
2180std::vector<std::byte> VTF::getThumbnailDataAsRGBA8888() const {
2182}
2183
2184void VTF::setThumbnail(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality) {
2185 if (format_ != this->thumbnailFormat) {
2186 this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(imageData_, format_, this->thumbnailFormat, width_, height_, quality));
2187 } else {
2189 }
2190 this->thumbnailWidth = width_;
2191 this->thumbnailHeight = height_;
2192}
2193
2194bool VTF::setThumbnail(const std::filesystem::path& imagePath, float quality) {
2195 ImageFormat inputFormat;
2196 int inputWidth, inputHeight, inputFrameCount;
2197 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
2198
2199 // Unable to decode file
2200 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
2201 return false;
2202 }
2203
2204 // One frame (normal)
2205 if (inputFrameCount == 1) {
2206 this->setThumbnail(imageData_, inputFormat, inputWidth, inputHeight, quality);
2207 return true;
2208 }
2209
2210 // Multiple frames (GIF) - we will just use the first one
2211 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
2212 this->setThumbnail({imageData_.data(), frameSize}, inputFormat, inputWidth, inputHeight, quality);
2213 return true;
2214}
2215
2217 if (!this->hasImageData()) {
2218 return;
2219 }
2220
2221 if (this->platform == PLATFORM_XBOX) {
2223 this->thumbnailWidth = 1;
2224 this->thumbnailHeight = 1;
2225 std::array newThumbnail{
2226 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[0], 0.f, 1.f) * 255.f)),
2227 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[1], 0.f, 1.f) * 255.f)),
2228 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[2], 0.f, 1.f) * 255.f)),
2229 };
2231 } else {
2233 this->thumbnailWidth = 16;
2234 this->thumbnailHeight = 16;
2235 this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(ImageConversion::resizeImageData(this->getImageDataRaw(), this->format, this->width, this->thumbnailWidth, this->height, this->thumbnailHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter), this->format, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, quality));
2236 }
2237}
2238
2241 this->thumbnailWidth = 0;
2242 this->thumbnailHeight = 0;
2244}
2245
2246std::vector<std::byte> VTF::saveThumbnailToFile(ImageConversion::FileFormat fileFormat) const {
2247 return ImageConversion::convertImageDataToFile(this->getThumbnailDataRaw(), this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, fileFormat);
2248}
2249
2250bool VTF::saveThumbnailToFile(const std::filesystem::path& imagePath, ImageConversion::FileFormat fileFormat) const {
2251 if (auto data_ = this->saveThumbnailToFile(fileFormat); !data_.empty()) {
2252 return fs::writeFileBuffer(imagePath, data_);
2253 }
2254 return false;
2255}
2256
2258 return this->fallbackWidth > 0 && this->fallbackHeight > 0 && this->fallbackMipCount > 0;
2259}
2260
2261std::span<const std::byte> VTF::getFallbackDataRaw(uint8_t mip, uint16_t frame, uint8_t face) const {
2262 if (const auto fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
2263 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->fallbackMipCount, frame, this->frameCount, face, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight)) {
2264 return fallbackResource->data.subspan(offset, length);
2265 }
2266 }
2267 return {};
2268}
2269
2270std::span<std::byte> VTF::getFallbackDataRaw(uint8_t mip, uint16_t frame, uint8_t face) {
2271 if (const auto fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
2272 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->fallbackMipCount, frame, this->frameCount, face, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight)) {
2273 return fallbackResource->data.subspan(offset, length);
2274 }
2275 }
2276 return {};
2277}
2278
2279std::vector<std::byte> VTF::getFallbackDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face) const {
2280 const auto rawFallbackData = this->getFallbackDataRaw(mip, frame, face);
2281 if (rawFallbackData.empty()) {
2282 return {};
2283 }
2284 if (this->format == ImageFormat::P8) {
2285 if (const auto paletteData = this->getPaletteResourceFrame(frame); !paletteData.empty()) {
2287 }
2288 }
2289 return ImageConversion::convertImageDataToFormat(rawFallbackData, this->format, newFormat, this->fallbackWidth, this->fallbackHeight);
2290}
2291
2292std::vector<std::byte> VTF::getFallbackDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face) const {
2293 return this->getFallbackDataAs(ImageFormat::RGBA8888, mip, frame, face);
2294}
2295
2297 if (!this->hasImageData()) {
2298 return;
2299 }
2300
2301 const auto faceCount = this->getFaceCount();
2302
2303 this->fallbackWidth = 8;
2304 this->fallbackHeight = 8;
2306
2307 std::vector<std::byte> fallbackData;
2308 fallbackData.resize(ImageFormatDetails::getDataLength(this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight));
2309 for (int i = this->fallbackMipCount - 1; i >= 0; i--) {
2310 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(i, this->fallbackWidth, this->fallbackHeight);
2311 for (int j = 0; j < this->frameCount; j++) {
2312 for (int k = 0; k < faceCount; k++) {
2313 auto mip = ImageConversion::resizeImageData(this->getImageDataRaw(0, j, k, 0), this->format, this->width, mipWidth, this->height, mipHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
2314 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->fallbackMipCount, j, this->frameCount, k, faceCount, this->fallbackWidth, this->fallbackHeight) && mip.size() == length) {
2315 std::memcpy(fallbackData.data() + offset, mip.data(), length);
2316 }
2317 }
2318 }
2319 }
2321}
2322
2324 this->fallbackWidth = 0;
2325 this->fallbackHeight = 0;
2326 this->fallbackMipCount = 0;
2328}
2329
2330std::vector<std::byte> VTF::saveFallbackToFile(uint8_t mip, uint16_t frame, uint8_t face, ImageConversion::FileFormat fileFormat) const {
2331 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->fallbackWidth, this->fallbackHeight);
2332 return ImageConversion::convertImageDataToFile(this->getFallbackDataRaw(mip, frame, face), this->format, mipWidth, mipHeight, fileFormat);
2333}
2334
2335bool VTF::saveFallbackToFile(const std::filesystem::path& imagePath, uint8_t mip, uint16_t frame, uint8_t face, ImageConversion::FileFormat fileFormat) const {
2336 if (auto data_ = this->saveFallbackToFile(mip, frame, face, fileFormat); !data_.empty()) {
2337 return fs::writeFileBuffer(imagePath, data_);
2338 }
2339 return false;
2340}
2341
2343 return this->consoleMipScale;
2344}
2345
2346void VTF::setConsoleMipScale(uint8_t consoleMipScale_) {
2347 this->consoleMipScale = consoleMipScale_;
2348}
2349
2350uint64_t VTF::estimateBakeSize() const {
2351 bool isExact;
2352 return this->estimateBakeSize(isExact);
2353}
2354
2355uint64_t VTF::estimateBakeSize(bool& isExact) const {
2356 isExact = true;
2357 uint64_t vtfSize = 0;
2358
2359 switch (this->platform) {
2360 case PLATFORM_UNKNOWN:
2361 break;
2362 case PLATFORM_PC:
2363 switch (this->version) {
2364 case 6:
2365 if (this->compressionLevel > 0) {
2366 vtfSize += sizeof(uint64_t); // AXC header size
2367 vtfSize += sizeof(uint32_t) * (2 + this->mipCount * this->frameCount * this->getFaceCount()); // AXC resource size
2368 }
2369 case 5:
2370 case 4:
2371 case 3:
2372 vtfSize += 11 + sizeof(uint32_t) + sizeof(uint64_t) * this->getResources().size(); // resources header size
2373 for (const auto& [resourceType, resourceFlags, resourceData] : this->getResources()) {
2374 if (!(resourceFlags & Resource::FLAG_LOCAL_DATA) && resourceType != Resource::TYPE_THUMBNAIL_DATA && resourceType != Resource::TYPE_IMAGE_DATA) {
2375 vtfSize += resourceData.size(); // resource size
2376 }
2377 }
2378 case 2:
2379 vtfSize += sizeof(uint16_t); // depth header field
2380 case 1:
2381 case 0:
2382 vtfSize += sizeof(uint32_t) * 9 + sizeof(float) * 4 + sizeof(uint16_t) * 4 + sizeof(uint8_t) * 3; // header fields
2383 if (this->version < 3) {
2384 vtfSize += math::paddingForAlignment(16, vtfSize); // align to 16 bytes
2385 }
2386
2387 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2388 vtfSize += thumbnailResource->data.size(); // thumbnail size
2389 }
2390 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2392 vtfSize += imageResource->data.size(); // uncompressed image size
2393 } else {
2394 isExact = false;
2395 vtfSize += static_cast<uint64_t>(static_cast<double>(imageResource->data.size()) / 1.75); // compressed image data
2396 }
2397 }
2398 break;
2399 default:
2400 break;
2401 }
2402 break;
2403 case PLATFORM_XBOX:
2404 vtfSize += sizeof(uint32_t) * 6 + sizeof(float) * 4 + sizeof(uint16_t) * 6 + sizeof(uint8_t) * 6; // header fields
2405
2406 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2407 vtfSize += thumbnailResource->data.size(); // thumbnail size
2408 }
2409 if (this->format == ImageFormat::P8) {
2410 if (const auto* paletteResource = this->getResource(Resource::TYPE_PALETTE_DATA)) {
2411 vtfSize += paletteResource->data.size(); // palette size
2412 }
2413 }
2414 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA); fallbackResource && this->hasFallbackData()) {
2415 vtfSize += ImageFormatDetails::getDataLengthXBOX(false, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight); // fallback size
2416 vtfSize += math::paddingForAlignment(512, vtfSize); // align to 512 bytes if fallback present
2417 }
2418 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2419 vtfSize += ImageFormatDetails::getDataLengthXBOX(true, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth); // image size
2420 }
2421 if (vtfSize > 512) {
2422 vtfSize += math::paddingForAlignment(512, vtfSize); // align to 512 bytes if longer than 512 bytes
2423 }
2424 break;
2426 vtfSize += sizeof(uint32_t); // align to 16 bytes
2427 case PLATFORM_X360:
2429 vtfSize += sizeof(uint32_t) * 7 + sizeof(float) * 4 + sizeof(uint16_t) * 5 + sizeof(uint8_t) * 6; // header fields
2430 vtfSize += sizeof(uint64_t) * this->getResources().size(); // resources header size
2431
2432 for (const auto& [resourceType, resourceFlags, resourceData] : this->getResources()) {
2433 if (!(resourceFlags & Resource::FLAG_LOCAL_DATA) && resourceType != Resource::TYPE_IMAGE_DATA) {
2434 vtfSize += resourceData.size(); // resource size
2435 }
2436 }
2437 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2439 vtfSize += imageResource->data.size(); // uncompressed image data
2440 } else {
2441 isExact = false;
2442 vtfSize += static_cast<uint64_t>(static_cast<double>(imageResource->data.size()) / 1.75); // compressed image data
2443 }
2444 }
2445 break;
2446 }
2447 return vtfSize;
2448}
2449
2450std::vector<std::byte> VTF::bake() const {
2451 std::vector<std::byte> out;
2452 BufferStream writer{out};
2453
2454 const auto writeResources = [&writer](uint64_t headerLengthPos, const std::vector<Resource>& sortedResources) -> uint64_t {
2455 auto resourceHeaderCurPos = writer.tell();
2456 writer.pad<uint64_t>(sortedResources.size());
2457 auto resourceDataCurPos = writer.tell();
2458 writer.seek_u(headerLengthPos).write<uint32_t>(resourceDataCurPos);
2459
2460 uint64_t resourceHeaderImagePos = 0;
2461 for (const auto& resource : sortedResources) {
2462 writer.seek_u(resourceHeaderCurPos);
2463
2464 uint32_t resourceType = resource.type;
2465 if (resource.flags & Resource::FLAG_LOCAL_DATA) {
2466 resourceType |= Resource::FLAG_LOCAL_DATA << 24;
2467 }
2468 if (writer.is_big_endian()) {
2469 // type threeCC is little-endian
2470 BufferStream::swap_endian(&resourceType);
2471 }
2472 writer.write<uint32_t>(resourceType);
2473
2474 if (resource.type == Resource::TYPE_IMAGE_DATA) {
2475 resourceHeaderImagePos = writer.tell();
2476 writer.write<uint32_t>(0);
2477 resourceHeaderCurPos = writer.tell();
2478 continue;
2479 }
2480
2481 if (resource.flags & Resource::FLAG_LOCAL_DATA) {
2482 writer.write(resource.data);
2483 resourceHeaderCurPos = writer.tell();
2484 } else {
2485 writer.write<uint32_t>(resourceDataCurPos);
2486 resourceHeaderCurPos = writer.tell();
2487 writer.seek_u(resourceDataCurPos).write(resource.data);
2488 resourceDataCurPos = writer.tell();
2489 }
2490 }
2491 if (resourceHeaderImagePos) {
2492 writer.seek_u(resourceHeaderImagePos).write<uint32_t>(resourceDataCurPos);
2493 for (auto& resource : sortedResources) {
2494 if (resource.type == Resource::TYPE_IMAGE_DATA) {
2495 writer.seek_u(resourceDataCurPos).write(resource.data);
2496 break;
2497 }
2498 }
2499 return resourceDataCurPos;
2500 }
2501 return 0;
2502 };
2503
2504 // Miscellaneous fixups to write to the output file
2505 auto bakeFormat = this->format;
2506 auto bakeFlags = this->flags;
2507 // No source game supports this format, but they do support the flag with reg. DXT1
2508 if (bakeFormat == ImageFormat::DXT1_ONE_BIT_ALPHA) {
2509 bakeFormat = ImageFormat::DXT1;
2510 bakeFlags |= FLAG_V0_ONE_BIT_ALPHA;
2511 }
2512 // NV_NULL / ATI2N / ATI1N are at a different position based on engine branch
2513 if (this->version < 5 && (this->format == ImageFormat::EMPTY || this->format == ImageFormat::ATI2N || this->format == ImageFormat::ATI1N)) {
2514 bakeFormat = static_cast<ImageFormat>(static_cast<int32_t>(this->format) + 3);
2515 }
2516 // Fix up the HDR format
2517 if (bakeFormat == ImageFormat::SOURCEPP_BGRA8888_HDR) {
2518 bakeFormat = ImageFormat::BGRA8888;
2520 bakeFormat = ImageFormat::RGBA16161616;
2521 }
2522
2523 switch (this->platform) {
2524 case PLATFORM_UNKNOWN:
2525 return out;
2526 case PLATFORM_PC: {
2527 writer
2528 .write(VTF_SIGNATURE)
2529 .write<int32_t>(7)
2530 .write(this->version);
2531
2532 const auto headerLengthPos = writer.tell();
2533 writer.write<uint32_t>(0);
2534
2535 writer
2536 .write(this->width)
2537 .write(this->height)
2538 .write(bakeFlags)
2539 .write(this->frameCount)
2540 .write(this->startFrame)
2541 .pad<uint32_t>()
2542 .write(this->reflectivity[0])
2543 .write(this->reflectivity[1])
2544 .write(this->reflectivity[2])
2545 .pad<uint32_t>()
2546 .write(this->bumpMapScale)
2547 .write(bakeFormat)
2548 .write(this->mipCount)
2549 .write(this->thumbnailFormat)
2550 .write(this->thumbnailWidth)
2551 .write(this->thumbnailHeight);
2552
2553 if (this->version >= 2) {
2554 writer << this->depth;
2555 }
2556
2557 if (this->version < 3) {
2558 writer.pad(math::paddingForAlignment(16, writer.tell()));
2559 const auto headerSize = writer.tell();
2560 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
2561
2562 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2563 writer.write(thumbnailResource->data);
2564 }
2565 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2566 writer.write(imageResource->data);
2567 }
2568 } else {
2569 std::vector<std::byte> auxCompressionResourceData;
2570 std::vector<std::byte> compressedImageResourceData;
2571 bool hasAuxCompression = false;
2572 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
2573 hasAuxCompression = this->version >= 6 && this->compressionLevel != 0;
2574 if (hasAuxCompression) {
2575 const auto faceCount = this->getFaceCount();
2576 auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) * sizeof(uint32_t));
2577 BufferStream auxWriter{auxCompressionResourceData, false};
2578
2579 // Format of aux resource is as follows, with each item of unspecified type being a 4 byte integer:
2580 // - Size of resource in bytes, not counting this int
2581 // - Compression level, method (2 byte integers)
2582 // - (X times) Size of each mip-face-frame combo
2583 auxWriter
2584 .write<uint32_t>(auxCompressionResourceData.size() - sizeof(uint32_t))
2585 .write(this->compressionLevel)
2586 .write(this->compressionMethod);
2587
2588 for (int i = this->mipCount - 1; i >= 0; i--) {
2589 for (int j = 0; j < this->frameCount; j++) {
2590 for (int k = 0; k < faceCount; k++) {
2591 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->depth)) {
2592 auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->depth}, static_cast<int16_t>(this->compressionLevel), this->compressionMethod);
2593 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
2594 auxWriter.write<uint32_t>(compressedData.size());
2595 }
2596 }
2597 }
2598 }
2599 }
2600 }
2601
2602 writer.pad(3).write<uint32_t>(this->getResources().size() + hasAuxCompression + (this->flagsExtra != 0)).pad(8);
2603
2604 std::vector<Resource> sortedResources = this->getResources();
2605 auto flagsExtraBacking = std::bit_cast<std::array<std::byte, 4>>(this->flagsExtra);
2606 if (this->flagsExtra != 0) {
2607 sortedResources.push_back({
2610 .data = flagsExtraBacking,
2611 });
2612 }
2613 if (hasAuxCompression) {
2614 for (auto& resource : sortedResources) {
2615 if (resource.type == Resource::TYPE_IMAGE_DATA) {
2616 resource.data = compressedImageResourceData;
2617 break;
2618 }
2619 }
2620 sortedResources.push_back({
2622 .flags = Resource::FLAG_NONE,
2623 .data = auxCompressionResourceData,
2624 });
2625 }
2626 std::ranges::sort(sortedResources, [](const Resource& lhs, const Resource& rhs) {
2627 return lhs.type < rhs.type;
2628 });
2629 writeResources(headerLengthPos, sortedResources);
2630 }
2631 break;
2632 }
2633 case PLATFORM_XBOX: {
2634 writer << XTF_SIGNATURE << PLATFORM_XBOX;
2635 writer.write<uint32_t>(0);
2636
2637 const auto headerSizePos = writer.tell();
2638 writer
2639 .write<uint32_t>(0)
2640 .write(this->flags)
2641 .write(this->width)
2642 .write(this->height)
2643 .write(this->depth)
2644 .write(this->frameCount);
2645 const auto preloadSizePos = writer.tell();
2646 writer.write<uint16_t>(0);
2647 const auto imageOffsetPos = writer.tell();
2648 writer
2649 .write<uint16_t>(0)
2650 .write(this->reflectivity[0])
2651 .write(this->reflectivity[1])
2652 .write(this->reflectivity[2])
2653 .write(this->bumpMapScale)
2654 .write(this->format)
2655 .write(this->thumbnailWidth)
2656 .write(this->thumbnailHeight)
2657 .write(this->fallbackWidth)
2658 .write(this->fallbackHeight)
2659 .write(this->consoleMipScale)
2660 .write<uint8_t>(0);
2661
2662 const auto headerSize = writer.tell();
2663 writer.seek_u(headerSizePos).write<uint32_t>(headerSize).seek_u(headerSize);
2664
2665 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2666 writer.write(thumbnailResource->data);
2667 }
2668
2669 if (this->format == ImageFormat::P8) {
2670 if (const auto* paletteResource = this->getResource(Resource::TYPE_PALETTE_DATA)) {
2671 writer.write(paletteResource->data);
2672 }
2673 }
2674
2675 bool hasFallbackResource = false;
2676 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA); fallbackResource && this->hasFallbackData()) {
2677 hasFallbackResource = true;
2678 std::vector<std::byte> reorderedFallbackData{fallbackResource->data.begin(), fallbackResource->data.end()};
2679 ::swapImageDataEndianForConsole<false>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, this->platform);
2680 bool ok;
2681 reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(false, reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, ok);
2682 if (ok) {
2683 writer.write(reorderedFallbackData);
2684 } else {
2685 return {};
2686 }
2687 }
2688
2689 const auto preloadSize = writer.tell();
2690 writer.seek_u(preloadSizePos).write<uint32_t>(preloadSize).seek_u(preloadSize);
2691
2692 if (hasFallbackResource) {
2693 writer.pad(math::paddingForAlignment(512, writer.tell()));
2694 }
2695 const auto imageOffset = writer.tell();
2696 writer.seek_u(imageOffsetPos).write<uint16_t>(imageOffset).seek_u(imageOffset);
2697
2698 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2699 std::vector<std::byte> reorderedImageData{imageResource->data.begin(), imageResource->data.end()};
2700 ::swapImageDataEndianForConsole<false>(reorderedImageData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
2701 bool ok;
2702 reorderedImageData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(true, reorderedImageData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
2703 if (ok) {
2704 writer.write(reorderedImageData);
2705 } else {
2706 return {};
2707 }
2708 }
2709 if (writer.tell() > 512) {
2710 writer.pad(math::paddingForAlignment(512, writer.tell()));
2711 }
2712 break;
2713 }
2714 case PLATFORM_X360:
2716 case PLATFORM_PS3_PORTAL2: {
2717 if (this->platform == PLATFORM_PS3_PORTAL2) {
2718 writer << VTF3_SIGNATURE;
2719 writer.set_big_endian(true);
2720 writer << PLATFORM_PS3_ORANGEBOX; // Intentional
2721 } else {
2722 writer << VTFX_SIGNATURE;
2723 writer.set_big_endian(true);
2724 writer << this->platform;
2725 }
2726 writer.write<uint32_t>(8);
2727
2728 const auto headerLengthPos = writer.tell();
2729 writer
2730 .write<uint32_t>(0)
2731 .write(bakeFlags)
2732 .write(this->width)
2733 .write(this->height)
2734 .write(this->depth)
2735 .write(this->frameCount);
2736 const auto preloadPos = writer.tell();
2737 writer
2738 .write<uint16_t>(0) // preload size
2739 .write(this->consoleMipScale)
2740 .write<uint8_t>(this->resources.size())
2741 .write(this->reflectivity[0])
2742 .write(this->reflectivity[1])
2743 .write(this->reflectivity[2])
2744 .write(this->bumpMapScale)
2745 .write(bakeFormat)
2746 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[0] * 255)), 0, 255))
2747 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[1] * 255)), 0, 255))
2748 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[2] * 255)), 0, 255))
2749 .write<uint8_t>(255);
2750 const auto compressionPos = writer.tell();
2751 writer.write<uint32_t>(0); // compressed length
2752
2753 if (this->platform == PLATFORM_PS3_PORTAL2) {
2754 // 16 byte aligned
2755 writer.write<uint32_t>(0);
2756 }
2757
2758 std::vector<std::byte> thumbnailResourceData;
2759 std::vector<std::byte> imageResourceData;
2760 std::vector<Resource> sortedResources = this->getResources();
2761 for (auto& resource : sortedResources) {
2762 if (resource.type == Resource::TYPE_THUMBNAIL_DATA) {
2763 thumbnailResourceData = {resource.data.begin(), resource.data.end()};
2764 ::swapImageDataEndianForConsole<false>(thumbnailResourceData, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
2765 resource.data = thumbnailResourceData;
2766 } else if (resource.type == Resource::TYPE_IMAGE_DATA) {
2767 if (this->platform == PLATFORM_PS3_ORANGEBOX) {
2768 bool ok;
2769 imageResourceData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(false, resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
2770 if (!ok || imageResourceData.size() != resource.data.size()) {
2771 return {};
2772 }
2773 } else {
2774 imageResourceData = {resource.data.begin(), resource.data.end()};
2775 }
2776 ::swapImageDataEndianForConsole<false>(imageResourceData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
2777
2778 // LZMA compression has not been observed on the PS3 copy of The Orange Box
2779 // todo(vtfpp): check cubemaps
2781 auto fixedCompressionLevel = this->compressionLevel;
2782 if (this->compressionLevel == 0) {
2783 // Compression level defaults to 0, so it works differently on console.
2784 // Rather than not compress on 0, 0 will be replaced with the default
2785 // compression level (6) if the compression method is LZMA.
2786 fixedCompressionLevel = 6;
2787 }
2788 if (auto compressedData = compression::compressValveLZMA(imageResourceData, fixedCompressionLevel)) {
2789 imageResourceData = std::move(*compressedData);
2790 const auto curPos = writer.tell();
2791 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
2792 }
2793 }
2794
2795 resource.data = imageResourceData;
2796 break;
2797 }
2798 }
2799 std::ranges::sort(sortedResources, [](const Resource& lhs, const Resource& rhs) {
2800 return lhs.type < rhs.type;
2801 });
2802 const auto resourceDataImagePos = writeResources(headerLengthPos, sortedResources);
2803 writer.seek_u(preloadPos).write(std::max<uint16_t>(resourceDataImagePos, 2048));
2804 break;
2805 }
2806 }
2807 out.resize(writer.size());
2808 return out;
2809}
2810
2811bool VTF::bake(const std::filesystem::path& vtfPath) const {
2812 return fs::writeFileBuffer(vtfPath, this->bake());
2813}
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
Definition Macros.h:49
std::vector< std::byte > bake() const
Definition HOT.cpp:61
std::vector< std::byte > bake() const
Definition SHT.cpp:84
uint16_t width
Definition VTF.h:530
@ PLATFORM_PC
Definition VTF.h:208
@ PLATFORM_X360
Definition VTF.h:210
@ PLATFORM_PS3_PORTAL2
Definition VTF.h:212
@ PLATFORM_PS3_ORANGEBOX
Definition VTF.h:211
@ PLATFORM_UNKNOWN
Definition VTF.h:207
@ PLATFORM_XBOX
Definition VTF.h:209
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition VTF.cpp:1209
void setParallaxCorrectedCubemapResource(const Resource::PCC &value)
Definition VTF.cpp:1897
bool setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubeMap, uint16_t newDepth=1)
Definition VTF.cpp:1502
void removeKeyValuesDataResource()
Definition VTF.cpp:1956
void computeFallback(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition VTF.cpp:2296
CompressionMethod compressionMethod
Definition VTF.h:564
void computeReflectivity()
Definition VTF.cpp:1526
uint8_t getThumbnailWidth() const
Definition VTF.cpp:1603
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition VTF.cpp:2246
uint8_t consoleMipScale
Definition VTF.h:553
uint8_t fallbackWidth
Definition VTF.h:548
bool hasNativeResourceSupport() const
Definition VTF.cpp:1631
ImageFormat getFormat() const
Definition VTF.cpp:1339
void setPlatform(Platform newPlatform)
Definition VTF.cpp:1079
uint16_t getHeight(uint8_t mip=0) const
Definition VTF.cpp:1221
uint32_t flagsExtra
Definition VTF.h:533
VTF & operator=(const VTF &other)
Definition VTF.cpp:887
void removeFallback()
Definition VTF.cpp:2323
void removeFlagsExtra(uint32_t flags_)
Definition VTF.cpp:1288
uint8_t getPaddedFallbackHeight(uint8_t mip=0) const
Definition VTF.cpp:1623
uint16_t getCompressionLevel() const
Definition VTF.cpp:1989
sourcepp::math::Vec3f reflectivity
Definition VTF.h:538
uint16_t getWidth(uint8_t mip=0) const
Definition VTF.cpp:1213
uint16_t startFrame
Definition VTF.h:536
void setThumbnail(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:2184
uint8_t mipCount
Definition VTF.h:542
bool setRecommendedMipCount()
Definition VTF.cpp:1385
static bool createInternal(VTF &writer, CreationOptions options)
Definition VTF.cpp:930
std::vector< std::byte > getParticleSheetFrameDataAsRGBA8888(uint16_t &spriteWidth, uint16_t &spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds=0, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
Definition VTF.cpp:1878
void regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newDepth, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:1719
bool hasFallbackData() const
Definition VTF.cpp:2257
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition VTF.cpp:1392
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
Definition VTF.cpp:1205
uint8_t thumbnailWidth
Definition VTF.h:545
void setCompressionLevel(int16_t newCompressionLevel)
Definition VTF.cpp:1993
uint8_t fallbackHeight
Definition VTF.h:549
ImageConversion::ResizeMethod imageHeightResizeMethod
Definition VTF.h:566
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:1343
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition VTF.cpp:1200
std::vector< std::byte > getParticleSheetFrameDataAs(ImageFormat newFormat, uint16_t &spriteWidth, uint16_t &spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds=0, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
Definition VTF.cpp:1874
void addFlagsExtra(uint32_t flags_)
Definition VTF.cpp:1284
float getBumpMapScale() const
Definition VTF.cpp:1591
void setKeyValuesDataResource(std::string_view value)
Definition VTF.cpp:1946
void computeTransparencyFlags()
Definition VTF.cpp:1312
std::vector< std::byte > saveFallbackToFile(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition VTF.cpp:2330
std::vector< std::byte > data
Definition VTF.h:526
ImageFormat format
Definition VTF.h:541
Platform getPlatform() const
Definition VTF.cpp:1075
std::vector< std::byte > getPaletteResourceFrame(uint16_t frame=0) const
Definition VTF.cpp:1825
void addFlags(uint32_t flags_)
Definition VTF.cpp:1268
uint8_t getFaceCount() const
Definition VTF.cpp:1456
ImageFormat thumbnailFormat
Definition VTF.h:544
void setFlags(uint32_t flags_)
Definition VTF.cpp:1264
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
Definition VTF.cpp:1229
void removeLODResource()
Definition VTF.cpp:1934
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
Definition VTF.cpp:1196
void setBumpMapScale(float newBumpMapScale)
Definition VTF.cpp:1595
ImageFormat getThumbnailFormat() const
Definition VTF.cpp:1599
uint16_t getStartFrame() const
Definition VTF.cpp:1510
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
Definition VTF.h:246
bool hasThumbnailData() const
Definition VTF.cpp:2154
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
Definition VTF.cpp:1522
void setSRGB(bool srgb)
Definition VTF.cpp:1296
const std::vector< Resource > & getResources() const
Definition VTF.cpp:1635
void removeHotspotDataResource()
Definition VTF.cpp:1985
void setVersion(uint32_t newVersion)
Definition VTF.cpp:1161
std::vector< std::byte > saveImageToFile(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0, ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition VTF.cpp:2143
Resource * getResourceInternal(Resource::Type type)
Definition VTF.cpp:1648
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:2216
std::vector< std::byte > bake() const
Definition VTF.cpp:2450
bool hasImageData() const
Definition VTF.cpp:2015
ImageConversion::ResizeMethod imageWidthResizeMethod
Definition VTF.h:565
uint16_t compressionLevel
Definition VTF.h:563
std::span< const std::byte > getFallbackDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition VTF.cpp:2261
uint8_t fallbackMipCount
Definition VTF.h:550
void removeParticleSheetResource()
Definition VTF.cpp:1893
std::vector< std::byte > getFallbackDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition VTF.cpp:2279
uint8_t getFallbackWidth(uint8_t mip=0) const
Definition VTF.cpp:1611
static bool create(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path &vtfPath, const CreationOptions &options)
Definition VTF.cpp:988
std::vector< std::byte > getImageDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
Definition VTF.cpp:2052
uint16_t getFrameCount() const
Definition VTF.cpp:1444
uint8_t getMipCount() const
Definition VTF.cpp:1367
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
Definition VTF.cpp:1925
void removeResourceInternal(Resource::Type type)
Definition VTF.cpp:1715
std::vector< std::byte > getFallbackDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition VTF.cpp:2292
bool setFrameCount(uint16_t newFrameCount)
Definition VTF.cpp:1448
void removeAuthorInfoResource()
Definition VTF.cpp:1970
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
Definition VTF.cpp:1192
std::vector< std::byte > getImageDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
Definition VTF.cpp:2037
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
Definition VTF.cpp:1326
void setStartFrame(uint16_t newStartFrame)
Definition VTF.cpp:1514
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
Definition VTF.cpp:2172
std::span< const std::byte > getImageDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
Definition VTF.cpp:2019
uint16_t depth
Definition VTF.h:556
sourcepp::math::Vec3f getReflectivity() const
Definition VTF.cpp:1518
uint8_t getFallbackMipCount() const
Definition VTF.cpp:1627
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
Definition VTF.cpp:2180
void setFlagsExtra(uint32_t flags_)
Definition VTF.cpp:1280
uint8_t getConsoleMipScale() const
Definition VTF.cpp:2342
uint16_t getDepth(uint8_t mip=0) const
Definition VTF.cpp:1490
bool isSRGB() const
Definition VTF.cpp:1292
uint32_t flags
Definition VTF.h:532
uint16_t getPaddedHeight(uint8_t mip=0) const
Definition VTF.cpp:1225
void removeThumbnail()
Definition VTF.cpp:2239
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
Definition VTF.h:243
uint32_t version
Definition VTF.h:528
std::span< const std::byte > getThumbnailDataRaw() const
Definition VTF.cpp:2158
bool setMipCount(uint8_t newMipCount)
Definition VTF.cpp:1371
void setExtendedFlagsResource(uint32_t value)
Definition VTF.cpp:1938
void removeParallaxCorrectedCubemapResource()
Definition VTF.cpp:1913
CompressionMethod getCompressionMethod() const
Definition VTF.cpp:2001
uint16_t getPaddedWidth(uint8_t mip=0) const
Definition VTF.cpp:1217
void setAuthorInfoResource(std::string_view value)
Definition VTF.cpp:1960
void setCRCResource(uint32_t value)
Definition VTF.cpp:1917
void setCompressionMethod(CompressionMethod newCompressionMethod)
Definition VTF.cpp:2005
bool setFaceCount(bool isCubeMap)
Definition VTF.cpp:1482
uint8_t getThumbnailHeight() const
Definition VTF.cpp:1607
void setConsoleMipScale(uint8_t consoleMipScale_)
Definition VTF.cpp:2346
const Resource * getResource(Resource::Type type) const
Definition VTF.cpp:1639
uint32_t getFlags() const
Definition VTF.cpp:1260
void setParticleSheetResource(const SHT &value)
Definition VTF.cpp:1882
@ FLAG_MASK_V5
Definition VTF.h:186
@ FLAG_V0_ONE_BIT_ALPHA
Definition VTF.h:137
@ FLAG_MASK_V4_TF2
Definition VTF.h:180
@ FLAG_V0_MULTI_BIT_ALPHA
Definition VTF.h:138
@ FLAG_MASK_V2
Definition VTF.h:157
@ FLAG_MASK_V5_CSGO
Definition VTF.h:193
@ FLAG_V0_NO_LOD
Definition VTF.h:134
@ FLAG_V4_SRGB
Definition VTF.h:172
@ FLAG_V0_NO_MIP
Definition VTF.h:133
@ FLAG_V5_SRGB
Definition VTF.h:183
@ FLAG_MASK_XBOX
Definition VTF.h:163
@ FLAG_V5_PWL_CORRECTED
Definition VTF.h:182
@ FLAG_MASK_V4
Definition VTF.h:173
@ FLAG_MASK_V3
Definition VTF.h:170
@ FLAG_V0_ENVMAP
Definition VTF.h:139
@ FLAG_MASK_V1
Definition VTF.h:150
@ FLAG_MASK_INTERNAL
Definition VTF.h:198
uint16_t height
Definition VTF.h:531
void removeCRCResource()
Definition VTF.cpp:1921
uint16_t frameCount
Definition VTF.h:535
uint8_t getFallbackHeight(uint8_t mip=0) const
Definition VTF.cpp:1619
uint32_t getVersion() const
Definition VTF.cpp:1157
@ FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE
Definition VTF.h:202
void setHotspotDataResource(const HOT &value)
Definition VTF.cpp:1974
void removeFlags(uint32_t flags_)
Definition VTF.cpp:1272
uint32_t getFlagsExtra() const
Definition VTF.cpp:1276
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
Definition VTF.cpp:1657
bool setDepth(uint16_t newDepth)
Definition VTF.cpp:1494
uint64_t estimateBakeSize() const
Definition VTF.cpp:2350
std::vector< Resource > resources
Definition VTF.h:559
uint8_t thumbnailHeight
Definition VTF.h:546
Platform platform
Definition VTF.h:562
uint8_t getPaddedFallbackWidth(uint8_t mip=0) const
Definition VTF.cpp:1615
bool setImage(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:2056
std::vector< std::byte > getParticleSheetFrameDataRaw(uint16_t &spriteWidth, uint16_t &spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds=0, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
Definition VTF.cpp:1832
void removeExtendedFlagsResource()
Definition VTF.cpp:1942
bool opened
Definition VTF.h:524
float bumpMapScale
Definition VTF.h:540
std::optional< std::vector< std::byte > > compressValveLZMA(std::span< const std::byte > data, uint8_t compressLevel=6)
Definition LZMA.cpp:8
constexpr auto VALVE_LZMA_SIGNATURE
Definition LZMA.h:13
std::optional< std::vector< std::byte > > decompressValveLZMA(std::span< const std::byte > data)
Definition LZMA.cpp:51
bool writeFileBuffer(const std::filesystem::path &filepath, std::span< const std::byte > buffer)
Definition FS.cpp:37
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
Definition FS.cpp:7
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
Definition Math.h:64
constexpr uint32_t log2ceil(uint32_t value)
Definition Math.h:60
void toLower(std::string &input)
Definition String.cpp:167
void invertGreenChannelForImageData(std::span< std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel on the given image data. Meant for converting normal maps between OpenGL and...
std::vector< std::byte > convertFileToImageData(std::span< const std::byte > fileData, ImageFormat &format, int &width, int &height, int &frameCount)
void setResizedDims(uint16_t &width, ResizeMethod widthResize, uint16_t &height, ResizeMethod heightResize)
Set the new image dimensions given a resize method.
std::vector< std::byte > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts an image from one format to another.
std::vector< std::byte > convertSeveralImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
void gammaCorrectImageData(std::span< std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, float gamma)
Perform gamma correction on the given image data. Will not perform gamma correction if the input imag...
std::vector< std::byte > cropImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset)
Crops the given image to the new dimensions. If the image format is compressed it will be converted t...
std::vector< std::byte > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT, float quality=-1.f)
Converts image data to the given file format (PNG or EXR by default). Quality parameter (0-1) is igno...
std::vector< std::byte > resizeImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, bool premultipliedAlpha, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions.
constexpr uint8_t getMaximumMipCount(uint16_t width, uint16_t height, uint16_t depth=1)
Calculate the largest mip count a texture with the given width, height, and depth can contain.
constexpr std::pair< uint16_t, uint16_t > getMipDims(uint8_t mip, uint16_t width, uint16_t height, bool addCompressedFormatPadding=false)
Get the width and height at a given mip level.
constexpr uint16_t getMipDim(uint8_t mip, uint16_t dim, bool addCompressedFormatPadding=false)
Get the dimension at a given mip level.
constexpr uint32_t getDataLength(ImageFormat format, uint16_t width, uint16_t height, uint16_t depth=1)
Calculate the amount of data required to store a texture with the given format and dimensions.
constexpr bool getDataPosition(uint32_t &offset, uint32_t &length, ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice=0, uint16_t depth=1)
Find the position of a specific mip, frame, face, and slice within a texture.
constexpr bool large(ImageFormat format)
Check if the given format is representable by RGBA8888 without losing data.
constexpr bool transparent(ImageFormat format)
Check if the given format can store transparency.
constexpr uint32_t getDataLengthXBOX(bool padded, ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth=1)
Calculate the amount of data required to store an XBOX platform texture with the given format,...
constexpr int8_t decompressedAlpha(ImageFormat format)
Get the number of bits of precision of the alpha channel in the given format regardless of compressio...
constexpr uint8_t bpp(ImageFormat format)
Find the bits per pixel of the given format.
constexpr bool getDataPositionXBOX(uint32_t &offset, uint32_t &length, bool padded, ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice=0, uint16_t depth=1)
Find the position of a specific mip, frame, face, and slice within an XBOX platform texture.
constexpr bool compressed(ImageFormat format)
Check if the given format is a compressed format (DXT1, DXT3, DXT5, ATI1N, ATI2N, BC7,...
std::vector< std::byte > convertP8ImageDataToBGRA8888(std::span< const std::byte > paletteData, std::span< const std::byte > imageData)
Converts a paletted image to something usable.
constexpr uint32_t VTF_SIGNATURE
Definition VTF.h:23
constexpr uint32_t XTF_SIGNATURE
Definition VTF.h:24
constexpr uint32_t VTFX_SIGNATURE
Definition VTF.h:25
constexpr uint32_t VTF3_SIGNATURE
Definition VTF.h:26
CompressionMethod
Definition VTF.h:28
std::pair< uint16_t, uint16_t > clamp(uint16_t width, uint16_t height) const
sourcepp::math::Mat4x4f inverseTransform
Definition VTF.h:67
sourcepp::math::Vec4d origin
Definition VTF.h:66
std::string getDataAsAuthorInfo() const
Definition VTF.cpp:385
HOT getDataAsHotspotData() const
Definition VTF.cpp:389
std::tuple< uint8_t, uint8_t, uint8_t, uint8_t > getDataAsLODControlInfo() const
Definition VTF.cpp:377
Type type
Definition VTF.h:61
ConvertedData convertData() const
Definition VTF.cpp:296
std::string getDataAsKeyValuesData() const
Definition VTF.cpp:381
int16_t getDataAsAuxCompressionLevel() const
Definition VTF.cpp:393
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT, PCC > ConvertedData
Definition VTF.h:70
CompressionMethod getDataAsAuxCompressionMethod() const
Definition VTF.cpp:400
SHT getDataAsParticleSheet() const
Definition VTF.cpp:361
uint32_t getDataAsFlags() const
Definition VTF.cpp:373
@ FLAG_LOCAL_DATA
Definition VTF.h:58
PCC getDataAsParallaxCorrectedCubemap() const
Definition VTF.cpp:365
uint32_t getDataAsCRC() const
Definition VTF.cpp:369
uint32_t getDataAsAuxCompressionLength(uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint16_t face, uint16_t faceCount) const
Definition VTF.cpp:411
@ TYPE_PARALLAX_CORRECTED_CUBEMAP
Definition VTF.h:47
@ TYPE_KEYVALUES_DATA
Definition VTF.h:51
@ TYPE_FALLBACK_DATA
Definition VTF.h:42
@ TYPE_PALETTE_DATA
Definition VTF.h:41
@ TYPE_PARTICLE_SHEET_DATA
Definition VTF.h:43
@ TYPE_AUTHOR_INFO
Definition VTF.h:52
@ TYPE_EXTENDED_FLAGS
Definition VTF.h:46
@ TYPE_IMAGE_DATA
Definition VTF.h:45
@ TYPE_AUX_COMPRESSION
Definition VTF.h:49
@ TYPE_LOD_CONTROL_INFO
Definition VTF.h:50
@ TYPE_THUMBNAIL_DATA
Definition VTF.h:40
@ TYPE_SOURCEPP_FLAGS
Definition VTF.h:53
@ TYPE_HOTSPOT_DATA
Definition VTF.h:44
Flags flags
Definition VTF.h:62
std::vector< std::byte > getDataAsPalette(uint16_t frame) const
Definition VTF.cpp:353
std::span< std::byte > data
Definition VTF.h:63
uint16_t initialFrameCount
Definition VTF.h:225
ImageConversion::ResizeBounds resizeBounds
Definition VTF.h:224
ImageConversion::ResizeFilter filter
Definition VTF.h:221
ImageConversion::ResizeMethod heightResizeMethod
Definition VTF.h:220
CompressionMethod compressionMethod
Definition VTF.h:235
ImageConversion::ResizeMethod widthResizeMethod
Definition VTF.h:219
ImageFormat outputFormat
Definition VTF.h:217