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