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