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#include <vtfpp/VTF.h>
2
3#include <algorithm>
4#include <cstring>
5#include <unordered_map>
6#include <utility>
7
8#ifdef SOURCEPP_BUILD_WITH_TBB
9#include <execution>
10#endif
11
12#ifdef SOURCEPP_BUILD_WITH_THREADS
13#include <future>
14#include <thread>
15#endif
16
17#include <BufferStream.h>
18#include <miniz.h>
19#include <zstd.h>
20
23
24using namespace sourcepp;
25using namespace vtfpp;
26
27namespace {
28
29std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level, CompressionMethod method) {
30 switch (method) {
31 using enum CompressionMethod;
32 case DEFLATE: {
33 mz_ulong compressedSize = mz_compressBound(data.size());
34 std::vector<std::byte> out(compressedSize);
35
36 int status = MZ_OK;
37 while ((status = mz_compress2(reinterpret_cast<unsigned char*>(out.data()), &compressedSize, reinterpret_cast<const unsigned char*>(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
38 compressedSize *= 2;
39 out.resize(compressedSize);
40 }
41
42 if (status != MZ_OK) {
43 return {};
44 }
45 out.resize(compressedSize);
46 return out;
47 }
48 case ZSTD: {
49 if (level < 0) {
50 level = 6;
51 }
52
53 auto expectedSize = ZSTD_compressBound(data.size());
54 std::vector<std::byte> out(expectedSize);
55
56 auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
57 if (ZSTD_isError(compressedSize)) {
58 return {};
59 }
60
61 out.resize(compressedSize);
62 return out;
63 }
64 case CONSOLE_LZMA: {
65 const auto out = compression::compressValveLZMA(data, level);
66 if (out) {
67 return *out;
68 }
69 return {};
70 }
71 }
72 return {};
73}
74
75void swapImageDataEndianForConsole(std::span<std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, VTF::Platform platform) {
76 if (imageData.empty() || format == ImageFormat::EMPTY || platform == VTF::PLATFORM_PC) {
77 return;
78 }
79
80 switch (format) {
81 using enum ImageFormat;
82 case BGRA8888:
83 case BGRX8888:
84 case UVWQ8888:
85 case UVLX8888: {
86 const auto newData = ImageConversion::convertImageDataToFormat(imageData, ImageFormat::ARGB8888, ImageFormat::BGRA8888, width, height);
87 std::copy(newData.begin(), newData.end(), imageData.begin());
88 break;
89 }
90 case DXT1:
92 case DXT3:
93 case DXT5:
94 case UV88: {
95 if (platform != VTF::PLATFORM_X360) {
96 break;
97 }
98 std::span<uint16_t> dxtData{reinterpret_cast<uint16_t*>(imageData.data()), imageData.size() / sizeof(uint16_t)};
99 std::for_each(
100#ifdef SOURCEPP_BUILD_WITH_TBB
101 std::execution::par_unseq,
102#endif
103 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
104 BufferStream::swap_endian(&value);
105 });
106 break;
107 }
108 default:
109 // SOURCEPP_DEBUG_BREAK;
110 break;
111 }
112}
113
114} // namespace
115
117 switch (this->type) {
119 if (this->data.size() <= sizeof(uint32_t)) {
120 return {};
121 }
122 return SHT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
123 case TYPE_CRC:
125 if (this->data.size() != sizeof(uint32_t)) {
126 return {};
127 }
128 return *reinterpret_cast<const uint32_t*>(this->data.data());
130 if (this->data.size() != sizeof(uint32_t)) {
131 return {};
132 }
133 return std::make_tuple(
134 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 0),
135 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 1),
136 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 2),
137 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 3));
139 if (this->data.size() <= sizeof(uint32_t)) {
140 return "";
141 }
142 return std::string(reinterpret_cast<const char*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data()));
144 if (this->data.size() <= sizeof(uint32_t)) {
145 return {};
146 }
147 return HOT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
149 if (this->data.size() <= sizeof(uint32_t) || this->data.size() % sizeof(uint32_t) != 0) {
150 return {};
151 }
152 return std::span{reinterpret_cast<uint32_t*>(this->data.data()), this->data.size() / 4};
153 default:
154 break;
155 }
156 return {};
157}
158
160 this->version = 4;
161
162 this->flags |= FLAG_NO_MIP | FLAG_NO_LOD;
163
164 this->format = ImageFormat::EMPTY;
166
167 this->width = 0;
168 this->height = 0;
169 this->mipCount = 0;
170 this->frameCount = 0;
171 this->sliceCount = 0;
172
173 this->opened = true;
174}
175
176VTF::VTF(std::vector<std::byte>&& vtfData, bool parseHeaderOnly)
177 : data(std::move(vtfData)) {
178 BufferStreamReadOnly stream{this->data};
179
180 if (auto signature = stream.read<uint32_t>(); signature == VTF_SIGNATURE) {
181 this->platform = PLATFORM_PC;
182 if (stream.read<uint32_t>() != 7) {
183 return;
184 }
185 stream >> this->version;
186 if (this->version > 6) {
187 return;
188 }
189 } else if (signature == VTFX_SIGNATURE || signature == VTF3_SIGNATURE) {
190 stream.set_big_endian(true);
191 uint32_t minorConsoleVersion = 0;
192 stream >> this->platform >> minorConsoleVersion;
193 if (minorConsoleVersion != 8) {
194 return;
195 }
196 if (signature == VTF3_SIGNATURE) {
197 this->platform = PLATFORM_PS3_PORTAL2;
198 }
199 switch (this->platform) {
201 case PLATFORM_X360:
202 this->version = 4;
203 break;
205 this->version = 5;
206 break;
207 default:
208 this->platform = PLATFORM_UNKNOWN;
209 return;
210 }
211 } else {
212 return;
213 }
214
215 const auto headerSize = stream.read<uint32_t>();
216
217 const auto readResources = [this, &stream](uint32_t resourceCount) {
218 // Read resource header info
219 this->resources.reserve(resourceCount);
220 for (int i = 0; i < resourceCount; i++) {
221 auto& [type, flags_, data_] = this->resources.emplace_back();
222
223 auto typeAndFlags = stream.read<uint32_t>();
224 if (stream.is_big_endian()) {
225 // This field is little-endian
226 BufferStream::swap_endian(&typeAndFlags);
227 }
228 type = static_cast<Resource::Type>(typeAndFlags & 0xffffff); // last 3 bytes
229 flags_ = static_cast<Resource::Flags>(typeAndFlags >> 24); // first byte
230 data_ = stream.read_span<std::byte>(4);
231
232 if (stream.is_big_endian() && !(flags_ & Resource::FLAG_LOCAL_DATA)) {
233 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(data_.data()));
234 }
235 }
236
237 // Sort resources by their offset, in case certain VTFs are written
238 // weirdly and have resource data written out of order. So far I have
239 // found only one VTF in an official Valve game where this is the case
240 std::sort(this->resources.begin(), this->resources.end(), [](const Resource& lhs, const Resource& rhs) {
241 if ((lhs.flags & Resource::FLAG_LOCAL_DATA) && (rhs.flags & Resource::FLAG_LOCAL_DATA)) {
242 return lhs.type < rhs.type;
243 }
245 return true;
246 }
248 return false;
249 }
250 return *reinterpret_cast<uint32_t*>(lhs.data.data()) < *reinterpret_cast<uint32_t*>(rhs.data.data());
251 });
252
253 // Fix up data spans to point to the actual data
254 Resource* lastResource = nullptr;
255 for (auto& resource : this->resources) {
256 if (!(resource.flags & Resource::FLAG_LOCAL_DATA)) {
257 if (lastResource) {
258 auto lastOffset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
259 auto currentOffset = *reinterpret_cast<uint32_t*>(resource.data.data());
260 auto curPos = stream.tell();
261 stream.seek(lastOffset);
262 lastResource->data = stream.read_span<std::byte>(currentOffset - lastOffset);
263 stream.seek(static_cast<int64_t>(curPos));
264 }
265 lastResource = &resource;
266 }
267 }
268 if (lastResource) {
269 auto offset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
270 auto curPos = stream.tell();
271 stream.seek(offset);
272 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
273 stream.seek(static_cast<int64_t>(curPos));
274 }
275 };
276
277 if (this->platform != PLATFORM_PC) {
278 uint8_t resourceCount;
279 stream
280 .read(this->flags)
281 .read(this->width)
282 .read(this->height)
283 .read(this->sliceCount)
284 .read(this->frameCount)
285 .skip<uint16_t>() // preload
286 .skip<uint8_t>() // skip high mip levels
287 .read(resourceCount)
288 .read(this->reflectivity[0])
289 .read(this->reflectivity[1])
290 .read(this->reflectivity[2])
291 .read(this->bumpMapScale)
292 .read(this->format)
293 .skip<math::Vec4ui8>() // lowResImageSample (replacement for thumbnail resource, presumably linear color)
294 .skip<uint32_t>(); // compressedLength
295
296 if (this->platform == PLATFORM_PS3_PORTAL2) {
297 stream.skip<uint32_t>();
298 }
299
300 this->mipCount = (this->flags & FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height);
301
302 if (parseHeaderOnly) {
303 this->opened = true;
304 return;
305 }
306
307 this->resources.reserve(resourceCount);
308 readResources(resourceCount);
309
310 this->opened = stream.tell() == headerSize;
311
312 // The resources vector isn't modified by setResourceInternal when we're not adding a new one, so this is fine
313 for (const auto& resource : this->resources) {
314 // Decompress LZMA resources
315 if (BufferStreamReadOnly rsrcStream{resource.data.data(), resource.data.size()}; rsrcStream.read<uint32_t>() == compression::VALVE_LZMA_SIGNATURE) {
316 if (auto decompressedData = compression::decompressValveLZMA(resource.data)) {
317 this->setResourceInternal(resource.type, *decompressedData);
318
319 if (resource.type == Resource::TYPE_IMAGE_DATA) {
320 // Do this here because compressionLength in header can be garbage on PS3
321 this->compressionMethod = CompressionMethod::CONSOLE_LZMA;
322 }
323 }
324 }
325
326 // Note: LOD, CRC, TS0 may be incorrect - I can't find a sample of any in official console VTFs
327 // If tweaking this switch, tweak the one in bake as well
328 switch (resource.type) {
329 default:
333 case Resource::TYPE_AUX_COMPRESSION: // Strata-specific
334 break;
336 ::swapImageDataEndianForConsole(resource.data, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, this->platform);
337 break;
339 ::swapImageDataEndianForConsole(resource.data, this->format, this->width, this->height, this->platform);
340 break;
344 if (resource.data.size() >= sizeof(uint32_t)) {
345 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resource.data.data()));
346 }
347 break;
348 }
349 }
350 return;
351 }
352
353 stream
354 .read(this->width)
355 .read(this->height)
356 .read(this->flags)
357 .read(this->frameCount)
358 .read(this->startFrame)
359 .skip(4)
360 .read(this->reflectivity[0])
361 .read(this->reflectivity[1])
362 .read(this->reflectivity[2])
363 .skip(4)
364 .read(this->bumpMapScale)
365 .read(this->format)
366 .read(this->mipCount);
367
368 // This will always be DXT1
369 stream.skip<ImageFormat>();
370 stream >> this->thumbnailWidth >> this->thumbnailHeight;
371 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
372 this->thumbnailFormat = ImageFormat::EMPTY;
373 } else {
374 this->thumbnailFormat = ImageFormat::DXT1;
375 }
376
377 if (this->version < 2) {
378 this->sliceCount = 1;
379 } else {
380 stream.read(this->sliceCount);
381 }
382
383 if (parseHeaderOnly) {
384 this->opened = true;
385 return;
386 }
387
388 if (this->version >= 3) {
389 stream.skip(3);
390 auto resourceCount = stream.read<uint32_t>();
391 stream.skip(8);
392 readResources(resourceCount);
393
394 this->opened = stream.tell() == headerSize;
395
396 if (this->opened && this->version >= 6) {
397 const auto* auxResource = this->getResource(Resource::TYPE_AUX_COMPRESSION);
398 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
399 if (auxResource && imageResource) {
400 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
401 const auto faceCount = this->getFaceCount();
402 std::vector<std::byte> decompressedImageData(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->sliceCount));
403 uint32_t oldOffset = 0;
404 for (int i = this->mipCount - 1; i >= 0; i--) {
405 for (int j = 0; j < this->frameCount; j++) {
406 for (int k = 0; k < faceCount; k++) {
407 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
408 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->getSliceCount())) {
409 // Keep in mind that slices are compressed together
410 mz_ulong decompressedImageDataSize = newLength * this->sliceCount;
411 switch (auxResource->getDataAsAuxCompressionMethod()) {
412 using enum CompressionMethod;
413 case DEFLATE:
414 if (mz_uncompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
415 this->opened = false;
416 return;
417 }
418 break;
419 case ZSTD:
420 if (auto decompressedSize = ZSTD_decompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength); ZSTD_isError(decompressedSize) || decompressedSize != decompressedImageDataSize) {
421 this->opened = false;
422 return;
423 }
424 break;
425 case CONSOLE_LZMA:
426 // Shouldn't be here!
428 break;
429 }
430 }
431 oldOffset += oldLength;
432 }
433 }
434 }
435 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, decompressedImageData);
436 }
437 }
438 }
439 } else {
440 stream.skip(math::paddingForAlignment(16, stream.tell()));
441 this->opened = stream.tell() == headerSize;
442
443 this->resources.reserve(2);
444
445 if (this->hasThumbnailData()) {
446 this->resources.push_back({
448 .flags = Resource::FLAG_NONE,
449 .data = stream.read_span<std::byte>(ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight)),
450 });
451 }
452 if (this->hasImageData()) {
453 this->resources.push_back({
455 .flags = Resource::FLAG_NONE,
456 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
457 });
458 }
459 }
460
461 if (const auto* resource = this->getResource(Resource::TYPE_AUX_COMPRESSION)) {
462 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
463 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
464 this->removeResourceInternal(Resource::TYPE_AUX_COMPRESSION);
465 }
466}
467
468VTF::VTF(std::span<const std::byte> vtfData, bool parseHeaderOnly)
469 : VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly) {}
470
471VTF::VTF(const std::string& vtfPath, bool parseHeaderOnly)
472 : VTF(fs::readFileBuffer(vtfPath), parseHeaderOnly) {}
473
474VTF::VTF(const VTF& other) {
475 *this = other;
476}
477
478VTF& VTF::operator=(const VTF& other) {
479 this->opened = other.opened;
480 this->data = other.data;
481 this->version = other.version;
482 this->width = other.width;
483 this->height = other.height;
484 this->flags = other.flags;
485 this->frameCount = other.frameCount;
486 this->startFrame = other.startFrame;
487 this->reflectivity = other.reflectivity;
488 this->bumpMapScale = other.bumpMapScale;
489 this->format = other.format;
490 this->mipCount = other.mipCount;
491 this->thumbnailFormat = other.thumbnailFormat;
492 this->thumbnailWidth = other.thumbnailWidth;
493 this->thumbnailHeight = other.thumbnailHeight;
494 this->sliceCount = other.sliceCount;
495
496 this->resources.clear();
497 for (const auto& [otherType, otherFlags, otherData] : other.resources) {
498 auto& [type, flags_, data_] = this->resources.emplace_back();
499 type = otherType;
500 flags_ = otherFlags;
501 data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
502 }
503
504 this->platform = other.platform;
509
510 return *this;
511}
512
513VTF::operator bool() const {
514 return this->opened;
515}
516
518 bool out = true;
519 if (writer.hasImageData() && (options.invertGreenChannel || options.gammaCorrection != 1.f)) {
520 for (int i = 1; i < writer.mipCount; i++) {
521 for (int j = 0; j < writer.frameCount; j++) {
522 for (int k = 0; k < writer.getFaceCount(); k++) {
523 for (int l = 0; l < writer.sliceCount; l++) {
524 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)) {
525 out = false;
526 }
527 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)) {
528 out = false;
529 }
530 }
531 }
532 }
533 }
534 }
535 writer.setPlatform(options.platform);
536 if (options.computeReflectivity) {
537 writer.computeReflectivity();
538 }
539 if (options.initialFrameCount > 1 || options.isCubeMap || options.initialSliceCount > 1) {
540 if (!writer.setFrameFaceAndSliceCount(options.initialFrameCount, options.isCubeMap, options.initialSliceCount)) {
541 out = false;
542 }
543 }
544 writer.setStartFrame(options.startFrame);
545 writer.setBumpMapScale(options.bumpMapScale);
546 if (options.computeThumbnail) {
547 writer.computeThumbnail();
548 }
549 if (options.outputFormat == VTF::FORMAT_UNCHANGED) {
550 options.outputFormat = writer.getFormat();
551 } else if (options.outputFormat == VTF::FORMAT_DEFAULT) {
552 options.outputFormat = VTF::getDefaultCompressedFormat(writer.getFormat(), writer.getVersion(), options.isCubeMap);
553 }
554 if (options.computeTransparencyFlags) {
556 }
557 if (options.computeMips) {
559 out = false;
560 }
561 writer.computeMips(options.filter);
562 }
563 writer.setFormat(options.outputFormat);
566 return out;
567}
568
569bool VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, CreationOptions options) {
570 VTF writer;
571 writer.setVersion(options.version);
572 writer.addFlags(options.flags);
574 if (!writer.setImage(imageData, format, width, height, options.filter)) {
575 return false;
576 }
577 if (!createInternal(writer, options)) {
578 return false;
579 }
580 return writer.bake(vtfPath);
581}
582
583bool VTF::create(ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, CreationOptions options) {
584 std::vector<std::byte> imageData;
585 imageData.resize(static_cast<uint32_t>(width) * height * ImageFormatDetails::bpp(format) / 8);
586 return create(imageData, format, width, height, vtfPath, options);
587}
588
589VTF VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, CreationOptions options) {
590 VTF writer;
591 writer.setVersion(options.version);
592 writer.addFlags(options.flags);
594 writer.setImage(imageData, format, width, height, options.filter);
595 createInternal(writer, options);
596 return writer;
597}
598
599VTF VTF::create(ImageFormat format, uint16_t width, uint16_t height, CreationOptions options) {
600 std::vector<std::byte> imageData;
601 imageData.resize(static_cast<uint32_t>(width) * height * ImageFormatDetails::bpp(format) / 8);
602 return create(imageData, format, width, height, options);
603}
604
605bool VTF::create(const std::string& imagePath, const std::string& vtfPath, CreationOptions options) {
606 VTF writer;
607 writer.setVersion(options.version);
608 writer.addFlags(options.flags);
610 if (!writer.setImage(imagePath, options.filter)) {
611 return false;
612 }
613 if (!createInternal(writer, options)) {
614 return false;
615 }
616 return writer.bake(vtfPath);
617}
618
619VTF VTF::create(const std::string& imagePath, CreationOptions options) {
620 VTF writer;
621 writer.setVersion(options.version);
622 writer.addFlags(options.flags);
624 writer.setImage(imagePath, options.filter);
625 createInternal(writer, options);
626 return writer;
627}
628
630 return this->platform;
631}
632
633void VTF::setPlatform(Platform newPlatform) {
634 if (this->platform != PLATFORM_PC) {
635 if (this->platform == PLATFORM_X360 || this->platform == PLATFORM_PS3_ORANGEBOX) {
636 this->setVersion(4);
637 } else /*if (this->platform == PLATFORM_PS3_PORTAL2)*/ {
638 this->setVersion(5);
639 }
640
641 const auto recommendedCount = (this->flags & VTF::FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height);
642 if (this->mipCount != recommendedCount) {
643 this->setMipCount(recommendedCount);
644 }
645 }
646 this->platform = newPlatform;
648}
649
650uint32_t VTF::getVersion() const {
651 return this->version;
652}
653
654void VTF::setVersion(uint32_t newVersion) {
655 if (this->platform != PLATFORM_PC) {
656 return;
657 }
658 if (this->hasImageData()) {
659 auto faceCount = this->getFaceCount();
660 if (faceCount == 7 && (newVersion < 1 || newVersion > 4)) {
661 faceCount = 6;
662 }
663 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, faceCount, this->sliceCount);
664 }
665
666 // Fix up flags
667 bool srgb = this->isSRGB();
668 if ((this->version < 2 && newVersion >= 2) || (this->version >= 2 && newVersion < 2)) {
670 }
671 if ((this->version < 3 && newVersion >= 3) || (this->version >= 3 && newVersion < 3)) {
673 }
674 if ((this->version < 4 && newVersion >= 4) || (this->version >= 4 && newVersion < 4)) {
677 }
678 if ((this->version < 5 && newVersion >= 5) || (this->version >= 5 && newVersion < 5)) {
681 }
682 this->setSRGB(srgb);
683
684 this->version = newVersion;
685}
686
688 return this->imageWidthResizeMethod;
689}
690
692 return this->imageHeightResizeMethod;
693}
694
695void VTF::setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_) {
696 this->imageWidthResizeMethod = imageWidthResizeMethod_;
697 this->imageHeightResizeMethod = imageHeightResizeMethod_;
698}
699
701 this->imageWidthResizeMethod = imageWidthResizeMethod_;
702}
703
705 this->imageHeightResizeMethod = imageHeightResizeMethod_;
706}
707
708uint16_t VTF::getWidth(uint8_t mip) const {
709 return mip > 0 ? ImageDimensions::getMipDim(mip, this->width) : this->width;
710}
711
712uint16_t VTF::getHeight(uint8_t mip) const {
713 return mip > 0 ? ImageDimensions::getMipDim(mip, this->height) : this->height;
714}
715
716void VTF::setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter) {
717 if (newWidth == 0 || newHeight == 0) {
718 this->format = ImageFormat::EMPTY;
719 this->width = 0;
720 this->height = 0;
722 return;
723 }
724
726 if (this->hasImageData()) {
727 if (this->width == newWidth && this->height == newHeight) {
728 return;
729 }
730 auto newMipCount = this->mipCount;
731 if (this->platform == VTF::PLATFORM_PC) {
732 if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(this->format, newWidth, newHeight); newMipCount > recommendedCount) {
733 newMipCount = recommendedCount;
734 }
735 } else {
736 newMipCount = (this->flags & VTF::FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(newWidth, newHeight);
737 }
738 this->regenerateImageData(this->format, newWidth, newHeight, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter);
739 } else {
740 this->format = ImageFormat::RGBA8888;
741 this->mipCount = 1;
742 this->frameCount = 1;
743 this->flags &= ~FLAG_ENVMAP;
744 this->width = newWidth;
745 this->height = newHeight;
746 this->sliceCount = 1;
747 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, std::vector<std::byte>(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 1, this->width, this->height, this->sliceCount)));
748 }
749}
750
751uint32_t VTF::getFlags() const {
752 return this->flags;
753}
754
755void VTF::setFlags(uint32_t flags_) {
756 this->flags = (this->flags & FLAGS_MASK_INTERNAL) | (flags_ & ~FLAGS_MASK_INTERNAL);
757}
758
759void VTF::addFlags(uint32_t flags_) {
760 this->flags |= flags_ & ~FLAGS_MASK_INTERNAL;
761}
762
763void VTF::removeFlags(uint32_t flags_) {
764 this->flags &= ~flags_ | FLAGS_MASK_INTERNAL;
765}
766
767bool VTF::isSRGB() const {
768 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);
769}
770
771void VTF::setSRGB(bool srgb) {
772 if (srgb) {
773 if (this->version >= 5) {
774 this->addFlags(FLAG_V5_SRGB);
775 } else if (this->version >= 4) {
776 this->addFlags(FLAG_V4_SRGB);
777 }
778 } else {
779 if (this->version >= 5) {
781 } else if (this->version >= 4) {
783 }
784 }
785}
786
788 if (ImageFormatDetails::transparent(this->format)) {
789 if (ImageFormatDetails::decompressedAlpha(this->format) > 1) {
792 } else {
795 }
796 } else {
799 }
800}
801
802ImageFormat VTF::getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap) {
803 if (inputFormat != ImageFormat::EMPTY) {
804 if (version >= 6 && isCubeMap) {
805 return ImageFormat::BC6H;
806 }
807 if (ImageFormatDetails::decompressedAlpha(inputFormat) > 0) {
808 if (version >= 6) {
809 return ImageFormat::BC7;
810 }
811 return ImageFormat::DXT5;
812 }
813 }
814 return ImageFormat::DXT1;
815}
816
818 return this->format;
819}
820
822 if (newFormat == VTF::FORMAT_UNCHANGED || newFormat == this->format) {
823 return;
824 }
825 if (newFormat == VTF::FORMAT_DEFAULT) {
826 newFormat = VTF::getDefaultCompressedFormat(this->format, this->version, this->getFaceCount() > 1);
827 }
828 if (!this->hasImageData()) {
829 this->format = newFormat;
830 return;
831 }
832 auto newMipCount = this->mipCount;
833 if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(newFormat, this->width, this->height); newMipCount > recommendedCount) {
834 newMipCount = recommendedCount;
835 }
836 if (ImageFormatDetails::compressed(newFormat)) {
837 this->regenerateImageData(newFormat, this->width + math::paddingForAlignment(4, this->width), this->height + math::paddingForAlignment(4, this->height), newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter);
838 } else {
839 this->regenerateImageData(newFormat, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter);
840 }
841}
842
843uint8_t VTF::getMipCount() const {
844 return this->mipCount;
845}
846
847bool VTF::setMipCount(uint8_t newMipCount) {
848 if (!this->hasImageData()) {
849 return false;
850 }
851 if (const auto recommended = ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height); newMipCount > recommended) {
852 newMipCount = recommended;
853 if (newMipCount == 1) {
854 return false;
855 }
856 }
857 if (newMipCount > 1) {
858 this->flags &= ~(FLAG_NO_MIP | FLAG_NO_LOD);
859 } else {
860 this->flags |= FLAG_NO_MIP | FLAG_NO_LOD;
861 }
862 this->regenerateImageData(this->format, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount);
863 return true;
864}
865
867 if (this->platform == VTF::PLATFORM_PC) {
868 return this->setMipCount(ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height));
869 }
870 return this->setMipCount(ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height));
871}
872
874 auto* imageResource = this->getResourceInternal(Resource::TYPE_IMAGE_DATA);
875 if (!imageResource || !this->hasImageData()) {
876 return;
877 }
878
879 if (this->mipCount <= 1) {
880 if (!this->setRecommendedMipCount() || this->mipCount <= 1) {
881 return;
882 }
883 }
884
885 auto* outputDataPtr = imageResource->data.data();
886 const auto faceCount = this->getFaceCount();
887
888#ifdef SOURCEPP_BUILD_WITH_THREADS
889 std::vector<std::future<void>> futures;
890 futures.reserve((this->mipCount - 1) * this->frameCount * faceCount * this->sliceCount);
891#endif
892 for (int j = 0; j < this->frameCount; j++) {
893 for (int k = 0; k < faceCount; k++) {
894 for (int l = 0; l < this->sliceCount; l++) {
895#ifdef SOURCEPP_BUILD_WITH_THREADS
896 futures.push_back(std::async(std::launch::async, [this, filter, outputDataPtr, faceCount, j, k, l] {
897#endif
898 for (int i = 1; i < this->mipCount; i++) {
899 auto mip = ImageConversion::resizeImageData(this->getImageDataRaw(i - 1, j, k, l), this->format, ImageDimensions::getMipDim(i - 1, this->width), ImageDimensions::getMipDim(i, this->width), ImageDimensions::getMipDim(i - 1, this->height), ImageDimensions::getMipDim(i, this->height), this->isSRGB(), filter);
900 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->sliceCount) && mip.size() == length) {
901 std::memcpy(outputDataPtr + offset, mip.data(), length);
902 }
903 }
904#ifdef SOURCEPP_BUILD_WITH_THREADS
905 }));
906 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
907 for (auto& future : futures) {
908 future.get();
909 }
910 futures.clear();
911 }
912#endif
913 }
914 }
915 }
916#ifdef SOURCEPP_BUILD_WITH_THREADS
917 for (auto& future : futures) {
918 future.get();
919 }
920#endif
921}
922
923uint16_t VTF::getFrameCount() const {
924 return this->frameCount;
925}
926
927bool VTF::setFrameCount(uint16_t newFrameCount) {
928 if (!this->hasImageData()) {
929 return false;
930 }
931 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, this->getFaceCount(), this->sliceCount);
932 return true;
933}
934
935uint8_t VTF::getFaceCount() const {
936 if (!this->hasImageData()) {
937 return 0;
938 }
939 const auto* image = this->getResource(Resource::TYPE_IMAGE_DATA);
940 if (!image) {
941 return 0;
942 }
943 if (!(this->flags & VTF::FLAG_ENVMAP)) {
944 return 1;
945 }
946 if (this->version >= 6) {
947 // All v7.6 VTFs are sane, and we need this special case to fix a bug in the parser where
948 // it won't recognize cubemaps as cubemaps because the image resource is compressed!
949 return 6;
950 }
951 const auto expectedLength = ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 6, this->width, this->height, this->sliceCount);
952 if (this->version >= 1 && this->version <= 4 && expectedLength < image->data.size()) {
953 return 7;
954 }
955 if (expectedLength == image->data.size()) {
956 return 6;
957 }
958 return 1;
959}
960
961bool VTF::setFaceCount(bool isCubemap) {
962 if (!this->hasImageData()) {
963 return false;
964 }
965 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, isCubemap ? ((this->version >= 1 && this->version <= 4) ? 7 : 6) : 1, this->sliceCount);
966 return true;
967}
968
969uint16_t VTF::getSliceCount() const {
970 return this->sliceCount;
971}
972
973bool VTF::setSliceCount(uint16_t newSliceCount) {
974 if (!this->hasImageData()) {
975 return false;
976 }
977 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), newSliceCount);
978 return true;
979}
980
981bool VTF::setFrameFaceAndSliceCount(uint16_t newFrameCount, bool isCubemap, uint16_t newSliceCount) {
982 if (!this->hasImageData()) {
983 return false;
984 }
985 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, isCubemap ? ((this->version >= 1 && this->version <= 4) ? 7 : 6) : 1, newSliceCount);
986 return true;
987}
988
989uint16_t VTF::getStartFrame() const {
990 return this->startFrame;
991}
992
993void VTF::setStartFrame(uint16_t newStartFrame) {
994 this->startFrame = newStartFrame;
995}
996
997math::Vec3f VTF::getReflectivity() const {
998 return this->reflectivity;
999}
1000
1001void VTF::setReflectivity(sourcepp::math::Vec3f newReflectivity) {
1002 this->reflectivity = newReflectivity;
1003}
1004
1006 static constexpr auto getReflectivityForImage = [](const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1007 static constexpr auto getReflectivityForPixel = [](const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1008 // http://markjstock.org/doc/gsd_talk_11_notes.pdf page 11
1009 math::Vec3f ref{static_cast<float>(pixel->r), static_cast<float>(pixel->g), static_cast<float>(pixel->b)};
1010 ref /= 255.f * 0.9f;
1011 ref[0] *= ref[0];
1012 ref[1] *= ref[1];
1013 ref[2] *= ref[2];
1014 return ref;
1015 };
1016
1017 auto rgba8888Data = vtf.getImageDataAsRGBA8888(0, frame, face, slice);
1018 math::Vec3f out{};
1019 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1020 out += getReflectivityForPixel(reinterpret_cast<ImagePixel::RGBA8888*>(rgba8888Data.data() + i));
1021 }
1022 return out / (rgba8888Data.size() / sizeof(ImagePixel::RGBA8888));
1023 };
1024
1025 const auto faceCount = this->getFaceCount();
1026
1027#ifdef SOURCEPP_BUILD_WITH_THREADS
1028 if (this->frameCount > 1 || faceCount > 1 || this->sliceCount > 1) {
1029 std::vector<std::future<math::Vec3f>> futures;
1030 futures.reserve(this->frameCount * faceCount * this->sliceCount);
1031
1032 this->reflectivity = {};
1033 for (int j = 0; j < this->frameCount; j++) {
1034 for (int k = 0; k < faceCount; k++) {
1035 for (int l = 0; l < this->sliceCount; l++) {
1036 futures.push_back(std::async(std::launch::async, [this, j, k, l] {
1037 return getReflectivityForImage(*this, j, k, l);
1038 }));
1039 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
1040 for (auto& future : futures) {
1041 this->reflectivity += future.get();
1042 }
1043 futures.clear();
1044 }
1045 }
1046 }
1047 }
1048
1049 for (auto& future : futures) {
1050 this->reflectivity += future.get();
1051 }
1052 this->reflectivity /= this->frameCount * faceCount * this->sliceCount;
1053 } else {
1054 this->reflectivity = getReflectivityForImage(*this, 0, 0, 0);
1055 }
1056#else
1057 this->reflectivity = {};
1058 for (int j = 0; j < this->frameCount; j++) {
1059 for (int k = 0; k < faceCount; k++) {
1060 for (int l = 0; l < this->sliceCount; l++) {
1061 this->reflectivity += getReflectivityForImage(*this, j, k, l);
1062 }
1063 }
1064 }
1065 this->reflectivity /= this->frameCount * faceCount * this->sliceCount;
1066#endif
1067}
1068
1070 return this->bumpMapScale;
1071}
1072
1073void VTF::setBumpMapScale(float newBumpMapScale) {
1074 this->bumpMapScale = newBumpMapScale;
1075}
1076
1078 return this->thumbnailFormat;
1079}
1080
1081uint8_t VTF::getThumbnailWidth() const {
1082 return this->thumbnailWidth;
1083}
1084
1086 return this->thumbnailHeight;
1087}
1088
1089const std::vector<Resource>& VTF::getResources() const {
1090 return this->resources;
1091}
1092
1094 for (const auto& resource : this->resources) {
1095 if (resource.type == type) {
1096 return &resource;
1097 }
1098 }
1099 return nullptr;
1100}
1101
1103 for (auto& resource : this->resources) {
1104 if (resource.type == type) {
1105 return &resource;
1106 }
1107 }
1108 return nullptr;
1109}
1110
1111void VTF::setResourceInternal(Resource::Type type, std::span<const std::byte> data_) {
1112 if (const auto* resource = this->getResource(type); resource && resource->data.size() == data_.size()) {
1113 std::memcpy(resource->data.data(), data_.data(), data_.size());
1114 return;
1115 }
1116
1117 // Store resource data
1118 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1119 for (const auto& [type_, flags_, dataSpan] : this->resources) {
1120 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1121 }
1122
1123 // Set new resource
1124 if (data_.empty()) {
1125 resourceData.erase(type);
1126 } else {
1127 resourceData[type] = {{data_.begin(), data_.end()}, 0};
1128 }
1129
1130 // Save the data
1131 this->data.clear();
1132 BufferStream writer{this->data};
1133
1134 for (auto resourceType : Resource::getOrder()) {
1135 if (!resourceData.contains(resourceType)) {
1136 continue;
1137 }
1138 auto& [specificResourceData, offset] = resourceData[resourceType];
1139 if (resourceType == type) {
1140 Resource newResource{
1141 type,
1142 specificResourceData.size() <= sizeof(uint32_t) ? Resource::FLAG_LOCAL_DATA : Resource::FLAG_NONE,
1143 {this->data.data() + offset, specificResourceData.size()},
1144 };
1145 if (auto* resourcePtr = this->getResourceInternal(type)) {
1146 *resourcePtr = newResource;
1147 } else {
1148 this->resources.push_back(newResource);
1149 }
1150 } else if (!resourceData.contains(resourceType)) {
1151 continue;
1152 }
1153 offset = writer.tell();
1154 writer.write(specificResourceData);
1155 }
1156 this->data.resize(writer.size());
1157
1158 for (auto& [type_, flags_, dataSpan] : this->resources) {
1159 if (resourceData.contains(type_)) {
1160 const auto& [specificResourceData, offset] = resourceData[type_];
1161 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1162 }
1163 }
1164}
1165
1167 std::erase_if(this->resources, [type](const Resource& resource) { return resource.type == type; });
1168}
1169
1170void VTF::regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newSliceCount, ImageConversion::ResizeFilter filter) {
1171 if (!newWidth) { newWidth = 1; }
1172 if (!newHeight) { newHeight = 1; }
1173 if (!newMipCount) { newMipCount = 1; }
1174 if (!newFrameCount) { newFrameCount = 1; }
1175 if (!newFaceCount) { newFaceCount = 1; }
1176 if (!newSliceCount) { newSliceCount = 1; }
1177
1178 const auto faceCount = this->getFaceCount();
1179 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->sliceCount == newSliceCount) {
1180 return;
1181 }
1182
1183 std::vector<std::byte> newImageData;
1184 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1185 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->sliceCount == newSliceCount) {
1186 newImageData = ImageConversion::convertSeveralImageDataToFormat(imageResource->data, this->format, newFormat, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->sliceCount);
1187 } else {
1188 newImageData.resize(ImageFormatDetails::getDataLength(this->format, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newSliceCount));
1189 for (int i = newMipCount - 1; i >= 0; i--) {
1190 for (int j = 0; j < newFrameCount; j++) {
1191 for (int k = 0; k < newFaceCount; k++) {
1192 for (int l = 0; l < newSliceCount; l++) {
1193 if (i < this->mipCount && j < this->frameCount && k < faceCount && l < this->sliceCount) {
1194 auto imageSpan = this->getImageDataRaw(i, j, k, l);
1195 std::vector<std::byte> image{imageSpan.begin(), imageSpan.end()};
1196 if (this->width != newWidth || this->height != newHeight) {
1197 image = ImageConversion::resizeImageData(image, this->format, ImageDimensions::getMipDim(i, this->width), ImageDimensions::getMipDim(i, newWidth), ImageDimensions::getMipDim(i, this->height), ImageDimensions::getMipDim(i, newHeight), this->isSRGB(), filter);
1198 }
1199 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newSliceCount) && image.size() == length) {
1200 std::memcpy(newImageData.data() + offset, image.data(), length);
1201 }
1202 }
1203 }
1204 }
1205 }
1206 }
1207 if (this->format != newFormat) {
1208 newImageData = ImageConversion::convertSeveralImageDataToFormat(newImageData, this->format, newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newSliceCount);
1209 }
1210 }
1211 } else {
1212 newImageData.resize(ImageFormatDetails::getDataLength(newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newSliceCount));
1213 }
1214
1215 this->format = newFormat;
1216 this->width = newWidth;
1217 this->height = newHeight;
1218 this->mipCount = newMipCount;
1219 this->frameCount = newFrameCount;
1220 if (newFaceCount > 1) {
1221 this->flags |= FLAG_ENVMAP;
1222 } else {
1223 this->flags &= ~FLAG_ENVMAP;
1224 }
1225 this->sliceCount = newSliceCount;
1226
1228}
1229
1230std::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 {
1231 spriteWidth = 0;
1232 spriteHeight = 0;
1233
1234 auto shtResource = this->getResource(Resource::TYPE_PARTICLE_SHEET_DATA);
1235 if (!shtResource) {
1236 return {};
1237 }
1238
1239 auto sht = shtResource->getDataAsParticleSheet();
1240 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1241 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1242 return {};
1243 }
1244
1245 // These values are likely slightly too large thanks to float magic, use a
1246 // shader that scales UVs instead of this function if precision is a concern
1247 // This will also break if any of the bounds are above 1 or below 0, but that
1248 // hasn't been observed in official textures
1249 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1250 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 * static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1251 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 * static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1252 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 * static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1253 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 * static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1254
1255 if (x1 > x2) [[unlikely]] {
1256 std::swap(x1, x2);
1257 }
1258 if (y1 > y2) [[unlikely]] {
1259 std::swap(y1, y2);
1260 }
1261 spriteWidth = x2 - x1;
1262 spriteWidth = y2 - y1;
1263
1264 const auto out = ImageConversion::cropImageData(this->getImageDataRaw(mip, frame, face, slice), this->getFormat(), this->getWidth(mip), spriteWidth, x1, this->getHeight(mip), spriteHeight, y1);
1265 if (out.empty()) {
1266 spriteWidth = 0;
1267 spriteHeight = 0;
1268 }
1269 return out;
1270}
1271
1272std::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 {
1273 return ImageConversion::convertImageDataToFormat(this->getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->getFormat(), newFormat, spriteWidth, spriteHeight);
1274}
1275
1276std::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 {
1277 return this->getParticleSheetFrameDataAs(ImageFormat::RGBA8888, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice);
1278}
1279
1281 std::vector<std::byte> particleSheetData;
1282 BufferStream writer{particleSheetData};
1283
1284 const auto bakedSheet = value.bake();
1285 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1286 particleSheetData.resize(writer.size());
1287
1289}
1290
1293}
1294
1295void VTF::setCRCResource(uint32_t value) {
1296 this->setResourceInternal(Resource::TYPE_CRC, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1297}
1298
1301}
1302
1303void VTF::setLODResource(uint8_t u, uint8_t v, uint8_t u360, uint8_t v360) {
1304 uint32_t lodData;
1305 BufferStream writer{&lodData, sizeof(lodData)};
1306
1307 writer << u << v << u360 << v360;
1308
1309 this->setResourceInternal(Resource::TYPE_LOD_CONTROL_INFO, {reinterpret_cast<const std::byte*>(&lodData), sizeof(lodData)});
1310}
1311
1314}
1315
1316void VTF::setExtendedFlagsResource(uint32_t value) {
1317 this->setResourceInternal(Resource::TYPE_EXTENDED_FLAGS, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1318}
1319
1322}
1323
1324void VTF::setKeyValuesDataResource(const std::string& value) {
1325 std::vector<std::byte> keyValuesData;
1326 BufferStream writer{keyValuesData};
1327
1328 writer.write<uint32_t>(value.size()).write(value, false);
1329 keyValuesData.resize(writer.size());
1330
1332}
1333
1336}
1337
1339 std::vector<std::byte> hotspotData;
1340 BufferStream writer{hotspotData};
1341
1342 const auto bakedHotspotData = value.bake();
1343 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1344 hotspotData.resize(writer.size());
1345
1347}
1348
1351}
1352
1354 return this->compressionLevel;
1355}
1356
1357void VTF::setCompressionLevel(int16_t newCompressionLevel) {
1358 this->compressionLevel = newCompressionLevel;
1359}
1360
1362 return this->compressionMethod;
1363}
1364
1366 if (newCompressionMethod == CompressionMethod::CONSOLE_LZMA && this->platform == VTF::PLATFORM_PC) {
1368 } else if (newCompressionMethod != CompressionMethod::CONSOLE_LZMA && this->platform != VTF::PLATFORM_PC) {
1370 } else {
1371 this->compressionMethod = newCompressionMethod;
1372 }
1373}
1374
1375bool VTF::hasImageData() const {
1376 return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0;
1377}
1378
1379std::span<const std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1380 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1381 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->sliceCount)) {
1382 return imageResource->data.subspan(offset, length);
1383 }
1384 }
1385 return {};
1386}
1387
1388std::vector<std::byte> VTF::getImageDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1389 const auto rawImageData = this->getImageDataRaw(mip, frame, face, slice);
1390 if (rawImageData.empty()) {
1391 return {};
1392 }
1393 return ImageConversion::convertImageDataToFormat(rawImageData, this->format, newFormat, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height));
1394}
1395
1396std::vector<std::byte> VTF::getImageDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1397 return this->getImageDataAs(ImageFormat::RGBA8888, mip, frame, face, slice);
1398}
1399
1400bool 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) {
1401 if (imageData_.empty()) {
1402 return false;
1403 }
1404
1405 if (!this->hasImageData()) {
1406 uint16_t resizedWidth = width_, resizedHeight = height_;
1407 ImageConversion::setResizedDims(resizedWidth, this->imageWidthResizeMethod, resizedHeight, this->imageHeightResizeMethod);
1408 if (ImageFormatDetails::compressed(format_)) {
1409 resizedWidth += math::paddingForAlignment(4, resizedWidth);
1410 resizedHeight += math::paddingForAlignment(4, resizedHeight);
1411 }
1412 if (const auto newMipCount = ImageDimensions::getRecommendedMipCountForDims(format_, resizedWidth, resizedHeight); newMipCount <= mip) {
1413 mip = newMipCount - 1;
1414 }
1415 if (face > 6 || (face == 6 && (this->version < 1 || this->version > 4))) {
1416 return false;
1417 }
1418 this->regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 5 ? 5 : face) : 0, slice + 1);
1419 }
1420
1421 const auto faceCount = this->getFaceCount();
1422 if (this->mipCount <= mip || this->frameCount <= frame || faceCount <= face || this->sliceCount <= slice) {
1423 return false;
1424 }
1425
1426 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
1427 if (!imageResource) {
1428 return false;
1429 }
1430 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->sliceCount)) {
1431 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
1432 const auto newWidth = ImageDimensions::getMipDim(mip, this->width);
1433 const auto newHeight = ImageDimensions::getMipDim(mip, this->height);
1434 if (width_ != newWidth || height_ != newHeight) {
1435 image = ImageConversion::resizeImageData(image, format_, width_, newWidth, height_, newHeight, this->isSRGB(), filter);
1436 }
1437 if (format_ != this->format) {
1438 image = ImageConversion::convertImageDataToFormat(image, format_, this->format, newWidth, newHeight);
1439 }
1440 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
1441 }
1442 return true;
1443}
1444
1445bool VTF::setImage(const std::string& imagePath, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) {
1446 ImageFormat inputFormat;
1447 int inputWidth, inputHeight, inputFrameCount;
1448 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
1449
1450 // Unable to decode file
1451 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
1452 return false;
1453 }
1454
1455 // One frame (normal)
1456 if (inputFrameCount == 1) {
1457 return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice);
1458 }
1459
1460 // Multiple frames (GIF)
1461 bool allSuccess = true;
1462 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
1463 for (int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
1464 if (!this->setImage({imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize}, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice)) {
1465 allSuccess = false;
1466 }
1467 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
1468 // Call this after setting the first image, this function is a no-op if no image data is present yet
1469 this->setFrameCount(frame + inputFrameCount);
1470 }
1471 }
1472 return allSuccess;
1473}
1474
1475std::vector<std::byte> VTF::saveImageToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
1476 return ImageConversion::convertImageDataToFile(this->getImageDataRaw(mip, frame, face, slice), this->format, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height), fileFormat);
1477}
1478
1479bool VTF::saveImageToFile(const std::string& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
1480 if (auto data_ = this->saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
1481 return fs::writeFileBuffer(imagePath, data_);
1482 }
1483 return false;
1484}
1485
1487 return this->thumbnailFormat != ImageFormat::EMPTY && this->thumbnailWidth > 0 && this->thumbnailHeight > 0;
1488}
1489
1490std::span<const std::byte> VTF::getThumbnailDataRaw() const {
1491 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
1492 return thumbnailResource->data;
1493 }
1494 return {};
1495}
1496
1497std::vector<std::byte> VTF::getThumbnailDataAs(ImageFormat newFormat) const {
1498 const auto rawThumbnailData = this->getThumbnailDataRaw();
1499 if (rawThumbnailData.empty()) {
1500 return {};
1501 }
1502 return ImageConversion::convertImageDataToFormat(rawThumbnailData, this->thumbnailFormat, newFormat, this->thumbnailWidth, this->thumbnailHeight);
1503}
1504
1505std::vector<std::byte> VTF::getThumbnailDataAsRGBA8888() const {
1507}
1508
1509void VTF::setThumbnail(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_) {
1510 if (format_ != this->thumbnailFormat) {
1512 } else {
1514 }
1515 this->thumbnailWidth = width_;
1516 this->thumbnailHeight = height_;
1517}
1518
1520 if (!this->hasImageData()) {
1521 return;
1522 }
1524 this->thumbnailWidth = 16;
1525 this->thumbnailHeight = 16;
1526 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));
1527}
1528
1531 this->thumbnailWidth = 0;
1532 this->thumbnailHeight = 0;
1534}
1535
1536std::vector<std::byte> VTF::saveThumbnailToFile(ImageConversion::FileFormat fileFormat) const {
1537 return ImageConversion::convertImageDataToFile(this->getThumbnailDataRaw(), this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, fileFormat);
1538}
1539
1540bool VTF::saveThumbnailToFile(const std::string& imagePath, ImageConversion::FileFormat fileFormat) const {
1541 if (auto data_ = this->saveThumbnailToFile(fileFormat); !data_.empty()) {
1542 return fs::writeFileBuffer(imagePath, data_);
1543 }
1544 return false;
1545}
1546
1547std::vector<std::byte> VTF::bake() const {
1548 std::vector<std::byte> out;
1549 BufferStream writer{out};
1550
1551 static constexpr auto writeNonLocalResource = [](BufferStream& writer_, Resource::Type type, std::span<const std::byte> data, VTF::Platform platform) {
1552 if (platform != VTF::PLATFORM_PC) {
1553 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(&type));
1554 }
1555 writer_.write<uint32_t>(type);
1556 const auto resourceOffsetPos = writer_.tell();
1557 writer_.seek(0, std::ios::end);
1558 const auto resourceOffsetValue = writer_.tell();
1559 writer_.write(data);
1560 writer_.seek_u(resourceOffsetPos).write<uint32_t>(resourceOffsetValue);
1561 };
1562
1563 if (this->platform != PLATFORM_PC) {
1564 writer << (this->platform == PLATFORM_PS3_PORTAL2 ? VTF3_SIGNATURE : VTFX_SIGNATURE);
1565 writer.set_big_endian(true);
1566
1567 if (this->platform == PLATFORM_PS3_PORTAL2) {
1568 writer << PLATFORM_PS3_ORANGEBOX;
1569 } else {
1570 writer << this->platform;
1571 }
1572 writer.write<uint32_t>(8);
1573 const auto headerLengthPos = writer.tell();
1574 writer
1575 .write<uint32_t>(0)
1576 .write(this->flags)
1577 .write(this->width)
1578 .write(this->height)
1579 .write(this->sliceCount)
1580 .write(this->frameCount);
1581 const auto preloadPos = writer.tell();
1582 writer
1583 .write<uint16_t>(0) // preload size
1584 .write<uint8_t>(0) // skip higher mips
1585 .write<uint8_t>(this->resources.size())
1586 .write(this->reflectivity[0])
1587 .write(this->reflectivity[1])
1588 .write(this->reflectivity[2])
1589 .write(this->bumpMapScale)
1590 .write(this->format)
1591 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[0] * 255)), 0, 255))
1592 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[1] * 255)), 0, 255))
1593 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[2] * 255)), 0, 255))
1594 .write<uint8_t>(255);
1595 const auto compressionPos = writer.tell();
1596 writer.write<uint32_t>(0); // compressed length
1597
1598 if (this->platform == PLATFORM_PS3_PORTAL2) {
1599 // 16 byte aligned
1600 writer.write<uint32_t>(0);
1601 }
1602
1603 std::vector<std::byte> imageResourceData;
1604 bool hasCompression = false;
1605 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1606 imageResourceData.assign(imageResource->data.begin(), imageResource->data.end());
1607 ::swapImageDataEndianForConsole(imageResourceData, this->format, this->width, this->height, this->platform);
1608
1609 // Compression has only been observed in X360 and PS3_PORTAL2 VTFs so far
1610 // todo(vtfpp): check PS3_ORANGEBOX cubemaps for compression
1611 if ((this->platform == VTF::PLATFORM_X360 || this->platform == PLATFORM_PS3_PORTAL2) && (hasCompression = this->compressionMethod == CompressionMethod::CONSOLE_LZMA)) {
1612 auto fixedCompressionLevel = this->compressionLevel;
1613 if (this->compressionLevel == 0) {
1614 // Compression level defaults to 0, so it works differently on console.
1615 // Rather than not compress on 0, 0 will be replaced with the default
1616 // compression level (6) if the compression method is LZMA.
1617 fixedCompressionLevel = 6;
1618 }
1619 auto compressedData = compression::compressValveLZMA(imageResourceData, fixedCompressionLevel);
1620 if (compressedData) {
1621 imageResourceData.assign(compressedData->begin(), compressedData->end());
1622 } else {
1623 hasCompression = false;
1624 }
1625 }
1626 }
1627
1628 const auto resourceStart = writer.tell();
1629 const auto headerSize = resourceStart + (this->getResources().size() * sizeof(uint64_t));
1630 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
1631 while (writer.tell() < headerSize) {
1632 writer.write<uint64_t>(0);
1633 }
1634 writer.seek_u(resourceStart);
1635
1636 for (const auto resourceType : Resource::getOrder()) {
1637 if (resourceType == Resource::TYPE_IMAGE_DATA) {
1638 auto curPos = writer.tell();
1639 const auto imagePos = writer.seek(0, std::ios::end).tell();
1640 writer.seek_u(preloadPos).write<uint16_t>(imagePos).seek_u(curPos);
1641
1642 writeNonLocalResource(writer, resourceType, imageResourceData, this->platform);
1643
1644 if (hasCompression) {
1645 curPos = writer.tell();
1646 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
1647 }
1648 } else if (const auto* resource = this->getResource(resourceType)) {
1649 std::vector<std::byte> resData{resource->data.begin(), resource->data.end()};
1650
1651 // If tweaking this switch, tweak the one in ctor as well
1652 switch (resource->type) {
1653 default:
1655 case Resource::TYPE_CRC:
1657 case Resource::TYPE_AUX_COMPRESSION: // Strata-specific
1658 break;
1660 ::swapImageDataEndianForConsole(resData, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, this->platform);
1661 break;
1665 if (resData.size() >= sizeof(uint32_t)) {
1666 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resData.data()));
1667 }
1668 break;
1669 }
1670
1671 if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) {
1672 writer.set_big_endian(false);
1673 writer.write<uint32_t>((Resource::FLAG_LOCAL_DATA << 24) | resource->type);
1674 writer.set_big_endian(true);
1675 writer.write(resource->data);
1676 } else {
1677 writeNonLocalResource(writer, resource->type, resource->data, this->platform);
1678 }
1679 }
1680 }
1681
1682 out.resize(writer.size());
1683 return out;
1684 }
1685
1686 writer
1687 .write(VTF_SIGNATURE)
1688 .write<int32_t>(7)
1689 .write(this->version);
1690
1691 const auto headerLengthPos = writer.tell();
1692 writer.write<uint32_t>(0);
1693
1694 writer
1695 .write(this->width)
1696 .write(this->height)
1697 .write(this->flags)
1698 .write(this->frameCount)
1699 .write(this->startFrame)
1700 .write<uint32_t>(0) // padding
1701 .write(this->reflectivity)
1702 .write<uint32_t>(0) // padding
1703 .write(this->bumpMapScale)
1704 .write(this->format)
1705 .write(this->mipCount)
1706 .write(ImageFormat::DXT1)
1707 .write(this->thumbnailWidth)
1708 .write(this->thumbnailHeight);
1709
1710 if (this->version >= 2) {
1711 writer << this->sliceCount;
1712 }
1713
1714 if (this->version < 3) {
1715 const auto headerAlignment = math::paddingForAlignment(16, writer.tell());
1716 for (uint16_t i = 0; i < headerAlignment; i++) {
1717 writer.write<std::byte>({});
1718 }
1719 const auto headerSize = writer.tell();
1720 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
1721
1722 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
1723 writer.write(thumbnailResource->data);
1724 }
1725 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1726 writer.write(imageResource->data);
1727 }
1728 } else {
1729 std::vector<std::byte> auxCompressionResourceData;
1730 std::vector<std::byte> compressedImageResourceData;
1731 bool hasAuxCompression = false;
1732 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1733 hasAuxCompression = this->version >= 6 && this->compressionLevel != 0;
1734 if (hasAuxCompression) {
1735 const auto faceCount = this->getFaceCount();
1736 auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) * sizeof(uint32_t));
1737 BufferStream auxWriter{auxCompressionResourceData, false};
1738
1739 // Format of aux resource is as follows, with each item of unspecified type being a 4 byte integer:
1740 // - Size of resource in bytes, not counting this int
1741 // - Compression level, method (2 byte integers)
1742 // - (X times) Size of each mip-face-frame combo
1743 auxWriter
1744 .write<uint32_t>(auxCompressionResourceData.size() - sizeof(uint32_t))
1745 .write(this->compressionLevel)
1746 .write(this->compressionMethod);
1747
1748 for (int i = this->mipCount - 1; i >= 0; i--) {
1749 for (int j = 0; j < this->frameCount; j++) {
1750 for (int k = 0; k < faceCount; k++) {
1751 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->sliceCount)) {
1752 auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->sliceCount}, this->compressionLevel, this->compressionMethod);
1753 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
1754 auxWriter.write<uint32_t>(compressedData.size());
1755 }
1756 }
1757 }
1758 }
1759 }
1760 }
1761
1762 writer
1763 .write<uint8_t>(0) // padding
1764 .write<uint8_t>(0) // padding
1765 .write<uint8_t>(0) // padding
1766 .write<uint32_t>(this->getResources().size() + hasAuxCompression)
1767 .write<uint64_t>(0); // padding
1768
1769 const auto resourceStart = writer.tell();
1770 const auto headerSize = resourceStart + ((this->getResources().size() + hasAuxCompression) * sizeof(uint64_t));
1771 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
1772 while (writer.tell() < headerSize) {
1773 writer.write<uint64_t>(0);
1774 }
1775 writer.seek_u(resourceStart);
1776
1777 for (const auto resourceType : Resource::getOrder()) {
1778 if (hasAuxCompression && resourceType == Resource::TYPE_AUX_COMPRESSION) {
1779 writeNonLocalResource(writer, resourceType, auxCompressionResourceData, this->platform);
1780 } else if (hasAuxCompression && resourceType == Resource::TYPE_IMAGE_DATA) {
1781 writeNonLocalResource(writer, resourceType, compressedImageResourceData, this->platform);
1782 } else if (const auto* resource = this->getResource(resourceType)) {
1783 if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) {
1784 writer.write<uint32_t>((Resource::FLAG_LOCAL_DATA << 24) | resource->type);
1785 writer.write(resource->data);
1786 } else {
1787 writeNonLocalResource(writer, resource->type, resource->data, this->platform);
1788 }
1789 }
1790 }
1791 }
1792
1793 out.resize(writer.size());
1794 return out;
1795}
1796
1797bool VTF::bake(const std::string& vtfPath) const {
1798 return fs::writeFileBuffer(vtfPath, this->bake());
1799}
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
Definition: Macros.h:19
Definition: HOT.h:13
std::vector< std::byte > bake() const
Definition: HOT.cpp:61
Definition: SHT.h:13
std::vector< std::byte > bake() const
Definition: SHT.cpp:84
static constexpr uint32_t FLAGS_MASK_V5
Definition: VTF.h:203
uint16_t width
Definition: VTF.h:471
static constexpr uint32_t FLAGS_MASK_V2
Definition: VTF.h:173
Platform
Definition: VTF.h:216
@ PLATFORM_PC
Definition: VTF.h:218
@ PLATFORM_X360
Definition: VTF.h:219
@ PLATFORM_PS3_PORTAL2
Definition: VTF.h:221
@ PLATFORM_PS3_ORANGEBOX
Definition: VTF.h:220
@ PLATFORM_UNKNOWN
Definition: VTF.h:217
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition: VTF.cpp:704
void removeKeyValuesDataResource()
Definition: VTF.cpp:1334
CompressionMethod compressionMethod
Definition: VTF.h:502
void computeReflectivity()
Definition: VTF.cpp:1005
uint8_t getThumbnailWidth() const
Definition: VTF.cpp:1081
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition: VTF.cpp:1536
ImageFormat getFormat() const
Definition: VTF.cpp:817
void setPlatform(Platform newPlatform)
Definition: VTF.cpp:633
uint16_t getHeight(uint8_t mip=0) const
Definition: VTF.cpp:712
VTF & operator=(const VTF &other)
Definition: VTF.cpp:478
int16_t compressionLevel
Definition: VTF.h:501
sourcepp::math::Vec3f reflectivity
Definition: VTF.h:479
uint16_t getWidth(uint8_t mip=0) const
Definition: VTF.cpp:708
uint16_t startFrame
Definition: VTF.h:476
static constexpr uint32_t FLAGS_MASK_INTERNAL
Definition: VTF.h:214
uint8_t mipCount
Definition: VTF.h:484
bool setRecommendedMipCount()
Definition: VTF.cpp:866
static bool createInternal(VTF &writer, CreationOptions options)
Definition: VTF.cpp:517
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:1276
@ FLAG_NO_LOD
Definition: VTF.h:156
@ FLAG_MULTI_BIT_ALPHA
Definition: VTF.h:160
@ FLAG_ONE_BIT_ALPHA
Definition: VTF.h:159
@ FLAG_ENVMAP
Definition: VTF.h:161
@ FLAG_NO_MIP
Definition: VTF.h:155
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:873
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
Definition: VTF.cpp:700
uint8_t thumbnailWidth
Definition: VTF.h:487
void setCompressionLevel(int16_t newCompressionLevel)
Definition: VTF.cpp:1357
ImageConversion::ResizeMethod imageHeightResizeMethod
Definition: VTF.h:504
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition: VTF.cpp:695
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:1272
static constexpr uint32_t FLAGS_MASK_V3
Definition: VTF.h:181
float getBumpMapScale() const
Definition: VTF.cpp:1069
void computeTransparencyFlags()
Definition: VTF.cpp:787
static constexpr uint32_t FLAGS_MASK_V4_TF2
Definition: VTF.h:195
std::vector< std::byte > data
Definition: VTF.h:465
bool setFrameFaceAndSliceCount(uint16_t newFrameCount, bool isCubeMap, uint16_t newSliceCount=1)
Definition: VTF.cpp:981
ImageFormat format
Definition: VTF.h:483
uint16_t sliceCount
Definition: VTF.h:491
Platform getPlatform() const
Definition: VTF.cpp:629
void addFlags(uint32_t flags_)
Definition: VTF.cpp:759
uint8_t getFaceCount() const
Definition: VTF.cpp:935
ImageFormat thumbnailFormat
Definition: VTF.h:486
void setFlags(uint32_t flags_)
Definition: VTF.cpp:755
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
Definition: VTF.cpp:716
void removeLODResource()
Definition: VTF.cpp:1312
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
Definition: VTF.cpp:691
void setBumpMapScale(float newBumpMapScale)
Definition: VTF.cpp:1073
ImageFormat getThumbnailFormat() const
Definition: VTF.cpp:1077
uint16_t getStartFrame() const
Definition: VTF.cpp:989
void setThumbnail(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_)
Definition: VTF.cpp:1509
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
Definition: VTF.h:251
bool hasThumbnailData() const
Definition: VTF.cpp:1486
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
Definition: VTF.cpp:1001
void setSRGB(bool srgb)
Definition: VTF.cpp:771
const std::vector< Resource > & getResources() const
Definition: VTF.cpp:1089
void removeHotspotDataResource()
Definition: VTF.cpp:1349
@ FLAG_V4_SRGB
Definition: VTF.h:184
void setVersion(uint32_t newVersion)
Definition: VTF.cpp:654
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:1475
Resource * getResourceInternal(Resource::Type type)
Definition: VTF.cpp:1102
std::vector< std::byte > bake() const
Definition: VTF.cpp:1547
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)
Definition: VTF.cpp:1400
bool hasImageData() const
Definition: VTF.cpp:1375
ImageConversion::ResizeMethod imageWidthResizeMethod
Definition: VTF.h:503
uint16_t getSliceCount() const
Definition: VTF.cpp:969
void removeParticleSheetResource()
Definition: VTF.cpp:1291
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:1396
uint16_t getFrameCount() const
Definition: VTF.cpp:923
uint8_t getMipCount() const
Definition: VTF.cpp:843
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
Definition: VTF.cpp:1303
void removeResourceInternal(Resource::Type type)
Definition: VTF.cpp:1166
bool setFrameCount(uint16_t newFrameCount)
Definition: VTF.cpp:927
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
Definition: VTF.cpp:687
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:1388
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:821
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
Definition: VTF.cpp:802
void setStartFrame(uint16_t newStartFrame)
Definition: VTF.cpp:993
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
Definition: VTF.cpp:1497
static constexpr uint32_t FLAGS_MASK_V5_CSGO
Definition: VTF.h:212
@ FLAG_V5_SRGB
Definition: VTF.h:199
@ FLAG_V5_PWL_CORRECTED
Definition: VTF.h:198
bool setSliceCount(uint16_t newSliceCount)
Definition: VTF.cpp:973
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:1379
sourcepp::math::Vec3f getReflectivity() const
Definition: VTF.cpp:997
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
Definition: VTF.cpp:1505
bool isSRGB() const
Definition: VTF.cpp:767
uint32_t flags
Definition: VTF.h:473
void removeThumbnail()
Definition: VTF.cpp:1529
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
Definition: VTF.h:248
uint32_t version
Definition: VTF.h:468
std::span< const std::byte > getThumbnailDataRaw() const
Definition: VTF.cpp:1490
bool setMipCount(uint8_t newMipCount)
Definition: VTF.cpp:847
void setExtendedFlagsResource(uint32_t value)
Definition: VTF.cpp:1316
CompressionMethod getCompressionMethod() const
Definition: VTF.cpp:1361
void setCRCResource(uint32_t value)
Definition: VTF.cpp:1295
void setCompressionMethod(CompressionMethod newCompressionMethod)
Definition: VTF.cpp:1365
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:1519
bool setFaceCount(bool isCubeMap)
Definition: VTF.cpp:961
uint8_t getThumbnailHeight() const
Definition: VTF.cpp:1085
const Resource * getResource(Resource::Type type) const
Definition: VTF.cpp:1093
static constexpr uint32_t FLAGS_MASK_V4
Definition: VTF.h:186
uint32_t getFlags() const
Definition: VTF.cpp:751
void setParticleSheetResource(const SHT &value)
Definition: VTF.cpp:1280
void setKeyValuesDataResource(const std::string &value)
Definition: VTF.cpp:1324
uint16_t height
Definition: VTF.h:472
void removeCRCResource()
Definition: VTF.cpp:1299
uint16_t frameCount
Definition: VTF.h:475
uint32_t getVersion() const
Definition: VTF.cpp:650
static bool create(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string &vtfPath, CreationOptions options)
Definition: VTF.cpp:569
int16_t getCompressionLevel() const
Definition: VTF.cpp:1353
void setHotspotDataResource(const HOT &value)
Definition: VTF.cpp:1338
void removeFlags(uint32_t flags_)
Definition: VTF.cpp:763
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
Definition: VTF.cpp:1111
std::vector< Resource > resources
Definition: VTF.h:496
uint8_t thumbnailHeight
Definition: VTF.h:488
Platform platform
Definition: VTF.h:500
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:1230
void removeExtendedFlagsResource()
Definition: VTF.cpp:1320
void regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newSliceCount, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:1170
bool opened
Definition: VTF.h:463
float bumpMapScale
Definition: VTF.h:482
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
std::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
Definition: FS.cpp:9
bool writeFileBuffer(const std::string &filepath, std::span< const std::byte > buffer)
Definition: FS.cpp:27
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
Definition: Math.h:57
Definition: LZMA.h:11
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 > 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 > 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 sliceCount)
Converts several images from one format to another.
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 > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height)
Converts an image from one format to another.
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 uint32_t getMipDim(uint8_t mip, uint16_t dim)
Definition: ImageFormats.h:650
constexpr uint8_t getActualMipCountForDimsOnConsole(uint16_t width, uint16_t height)
Definition: ImageFormats.h:685
constexpr uint8_t getRecommendedMipCountForDims(ImageFormat format, uint16_t width, uint16_t height)
Definition: ImageFormats.h:657
constexpr bool large(ImageFormat format)
Definition: ImageFormats.h:571
constexpr uint32_t getDataLength(ImageFormat format, uint16_t width, uint16_t height, uint16_t sliceCount=1)
Definition: ImageFormats.h:711
constexpr bool transparent(ImageFormat format)
Definition: ImageFormats.h:583
constexpr int8_t decompressedAlpha(ImageFormat format)
Definition: ImageFormats.h:414
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 sliceCount=1)
Definition: ImageFormats.h:737
constexpr uint8_t bpp(ImageFormat format)
Definition: ImageFormats.h:436
constexpr bool compressed(ImageFormat format)
Definition: ImageFormats.h:579
Definition: HOT.h:11
constexpr uint32_t VTF_SIGNATURE
Definition: VTF.h:21
constexpr uint32_t VTFX_SIGNATURE
Definition: VTF.h:22
constexpr uint32_t VTF3_SIGNATURE
Definition: VTF.h:23
CompressionMethod
Definition: VTF.h:25
ImageFormat
Definition: ImageFormats.h:7
static consteval std::array< Type, 9 > getOrder()
Definition: VTF.h:47
Type type
Definition: VTF.h:66
ConvertedData convertData() const
Definition: VTF.cpp:116
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT, std::span< uint32_t > > ConvertedData
Definition: VTF.h:78
@ FLAG_LOCAL_DATA
Definition: VTF.h:63
@ FLAG_NONE
Definition: VTF.h:62
@ TYPE_KEYVALUES_DATA
Definition: VTF.h:45
@ TYPE_CRC
Definition: VTF.h:42
@ TYPE_PARTICLE_SHEET_DATA
Definition: VTF.h:38
@ TYPE_EXTENDED_FLAGS
Definition: VTF.h:41
@ TYPE_UNKNOWN
Definition: VTF.h:36
@ TYPE_IMAGE_DATA
Definition: VTF.h:40
@ TYPE_AUX_COMPRESSION
Definition: VTF.h:43
@ TYPE_LOD_CONTROL_INFO
Definition: VTF.h:44
@ TYPE_THUMBNAIL_DATA
Definition: VTF.h:37
@ TYPE_HOTSPOT_DATA
Definition: VTF.h:39
Flags flags
Definition: VTF.h:67
std::span< std::byte > data
Definition: VTF.h:68
uint16_t initialFrameCount
Definition: VTF.h:231
uint16_t initialSliceCount
Definition: VTF.h:234
int16_t compressionLevel
Definition: VTF.h:240
ImageConversion::ResizeFilter filter
Definition: VTF.h:229
ImageConversion::ResizeMethod heightResizeMethod
Definition: VTF.h:228
CompressionMethod compressionMethod
Definition: VTF.h:241
ImageConversion::ResizeMethod widthResizeMethod
Definition: VTF.h:227
ImageFormat outputFormat
Definition: VTF.h:226