From 2b8bbbea9331c06b6a0f995c2a84278391afe9e8 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Sun, 26 Jan 2025 18:13:07 -0500 Subject: [PATCH 1/7] sourcepp: fix valve lzma compression function --- src/sourcepp/compression/LZMA.cpp | 74 +++++++++++++------------------ 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/src/sourcepp/compression/LZMA.cpp b/src/sourcepp/compression/LZMA.cpp index 401a0e1a0..a146ac61b 100644 --- a/src/sourcepp/compression/LZMA.cpp +++ b/src/sourcepp/compression/LZMA.cpp @@ -6,54 +6,45 @@ using namespace sourcepp; std::optional> compression::compressValveLZMA(std::span data, uint8_t compressLevel) { - // Preallocate extra 4 bytes for Valve LZMA header signature - std::vector compressedData(sizeof(uint32_t)); - std::array compressedChunk{}; - - lzma_stream stream{ - .next_in = reinterpret_cast(data.data()), - .avail_in = data.size(), - .next_out = reinterpret_cast(compressedChunk.data()), - .avail_out = compressedChunk.size(), - }; + // Shift over 4 bytes for Valve LZMA header signature + std::vector compressedData(sizeof(uint32_t) + data.size() * 2); lzma_options_lzma options{}; - lzma_lzma_preset(&options, std::clamp(compressLevel, 0, 9)); + if (lzma_lzma_preset(&options, std::clamp(compressLevel, 0, 9))) { + return std::nullopt; + } + + lzma_stream stream = LZMA_STREAM_INIT; if (lzma_alone_encoder(&stream, &options) != LZMA_OK) { lzma_end(&stream); return std::nullopt; } - lzma_ret ret; - do { - stream.next_out = reinterpret_cast(compressedChunk.data()); - stream.avail_out = compressedChunk.size(); - - ret = lzma_code(&stream, LZMA_RUN); - compressedData.insert(compressedData.end(), compressedChunk.begin(), compressedChunk.begin() + compressedChunk.size() - static_cast(stream.avail_out)); - } while (ret == LZMA_OK); + stream.next_in = reinterpret_cast(data.data()); + stream.avail_in = data.size(); + stream.next_out = reinterpret_cast(compressedData.data() + sizeof(uint32_t)); + stream.avail_out = compressedData.size() - sizeof(uint32_t); - ret = lzma_code(&stream, LZMA_FINISH); - if (ret != LZMA_OK && ret != LZMA_STREAM_END) { - lzma_end(&stream); + lzma_ret ret = lzma_code(&stream, LZMA_FINISH); + lzma_end(&stream); + if (ret != LZMA_STREAM_END) { return std::nullopt; } - lzma_end(&stream); - { - // Switch out normal header with Valve one - BufferStream compressedStream{compressedData}; - compressedStream << VALVE_LZMA_SIGNATURE; - const auto properties = compressedStream.read(); - const auto dictionarySize = compressedStream.read(); - compressedStream - .seek_u(sizeof(uint32_t)) - .write(data.size()) - .write(compressedData.size() - (sizeof(uint32_t) * 3) + (sizeof(uint8_t) * 5)) - .write(properties) - .write(dictionarySize); - } + // Switch out normal header with Valve one + BufferStream compressedStream{compressedData}; + compressedStream.seek(0).write(VALVE_LZMA_SIGNATURE); + const auto properties = compressedStream.read(); + const auto dictionarySize = compressedStream.read(); + compressedStream + .seek_u(sizeof(uint32_t)) + .write(data.size()) + .write(compressedData.size() - (sizeof(uint32_t) * 3) + (sizeof(uint8_t) * 5)) + .write(properties) + .write(dictionarySize); + + compressedData.resize(stream.total_out + sizeof(uint32_t)); return compressedData; } @@ -84,12 +75,11 @@ std::optional> compression::decompressValveLZMA(std::span uncompressedData.resize(uncompressedLength); } - lzma_stream stream{ - .next_in = reinterpret_cast(compressedData.data()), - .avail_in = compressedData.size(), - .next_out = reinterpret_cast(uncompressedData.data()), - .avail_out = uncompressedData.size(), - }; + lzma_stream stream = LZMA_STREAM_INIT; + stream.next_in = reinterpret_cast(compressedData.data()); + stream.avail_in = compressedData.size(); + stream.next_out = reinterpret_cast(uncompressedData.data()); + stream.avail_out = uncompressedData.size(); if (lzma_alone_decoder(&stream, UINT64_MAX) != LZMA_OK) { lzma_end(&stream); From 43dcfb28d8e689c42a06cbe2075e81f4c3a03b3a Mon Sep 17 00:00:00 2001 From: craftablescience Date: Mon, 20 Jan 2025 17:43:30 -0500 Subject: [PATCH 2/7] vtfpp: vtfx support (x360 complete, ps3 mostly complete) --- include/vtfpp/ImageConversion.h | 146 +++++------ include/vtfpp/ImageFormats.h | 121 ++++++++- include/vtfpp/VTF.h | 28 ++- lang/python/src/vtfpp.h | 9 +- src/vtfpp/ImageConversion.cpp | 153 +++++++---- src/vtfpp/VTF.cpp | 433 +++++++++++++++++++++++++++----- src/vtfpp/_vtfpp.cmake | 2 +- 7 files changed, 698 insertions(+), 194 deletions(-) diff --git a/include/vtfpp/ImageConversion.h b/include/vtfpp/ImageConversion.h index 9c1be6f31..e24d36ed7 100644 --- a/include/vtfpp/ImageConversion.h +++ b/include/vtfpp/ImageConversion.h @@ -18,14 +18,19 @@ namespace ImagePixel { #define VTFPP_CHECK_SIZE(format) \ static_assert(sizeof(format) == ImageFormatDetails::bpp(ImageFormat::format) / 8) +#define VTFPP_FORMAT_INHERITED(format, parent) \ + struct format : parent { \ + static constexpr auto FORMAT = ImageFormat::format; \ + }; \ + VTFPP_CHECK_SIZE(format) + struct RGBA8888 { static constexpr auto FORMAT = ImageFormat::RGBA8888; uint8_t r; uint8_t g; uint8_t b; uint8_t a; -}; -VTFPP_CHECK_SIZE(RGBA8888); +}; VTFPP_CHECK_SIZE(RGBA8888); struct ABGR8888 { static constexpr auto FORMAT = ImageFormat::ABGR8888; @@ -33,67 +38,53 @@ struct ABGR8888 { uint8_t b; uint8_t g; uint8_t r; -}; -VTFPP_CHECK_SIZE(ABGR8888); +}; VTFPP_CHECK_SIZE(ABGR8888); struct RGB888 { static constexpr auto FORMAT = ImageFormat::RGB888; uint8_t r; uint8_t g; uint8_t b; -}; -VTFPP_CHECK_SIZE(RGB888); +}; VTFPP_CHECK_SIZE(RGB888); -struct RGB888_BLUESCREEN : RGB888 { - static constexpr auto FORMAT = ImageFormat::RGB888_BLUESCREEN; -}; -VTFPP_CHECK_SIZE(RGB888_BLUESCREEN); +VTFPP_FORMAT_INHERITED(RGB888_BLUESCREEN, RGB888); struct BGR888 { static constexpr auto FORMAT = ImageFormat::BGR888; uint8_t b; uint8_t g; uint8_t r; -}; -VTFPP_CHECK_SIZE(BGR888); +}; VTFPP_CHECK_SIZE(BGR888); -struct BGR888_BLUESCREEN : BGR888 { - static constexpr auto FORMAT = ImageFormat::BGR888_BLUESCREEN; -}; -VTFPP_CHECK_SIZE(BGR888_BLUESCREEN); +VTFPP_FORMAT_INHERITED(BGR888_BLUESCREEN, BGR888); struct RGB565 { static constexpr auto FORMAT = ImageFormat::RGB565; uint16_t r : 5; uint16_t g : 6; uint16_t b : 5; -}; -VTFPP_CHECK_SIZE(RGB565); +}; VTFPP_CHECK_SIZE(RGB565); struct I8 { static constexpr auto FORMAT = ImageFormat::I8; uint8_t i; -}; -VTFPP_CHECK_SIZE(I8); +}; VTFPP_CHECK_SIZE(I8); struct IA88 { static constexpr auto FORMAT = ImageFormat::IA88; uint8_t i; uint8_t a; -}; -VTFPP_CHECK_SIZE(IA88); +}; VTFPP_CHECK_SIZE(IA88); struct P8 { static constexpr auto FORMAT = ImageFormat::P8; uint8_t p; -}; -VTFPP_CHECK_SIZE(P8); +}; VTFPP_CHECK_SIZE(P8); struct A8 { static constexpr auto FORMAT = ImageFormat::A8; uint8_t a; -}; -VTFPP_CHECK_SIZE(A8); +}; VTFPP_CHECK_SIZE(A8); struct ARGB8888 { static constexpr auto FORMAT = ImageFormat::ARGB8888; @@ -101,8 +92,7 @@ struct ARGB8888 { uint8_t r; uint8_t g; uint8_t b; -}; -VTFPP_CHECK_SIZE(ARGB8888); +}; VTFPP_CHECK_SIZE(ARGB8888); struct BGRA8888 { static constexpr auto FORMAT = ImageFormat::BGRA8888; @@ -110,8 +100,7 @@ struct BGRA8888 { uint8_t g; uint8_t r; uint8_t a; -}; -VTFPP_CHECK_SIZE(BGRA8888); +}; VTFPP_CHECK_SIZE(BGRA8888); struct BGRX8888 { static constexpr auto FORMAT = ImageFormat::BGRX8888; @@ -119,16 +108,14 @@ struct BGRX8888 { uint8_t g; uint8_t r; uint8_t x; -}; -VTFPP_CHECK_SIZE(BGRX8888); +}; VTFPP_CHECK_SIZE(BGRX8888); struct BGR565 { static constexpr auto FORMAT = ImageFormat::BGR565; uint16_t b : 5; uint16_t g : 6; uint16_t r : 5; -}; -VTFPP_CHECK_SIZE(BGR565); +}; VTFPP_CHECK_SIZE(BGR565); struct BGRX5551 { static constexpr auto FORMAT = ImageFormat::BGRX5551; @@ -136,8 +123,7 @@ struct BGRX5551 { uint16_t g : 5; uint16_t r : 5; uint16_t x : 1; -}; -VTFPP_CHECK_SIZE(BGRX5551); +}; VTFPP_CHECK_SIZE(BGRX5551); struct BGRA4444 { static constexpr auto FORMAT = ImageFormat::BGRA4444; @@ -145,8 +131,7 @@ struct BGRA4444 { uint16_t g : 4; uint16_t r : 4; uint16_t a : 4; -}; -VTFPP_CHECK_SIZE(BGRA4444); +}; VTFPP_CHECK_SIZE(BGRA4444); struct BGRA5551 { static constexpr auto FORMAT = ImageFormat::BGRA5551; @@ -154,15 +139,13 @@ struct BGRA5551 { uint16_t g : 5; uint16_t r : 5; uint16_t a : 1; -}; -VTFPP_CHECK_SIZE(BGRA5551); +}; VTFPP_CHECK_SIZE(BGRA5551); struct UV88 { static constexpr auto FORMAT = ImageFormat::UV88; uint8_t u; uint8_t v; -}; -VTFPP_CHECK_SIZE(UV88); +}; VTFPP_CHECK_SIZE(UV88); struct UVWQ8888 { static constexpr auto FORMAT = ImageFormat::UVWQ8888; @@ -170,8 +153,7 @@ struct UVWQ8888 { uint8_t v; uint8_t w; uint8_t q; -}; -VTFPP_CHECK_SIZE(UVWQ8888); +}; VTFPP_CHECK_SIZE(UVWQ8888); struct RGBA16161616F { static constexpr auto FORMAT = ImageFormat::RGBA16161616F; @@ -179,8 +161,7 @@ struct RGBA16161616F { half g; half b; half a; -}; -VTFPP_CHECK_SIZE(RGBA16161616F); +}; VTFPP_CHECK_SIZE(RGBA16161616F); struct RGBA16161616 { static constexpr auto FORMAT = ImageFormat::RGBA16161616; @@ -188,8 +169,7 @@ struct RGBA16161616 { uint16_t g; uint16_t b; uint16_t a; -}; -VTFPP_CHECK_SIZE(RGBA16161616); +}; VTFPP_CHECK_SIZE(RGBA16161616); struct UVLX8888 { static constexpr auto FORMAT = ImageFormat::UVLX8888; @@ -197,22 +177,19 @@ struct UVLX8888 { uint8_t v; uint8_t l; uint8_t x; -}; -VTFPP_CHECK_SIZE(UVLX8888); +}; VTFPP_CHECK_SIZE(UVLX8888); struct R32F { static constexpr auto FORMAT = ImageFormat::R32F; float r; -}; -VTFPP_CHECK_SIZE(R32F); +}; VTFPP_CHECK_SIZE(R32F); struct RGB323232F { static constexpr auto FORMAT = ImageFormat::R32F; float r; float g; float b; -}; -VTFPP_CHECK_SIZE(RGB323232F); +}; VTFPP_CHECK_SIZE(RGB323232F); struct RGBA32323232F { static constexpr auto FORMAT = ImageFormat::RGBA32323232F; @@ -220,22 +197,19 @@ struct RGBA32323232F { float g; float b; float a; -}; -VTFPP_CHECK_SIZE(RGBA32323232F); +}; VTFPP_CHECK_SIZE(RGBA32323232F); struct RG1616F { static constexpr auto FORMAT = ImageFormat::RG1616F; half r; half g; -}; -VTFPP_CHECK_SIZE(RG1616F); +}; VTFPP_CHECK_SIZE(RG1616F); struct RG3232F { static constexpr auto FORMAT = ImageFormat::RG3232F; float r; float g; -}; -VTFPP_CHECK_SIZE(RG3232F); +}; VTFPP_CHECK_SIZE(RG3232F); struct RGBX8888 { static constexpr auto FORMAT = ImageFormat::RGBX8888; @@ -243,8 +217,7 @@ struct RGBX8888 { uint8_t g; uint8_t b; uint8_t x; -}; -VTFPP_CHECK_SIZE(RGBX8888); +}; VTFPP_CHECK_SIZE(RGBX8888); struct RGBA1010102 { static constexpr auto FORMAT = ImageFormat::RGBA1010102; @@ -252,8 +225,7 @@ struct RGBA1010102 { uint32_t g : 10; uint32_t b : 10; uint32_t a : 2; -}; -VTFPP_CHECK_SIZE(RGBA1010102); +}; VTFPP_CHECK_SIZE(RGBA1010102); struct BGRA1010102 { static constexpr auto FORMAT = ImageFormat::BGRA1010102; @@ -261,21 +233,43 @@ struct BGRA1010102 { uint32_t g : 10; uint32_t r : 10; uint32_t a : 2; -}; -VTFPP_CHECK_SIZE(BGRA1010102); +}; VTFPP_CHECK_SIZE(BGRA1010102); struct R16F { static constexpr auto FORMAT = ImageFormat::R16F; half r; -}; -VTFPP_CHECK_SIZE(R16F); +}; VTFPP_CHECK_SIZE(R16F); struct R8 { static constexpr auto FORMAT = ImageFormat::R8; uint8_t r; -}; -VTFPP_CHECK_SIZE(R8); +}; VTFPP_CHECK_SIZE(R8); + +VTFPP_FORMAT_INHERITED(CONSOLE_BGRX8888_LINEAR, BGRX8888); + +VTFPP_FORMAT_INHERITED(CONSOLE_RGBA8888_LINEAR, RGBA8888); + +VTFPP_FORMAT_INHERITED(CONSOLE_ABGR8888_LINEAR, ABGR8888); + +VTFPP_FORMAT_INHERITED(CONSOLE_ARGB8888_LINEAR, ARGB8888); + +VTFPP_FORMAT_INHERITED(CONSOLE_BGRA8888_LINEAR, BGRA8888); + +VTFPP_FORMAT_INHERITED(CONSOLE_RGB888_LINEAR, RGB888); + +VTFPP_FORMAT_INHERITED(CONSOLE_BGR888_LINEAR, BGR888); + +VTFPP_FORMAT_INHERITED(CONSOLE_BGRX5551_LINEAR, BGRX5551); + +VTFPP_FORMAT_INHERITED(CONSOLE_I8_LINEAR, I8); + +VTFPP_FORMAT_INHERITED(CONSOLE_RGBA16161616_LINEAR, RGBA16161616); + +VTFPP_FORMAT_INHERITED(CONSOLE_BGRX8888_LE, BGRX8888); + +VTFPP_FORMAT_INHERITED(CONSOLE_BGRA8888_LE, BGRA8888); +#undef VTFPP_FORMAT_INHERITED #undef VTFPP_CHECK_SIZE template @@ -312,6 +306,18 @@ concept PixelType = std::same_as || std::same_as || std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || std::same_as; } // namespace ImagePixel diff --git a/include/vtfpp/ImageFormats.h b/include/vtfpp/ImageFormats.h index f515275ed..efb7084a6 100644 --- a/include/vtfpp/ImageFormats.h +++ b/include/vtfpp/ImageFormats.h @@ -5,6 +5,7 @@ namespace vtfpp { enum class ImageFormat : int32_t { + // region Universal Formats RGBA8888 = 0, ABGR8888, RGB888, @@ -35,6 +36,9 @@ enum class ImageFormat : int32_t { R32F, RGB323232F, RGBA32323232F, + // endregion + + // region Alien Swarm & Beyond Formats RG1616F, RG3232F, RGBX8888, @@ -44,10 +48,28 @@ enum class ImageFormat : int32_t { RGBA1010102, BGRA1010102, R16F, - + // endregion + + // region Console Formats + CONSOLE_BGRX8888_LINEAR = 42, + CONSOLE_RGBA8888_LINEAR, + CONSOLE_ABGR8888_LINEAR, + CONSOLE_ARGB8888_LINEAR, + CONSOLE_BGRA8888_LINEAR, + CONSOLE_RGB888_LINEAR, + CONSOLE_BGR888_LINEAR, + CONSOLE_BGRX5551_LINEAR, + CONSOLE_I8_LINEAR, + CONSOLE_RGBA16161616_LINEAR, + CONSOLE_BGRX8888_LE, + CONSOLE_BGRA8888_LE, + // endregion + + // region Strata Source Formats R8 = 69, BC7, BC6H, + // endregion }; namespace ImageFormatDetails { @@ -64,22 +86,33 @@ namespace ImageFormatDetails { case RG1616F: case RGBA16161616F: case RGBA16161616: + case CONSOLE_RGBA16161616_LINEAR: return 16; case RGBA1010102: case BGRA1010102: return 10; case RGBA8888: + case CONSOLE_RGBA8888_LINEAR: case ABGR8888: + case CONSOLE_ABGR8888_LINEAR: case RGB888: + case CONSOLE_RGB888_LINEAR: case BGR888: + case CONSOLE_BGR888_LINEAR: case I8: + case CONSOLE_I8_LINEAR: case IA88: case P8: case RGB888_BLUESCREEN: case BGR888_BLUESCREEN: case ARGB8888: + case CONSOLE_ARGB8888_LINEAR: case BGRA8888: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_BGRA8888_LE: case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case UV88: case UVWQ8888: case UVLX8888: @@ -89,6 +122,7 @@ namespace ImageFormatDetails { case RGB565: case BGR565: case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case BGRA5551: return 5; case BGRA4444: @@ -139,19 +173,29 @@ namespace ImageFormatDetails { case RG1616F: case RGBA16161616F: case RGBA16161616: + case CONSOLE_RGBA16161616_LINEAR: return 16; case RGBA1010102: case BGRA1010102: return 10; case RGBA8888: + case CONSOLE_RGBA8888_LINEAR: case ABGR8888: + case CONSOLE_ABGR8888_LINEAR: case RGB888: + case CONSOLE_RGB888_LINEAR: case BGR888: + case CONSOLE_BGR888_LINEAR: case RGB888_BLUESCREEN: case BGR888_BLUESCREEN: case ARGB8888: + case CONSOLE_ARGB8888_LINEAR: case BGRA8888: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_BGRA8888_LE: case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case UV88: case UVWQ8888: case UVLX8888: @@ -161,11 +205,13 @@ namespace ImageFormatDetails { case BGR565: return 6; case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case BGRA5551: return 5; case BGRA4444: return 4; case I8: + case CONSOLE_I8_LINEAR: case IA88: case P8: case R32F: @@ -215,19 +261,29 @@ namespace ImageFormatDetails { return 32; case RGBA16161616F: case RGBA16161616: + case CONSOLE_RGBA16161616_LINEAR: return 16; case RGBA1010102: case BGRA1010102: return 10; case RGBA8888: + case CONSOLE_RGBA8888_LINEAR: case ABGR8888: + case CONSOLE_ABGR8888_LINEAR: case RGB888: + case CONSOLE_RGB888_LINEAR: case BGR888: + case CONSOLE_BGR888_LINEAR: case RGB888_BLUESCREEN: case BGR888_BLUESCREEN: case ARGB8888: + case CONSOLE_ARGB8888_LINEAR: case BGRA8888: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_BGRA8888_LE: case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case UVWQ8888: case UVLX8888: case RGBX8888: @@ -235,11 +291,13 @@ namespace ImageFormatDetails { case RGB565: case BGR565: case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case BGRA5551: return 5; case BGRA4444: return 4; case I8: + case CONSOLE_I8_LINEAR: case IA88: case P8: case UV88: @@ -291,13 +349,21 @@ namespace ImageFormatDetails { return 32; case RGBA16161616F: case RGBA16161616: + case CONSOLE_RGBA16161616_LINEAR: return 16; case RGBA8888: + case CONSOLE_RGBA8888_LINEAR: case ABGR8888: + case CONSOLE_ABGR8888_LINEAR: case IA88: case ARGB8888: + case CONSOLE_ARGB8888_LINEAR: case BGRA8888: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_BGRA8888_LE: case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case UVWQ8888: case UVLX8888: case RGBX8888: @@ -308,12 +374,16 @@ namespace ImageFormatDetails { case BGRA1010102: return 2; case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case BGRA5551: return 1; case RGB888: + case CONSOLE_RGB888_LINEAR: case BGR888: + case CONSOLE_BGR888_LINEAR: case P8: case I8: + case CONSOLE_I8_LINEAR: case RGB888_BLUESCREEN: case BGR888_BLUESCREEN: case UV88: @@ -372,13 +442,21 @@ namespace ImageFormatDetails { return 96; case RGBA16161616F: case RGBA16161616: + case CONSOLE_RGBA16161616_LINEAR: case RG3232F: return 64; case RGBA8888: + case CONSOLE_RGBA8888_LINEAR: case ABGR8888: + case CONSOLE_ABGR8888_LINEAR: case ARGB8888: + case CONSOLE_ARGB8888_LINEAR: case BGRA8888: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_BGRA8888_LE: case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case UVLX8888: case R32F: case UVWQ8888: @@ -388,7 +466,9 @@ namespace ImageFormatDetails { case RG1616F: return 32; case RGB888: + case CONSOLE_RGB888_LINEAR: case BGR888: + case CONSOLE_BGR888_LINEAR: case RGB888_BLUESCREEN: case BGR888_BLUESCREEN: return 24; @@ -396,12 +476,14 @@ namespace ImageFormatDetails { case BGR565: case IA88: case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case BGRA4444: case BGRA5551: case UV88: case R16F: return 16; case I8: + case CONSOLE_I8_LINEAR: case P8: case A8: case DXT3: @@ -434,26 +516,38 @@ namespace ImageFormatDetails { case BC6H: return RGBA32323232F; case RGBA16161616: + case CONSOLE_RGBA16161616_LINEAR: case RGBA1010102: case BGRA1010102: return RGBA16161616; case RGBA8888: + case CONSOLE_RGBA8888_LINEAR: case ABGR8888: + case CONSOLE_ABGR8888_LINEAR: case RGB888: + case CONSOLE_RGB888_LINEAR: case BGR888: + case CONSOLE_BGR888_LINEAR: case RGB888_BLUESCREEN: case BGR888_BLUESCREEN: case ARGB8888: + case CONSOLE_ARGB8888_LINEAR: case BGRA8888: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_BGRA8888_LE: case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case UVWQ8888: case UVLX8888: case RGB565: case BGR565: case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case BGRA5551: case BGRA4444: case I8: + case CONSOLE_I8_LINEAR: case IA88: case P8: case UV88: @@ -510,7 +604,10 @@ namespace ImageFormatDetails { case BGR888_BLUESCREEN: return true; case BGRX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_BGRX8888_LE: case BGRX5551: + case CONSOLE_BGRX5551_LINEAR: case UVLX8888: case RGBX8888: return false; @@ -563,6 +660,28 @@ namespace ImageDimensions { return maxMipCount; } +[[nodiscard]] constexpr uint8_t getActualMipCountForDimsOnConsole(uint16_t width, uint16_t height) { + if (width == 0 || height == 0) { + return 0; + } + uint8_t numMipLevels = 1; + while (true) { + if (width == 1 && height == 1) { + break; + } + width >>= 1; + if (width < 1) { + width = 1; + } + height >>= 1; + if (height < 1) { + height = 1; + } + numMipLevels += 1; + } + return numMipLevels; +} + } // namespace ImageDimensions namespace ImageFormatDetails { diff --git a/include/vtfpp/VTF.h b/include/vtfpp/VTF.h index 5deb09ef7..c5c259850 100644 --- a/include/vtfpp/VTF.h +++ b/include/vtfpp/VTF.h @@ -18,10 +18,15 @@ namespace vtfpp { constexpr uint32_t VTF_SIGNATURE = sourcepp::parser::binary::makeFourCC("VTF\0"); +constexpr uint32_t VTFX_SIGNATURE = sourcepp::parser::binary::makeFourCC("VTFX"); enum class CompressionMethod : int16_t { + // Strata Source v7.6 defines DEFLATE = 8, ZSTD = 93, + + // Signify the image resource should be compressed with LZMA on console + CONSOLE_LZMA = 0x360, }; struct Resource { @@ -157,6 +162,13 @@ class VTF { }; static constexpr std::underlying_type_t FLAG_MASK_GENERATED = FLAG_NO_MIP | FLAG_NO_LOD | FLAG_ONE_BIT_ALPHA | FLAG_MULTI_BIT_ALPHA | FLAG_ENVMAP; + enum Platform : uint32_t { + PLATFORM_UNKNOWN = 0, + PLATFORM_PC = 1, + PLATFORM_PS3 = 0x333, + PLATFORM_X360 = 0x360, + }; + struct CreationOptions { uint32_t majorVersion = 7; uint32_t minorVersion = 4; @@ -173,6 +185,7 @@ class VTF { bool createMips = true; bool createThumbnail = true; bool createReflectivity = true; + Platform platform = PLATFORM_PC; int16_t compressionLevel = -1; CompressionMethod compressionMethod = CompressionMethod::ZSTD; float bumpMapScale = 1.f; @@ -184,8 +197,6 @@ class VTF { /// This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat static constexpr auto FORMAT_DEFAULT = static_cast(-1); - static constexpr int32_t MAX_RESOURCES = 32; - VTF(); explicit VTF(std::vector&& vtfData, bool parseHeaderOnly = false); @@ -216,6 +227,10 @@ class VTF { [[nodiscard]] static VTF create(const std::string& imagePath, CreationOptions options); + [[nodiscard]] Platform getPlatform() const; + + void setPlatform(Platform newPlatform); + [[nodiscard]] uint32_t getMajorVersion() const; [[nodiscard]] uint32_t getMinorVersion() const; @@ -270,8 +285,6 @@ class VTF { bool setFaceCount(bool isCubemap, bool hasSphereMap = false); - //bool computeSphereMap(); - [[nodiscard]] uint16_t getSliceCount() const; bool setSliceCount(uint16_t newSliceCount); @@ -413,10 +426,10 @@ class VTF { //uint8_t _padding1[4]; float bumpMapScale{}; - ImageFormat format{}; + ImageFormat format = ImageFormat::EMPTY; uint8_t mipCount = 1; - ImageFormat thumbnailFormat{}; + ImageFormat thumbnailFormat = ImageFormat::EMPTY; uint8_t thumbnailWidth{}; uint8_t thumbnailHeight{}; @@ -429,7 +442,8 @@ class VTF { std::vector resources; //uint8_t _padding3[4]; - // These aren't in the header, these are for VTF modification + // These aren't in the header + Platform platform = PLATFORM_PC; int16_t compressionLevel = 0; CompressionMethod compressionMethod = CompressionMethod::ZSTD; ImageConversion::ResizeMethod imageWidthResizeMethod = ImageConversion::ResizeMethod::POWER_OF_TWO_BIGGER; diff --git a/lang/python/src/vtfpp.h b/lang/python/src/vtfpp.h index 62fa54739..575a42abc 100644 --- a/lang/python/src/vtfpp.h +++ b/lang/python/src/vtfpp.h @@ -367,6 +367,13 @@ void register_python(py::module_& m) { .value("SPECVAR_ALPHA", VTF::FLAG_SPECVAR_ALPHA) .export_values(); + py::enum_(cVTF, "Platform") + .value("UNKNOWN", VTF::PLATFORM_UNKNOWN) + .value("PC", VTF::PLATFORM_PC) + .value("PS3", VTF::PLATFORM_PS3) + .value("X360", VTF::PLATFORM_X360) + .export_values(); + py::class_(cVTF, "CreationOptions") .def(py::init<>()) .def_rw("major_version", &VTF::CreationOptions::majorVersion) @@ -392,7 +399,6 @@ void register_python(py::module_& m) { .def_ro_static("FLAG_MASK_GENERATED", &VTF::FLAG_MASK_GENERATED) .def_ro_static("FORMAT_UNCHANGED", &VTF::FORMAT_UNCHANGED) .def_ro_static("FORMAT_DEFAULT", &VTF::FORMAT_DEFAULT) - .def_ro_static("MAX_RESOURCES", &VTF::MAX_RESOURCES) .def(py::init<>()) .def("__init__", [](VTF* self, const py::bytes& vtfData, bool parseHeaderOnly = false) { return new(self) VTF{std::span{reinterpret_cast(vtfData.data()), vtfData.size()}, parseHeaderOnly}; @@ -409,6 +415,7 @@ void register_python(py::module_& m) { .def_static("create_blank", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create_from_file_and_bake", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) .def_static("create_from_file", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_prop_rw("platform", &VTF::getPlatform, &VTF::setPlatform) .def_prop_rw("version_major", &VTF::getMajorVersion, &VTF::setMajorVersion) .def_prop_rw("version_minor", &VTF::getMinorVersion, &VTF::setMinorVersion) .def_prop_rw("image_width_resize_method", &VTF::getImageWidthResizeMethod, &VTF::setImageWidthResizeMethod) diff --git a/src/vtfpp/ImageConversion.cpp b/src/vtfpp/ImageConversion.cpp index ce60fb62d..3d85fdfe3 100644 --- a/src/vtfpp/ImageConversion.cpp +++ b/src/vtfpp/ImageConversion.cpp @@ -121,6 +121,18 @@ namespace { case EMPTY: case BGRA1010102: case RGBX8888: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_RGBA8888_LINEAR: + case CONSOLE_ABGR8888_LINEAR: + case CONSOLE_ARGB8888_LINEAR: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_RGB888_LINEAR: + case CONSOLE_BGR888_LINEAR: + case CONSOLE_BGRX5551_LINEAR: + case CONSOLE_I8_LINEAR: + case CONSOLE_RGBA16161616_LINEAR: + case CONSOLE_BGRX8888_LE: + case CONSOLE_BGRA8888_LE: return CMP_FORMAT_Unknown; } return CMP_FORMAT_Unknown; @@ -181,6 +193,18 @@ namespace { case EMPTY: case RGBA1010102: case BGRA1010102: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_RGBA8888_LINEAR: + case CONSOLE_ABGR8888_LINEAR: + case CONSOLE_ARGB8888_LINEAR: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_RGB888_LINEAR: + case CONSOLE_BGR888_LINEAR: + case CONSOLE_BGRX5551_LINEAR: + case CONSOLE_I8_LINEAR: + case CONSOLE_RGBA16161616_LINEAR: + case CONSOLE_BGRX8888_LE: + case CONSOLE_BGRA8888_LE: break; } return -1; @@ -233,6 +257,18 @@ namespace { case ATI1N: case RGBA1010102: case BGRA1010102: + case CONSOLE_BGRX8888_LINEAR: + case CONSOLE_RGBA8888_LINEAR: + case CONSOLE_ABGR8888_LINEAR: + case CONSOLE_ARGB8888_LINEAR: + case CONSOLE_BGRA8888_LINEAR: + case CONSOLE_RGB888_LINEAR: + case CONSOLE_BGR888_LINEAR: + case CONSOLE_BGRX5551_LINEAR: + case CONSOLE_I8_LINEAR: + case CONSOLE_RGBA16161616_LINEAR: + case CONSOLE_BGRX8888_LE: + case CONSOLE_BGRA8888_LE: case BC7: case BC6H: break; @@ -310,27 +346,38 @@ namespace { switch (format) { using enum ImageFormat; - VTFPP_CASE_CONVERT_AND_BREAK(ABGR8888, pixel.r, pixel.g, pixel.b, pixel.a); - VTFPP_CASE_CONVERT_AND_BREAK(RGB888, pixel.r, pixel.g, pixel.b, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(RGB888_BLUESCREEN, pixel.r, pixel.g, pixel.b, static_cast((pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff) ? 0 : 0xff)); - VTFPP_CASE_CONVERT_AND_BREAK(BGR888, pixel.r, pixel.g, pixel.b, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(BGR888_BLUESCREEN, pixel.r, pixel.g, pixel.b, static_cast((pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff) ? 0 : 0xff)); - VTFPP_CASE_CONVERT_AND_BREAK(RGB565, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 6), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(P8, pixel.p, pixel.p, pixel.p, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(I8, pixel.i, pixel.i, pixel.i, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(IA88, pixel.i, pixel.i, pixel.i, pixel.a); - VTFPP_CASE_CONVERT_AND_BREAK(A8, 0, 0, 0, pixel.a); - VTFPP_CASE_CONVERT_AND_BREAK(ARGB8888, pixel.r, pixel.g, pixel.b, pixel.a); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA8888, pixel.r, pixel.g, pixel.b, pixel.a); - VTFPP_CASE_CONVERT_AND_BREAK(BGRX8888, pixel.r, pixel.g, pixel.b, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(BGR565, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 6), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA5551, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 5), VTFPP_REMAP_TO_8(pixel.b, 5), static_cast(pixel.a * 0xff)); - VTFPP_CASE_CONVERT_AND_BREAK(BGRX5551, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 5), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA4444, VTFPP_REMAP_TO_8(pixel.r, 4), VTFPP_REMAP_TO_8(pixel.g, 4), VTFPP_REMAP_TO_8(pixel.b, 4), VTFPP_REMAP_TO_8(pixel.a, 4)); - VTFPP_CASE_CONVERT_AND_BREAK(UV88, pixel.u, pixel.v, 0, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(UVLX8888, pixel.u, pixel.v, pixel.l, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(RGBX8888, pixel.r, pixel.g, pixel.b, 0xff); - VTFPP_CASE_CONVERT_AND_BREAK(R8, pixel.r, 0, 0, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(ABGR8888, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(RGB888, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(RGB888_BLUESCREEN, pixel.r, pixel.g, pixel.b, static_cast((pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff) ? 0 : 0xff)); + VTFPP_CASE_CONVERT_AND_BREAK(BGR888, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(BGR888_BLUESCREEN, pixel.r, pixel.g, pixel.b, static_cast((pixel.r == 0 && pixel.g == 0 && pixel.b == 0xff) ? 0 : 0xff)); + VTFPP_CASE_CONVERT_AND_BREAK(RGB565, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 6), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(P8, pixel.p, pixel.p, pixel.p, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(I8, pixel.i, pixel.i, pixel.i, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(IA88, pixel.i, pixel.i, pixel.i, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(A8, 0, 0, 0, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(ARGB8888, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA8888, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(BGRX8888, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(BGR565, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 6), VTFPP_REMAP_TO_8(pixel.b, 5), 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA5551, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 5), VTFPP_REMAP_TO_8(pixel.b, 5), static_cast(pixel.a * 0xff)); + VTFPP_CASE_CONVERT_AND_BREAK(BGRX5551, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 5), VTFPP_REMAP_TO_8(pixel.b, 5), 1); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA4444, VTFPP_REMAP_TO_8(pixel.r, 4), VTFPP_REMAP_TO_8(pixel.g, 4), VTFPP_REMAP_TO_8(pixel.b, 4), VTFPP_REMAP_TO_8(pixel.a, 4)); + VTFPP_CASE_CONVERT_AND_BREAK(UV88, pixel.u, pixel.v, 0, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(UVLX8888, pixel.u, pixel.v, pixel.l, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(RGBX8888, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LINEAR, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ABGR8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ARGB8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGB888_LINEAR, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGR888_LINEAR, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX5551_LINEAR, VTFPP_REMAP_TO_8(pixel.r, 5), VTFPP_REMAP_TO_8(pixel.g, 5), VTFPP_REMAP_TO_8(pixel.b, 5), 1); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_I8_LINEAR, pixel.i, pixel.i, pixel.i, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LE, pixel.r, pixel.g, pixel.b, 0xff); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LE, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(R8, pixel.r, 0, 0, 0xff); default: SOURCEPP_DEBUG_BREAK; break; } @@ -377,27 +424,38 @@ namespace { switch (format) { using enum ImageFormat; - VTFPP_CASE_CONVERT_AND_BREAK(ABGR8888, {pixel.a, pixel.b, pixel.g, pixel.r}); - VTFPP_CASE_CONVERT_AND_BREAK(RGB888, {pixel.r, pixel.g, pixel.b}); - VTFPP_CASE_CONVERT_AND_BREAK(RGB888_BLUESCREEN, pixel.a < 0xff ? ImagePixel::RGB888_BLUESCREEN{pixel.r, pixel.g, pixel.b} : ImagePixel::RGB888_BLUESCREEN{0, 0, 0xff}); - VTFPP_CASE_CONVERT_AND_BREAK(BGR888, {pixel.b, pixel.g, pixel.r}); - VTFPP_CASE_CONVERT_AND_BREAK(BGR888_BLUESCREEN, pixel.a < 0xff ? ImagePixel::BGR888_BLUESCREEN{pixel.b, pixel.g, pixel.r} : ImagePixel::BGR888_BLUESCREEN{0xff, 0, 0}); - VTFPP_CASE_CONVERT_AND_BREAK(RGB565, {VTFPP_REMAP_FROM_8(pixel.r, 5), VTFPP_REMAP_FROM_8(pixel.g, 6), VTFPP_REMAP_FROM_8(pixel.b, 5)}); - VTFPP_CASE_CONVERT_AND_BREAK(P8, {pixel.r}); - VTFPP_CASE_CONVERT_AND_BREAK(I8, {pixel.r}); - VTFPP_CASE_CONVERT_AND_BREAK(IA88, {pixel.r, pixel.a}); - VTFPP_CASE_CONVERT_AND_BREAK(A8, {pixel.a}); - VTFPP_CASE_CONVERT_AND_BREAK(ARGB8888, {pixel.a, pixel.r, pixel.g, pixel.b}); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA8888, {pixel.b, pixel.g, pixel.r, pixel.a}); - VTFPP_CASE_CONVERT_AND_BREAK(BGRX8888, {pixel.b, pixel.g, pixel.r, 0xff}); - VTFPP_CASE_CONVERT_AND_BREAK(BGR565, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 6), VTFPP_REMAP_FROM_8(pixel.r, 5)}); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA5551, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 5), VTFPP_REMAP_FROM_8(pixel.r, 5), static_cast(pixel.a < 0xff ? 1 : 0)}); - VTFPP_CASE_CONVERT_AND_BREAK(BGRX5551, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 5), VTFPP_REMAP_FROM_8(pixel.r, 5), 0x1}); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA4444, {VTFPP_REMAP_FROM_8(pixel.b, 4), VTFPP_REMAP_FROM_8(pixel.g, 4), VTFPP_REMAP_FROM_8(pixel.r, 4), VTFPP_REMAP_FROM_8(pixel.a, 4)}); - VTFPP_CASE_CONVERT_AND_BREAK(UV88, {pixel.r, pixel.g}); - VTFPP_CASE_CONVERT_AND_BREAK(UVLX8888, {pixel.r, pixel.g, pixel.b}); - VTFPP_CASE_CONVERT_AND_BREAK(RGBX8888, {pixel.r, pixel.g, pixel.b, 0xff}); - VTFPP_CASE_CONVERT_AND_BREAK(R8, {pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(ABGR8888, {pixel.a, pixel.b, pixel.g, pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(RGB888, {pixel.r, pixel.g, pixel.b}); + VTFPP_CASE_CONVERT_AND_BREAK(RGB888_BLUESCREEN, pixel.a < 0xff ? ImagePixel::RGB888_BLUESCREEN{pixel.r, pixel.g, pixel.b} : ImagePixel::RGB888_BLUESCREEN{0, 0, 0xff}); + VTFPP_CASE_CONVERT_AND_BREAK(BGR888, {pixel.b, pixel.g, pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(BGR888_BLUESCREEN, pixel.a < 0xff ? ImagePixel::BGR888_BLUESCREEN{pixel.b, pixel.g, pixel.r} : ImagePixel::BGR888_BLUESCREEN{0xff, 0, 0}); + VTFPP_CASE_CONVERT_AND_BREAK(RGB565, {VTFPP_REMAP_FROM_8(pixel.r, 5), VTFPP_REMAP_FROM_8(pixel.g, 6), VTFPP_REMAP_FROM_8(pixel.b, 5)}); + VTFPP_CASE_CONVERT_AND_BREAK(P8, {pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(I8, {pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(IA88, {pixel.r, pixel.a}); + VTFPP_CASE_CONVERT_AND_BREAK(A8, {pixel.a}); + VTFPP_CASE_CONVERT_AND_BREAK(ARGB8888, {pixel.a, pixel.r, pixel.g, pixel.b}); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA8888, {pixel.b, pixel.g, pixel.r, pixel.a}); + VTFPP_CASE_CONVERT_AND_BREAK(BGRX8888, {pixel.b, pixel.g, pixel.r, 0xff}); + VTFPP_CASE_CONVERT_AND_BREAK(BGR565, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 6), VTFPP_REMAP_FROM_8(pixel.r, 5)}); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA5551, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 5), VTFPP_REMAP_FROM_8(pixel.r, 5), static_cast(pixel.a < 0xff ? 1 : 0)}); + VTFPP_CASE_CONVERT_AND_BREAK(BGRX5551, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 5), VTFPP_REMAP_FROM_8(pixel.r, 5), 1}); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA4444, {VTFPP_REMAP_FROM_8(pixel.b, 4), VTFPP_REMAP_FROM_8(pixel.g, 4), VTFPP_REMAP_FROM_8(pixel.r, 4), VTFPP_REMAP_FROM_8(pixel.a, 4)}); + VTFPP_CASE_CONVERT_AND_BREAK(UV88, {pixel.r, pixel.g}); + VTFPP_CASE_CONVERT_AND_BREAK(UVLX8888, {pixel.r, pixel.g, pixel.b}); + VTFPP_CASE_CONVERT_AND_BREAK(RGBX8888, {pixel.r, pixel.g, pixel.b, 0xff}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LINEAR, {pixel.b, pixel.g, pixel.r, 0xff}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA8888_LINEAR, {pixel.r, pixel.g, pixel.b, pixel.a}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ABGR8888_LINEAR, {pixel.a, pixel.b, pixel.g, pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_ARGB8888_LINEAR, {pixel.a, pixel.r, pixel.g, pixel.b}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LINEAR, {pixel.b, pixel.g, pixel.r, pixel.a}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGB888_LINEAR, {pixel.r, pixel.g, pixel.b}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGR888_LINEAR, {pixel.b, pixel.g, pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX5551_LINEAR, {VTFPP_REMAP_FROM_8(pixel.b, 5), VTFPP_REMAP_FROM_8(pixel.g, 5), VTFPP_REMAP_FROM_8(pixel.r, 5), 1}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_I8_LINEAR, {pixel.r}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRX8888_LE, {pixel.b, pixel.g, pixel.r, 0xff}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_BGRA8888_LE, {pixel.b, pixel.g, pixel.r, pixel.a}); + VTFPP_CASE_CONVERT_AND_BREAK(R8, {pixel.r}); default: SOURCEPP_DEBUG_BREAK; break; } @@ -466,8 +524,9 @@ namespace { switch (format) { using enum ImageFormat; - VTFPP_CASE_CONVERT_REMAP_AND_BREAK(RGBA1010102, pixel.r, pixel.g, pixel.b, pixel.a); - VTFPP_CASE_CONVERT_REMAP_AND_BREAK(BGRA1010102, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_REMAP_AND_BREAK(RGBA1010102, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_REMAP_AND_BREAK(BGRA1010102, pixel.r, pixel.g, pixel.b, pixel.a); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA16161616_LINEAR, pixel.r, pixel.g, pixel.b, pixel.a); default: SOURCEPP_DEBUG_BREAK; break; } @@ -516,8 +575,9 @@ namespace { switch (format) { using enum ImageFormat; - VTFPP_CASE_CONVERT_AND_BREAK(RGBA1010102, {VTFPP_REMAP_FROM_16(pixel.r, 10), VTFPP_REMAP_FROM_16(pixel.g, 10), VTFPP_REMAP_FROM_16(pixel.b, 10), VTFPP_REMAP_FROM_16(pixel.a, 2)}); - VTFPP_CASE_CONVERT_AND_BREAK(BGRA1010102, {VTFPP_REMAP_FROM_16(pixel.b, 10), VTFPP_REMAP_FROM_16(pixel.g, 10), VTFPP_REMAP_FROM_16(pixel.r, 10), VTFPP_REMAP_FROM_16(pixel.a, 2)}); + VTFPP_CASE_CONVERT_AND_BREAK(RGBA1010102, {VTFPP_REMAP_FROM_16(pixel.r, 10), VTFPP_REMAP_FROM_16(pixel.g, 10), VTFPP_REMAP_FROM_16(pixel.b, 10), VTFPP_REMAP_FROM_16(pixel.a, 2)}); + VTFPP_CASE_CONVERT_AND_BREAK(BGRA1010102, {VTFPP_REMAP_FROM_16(pixel.b, 10), VTFPP_REMAP_FROM_16(pixel.g, 10), VTFPP_REMAP_FROM_16(pixel.r, 10), VTFPP_REMAP_FROM_16(pixel.a, 2)}); + VTFPP_CASE_CONVERT_AND_BREAK(CONSOLE_RGBA16161616_LINEAR, {pixel.r, pixel.g, pixel.b, pixel.a}); default: SOURCEPP_DEBUG_BREAK; break; } @@ -1805,6 +1865,7 @@ std::vector ImageConversion::resizeImageDataStrict(std::span ImageConversion::cropImageData(std::span imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset) { if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) { return {}; diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp index d63ab26ab..8eae932e6 100644 --- a/src/vtfpp/VTF.cpp +++ b/src/vtfpp/VTF.cpp @@ -5,6 +5,10 @@ #include #include +#ifdef SOURCEPP_BUILD_WITH_TBB +#include +#endif + #ifdef SOURCEPP_BUILD_WITH_THREADS #include #include @@ -14,6 +18,7 @@ #include #include +#include #include using namespace sourcepp; @@ -56,10 +61,56 @@ std::vector compressData(std::span data, int16_t lev out.resize(compressedSize); return out; } + case CONSOLE_LZMA: { + const auto out = compression::compressValveLZMA(data, level); + if (out) { + return *out; + } + return {}; + } } return {}; } +void swapImageDataEndianForConsole(std::span imageData, ImageFormat format, uint16_t width, uint16_t height, VTF::Platform platform) { + if (imageData.empty() || format == ImageFormat::EMPTY || platform == VTF::PLATFORM_PC) { + return; + } + + switch (format) { + using enum ImageFormat; + case BGRA8888: + case BGRX8888: + case UVWQ8888: + case UVLX8888: { + const auto newData = ImageConversion::convertImageDataToFormat(imageData, ImageFormat::ARGB8888, ImageFormat::BGRA8888, width, height); + std::copy(newData.begin(), newData.end(), imageData.begin()); + break; + } + case DXT1: + case DXT1_ONE_BIT_ALPHA: + case DXT3: + case DXT5: + case UV88: { + if (platform != VTF::PLATFORM_X360) { + break; + } + std::span dxtData{reinterpret_cast(imageData.data()), imageData.size() / sizeof(uint16_t)}; + std::for_each( +#ifdef SOURCEPP_BUILD_WITH_TBB + std::execution::par_unseq, +#endif + dxtData.begin(), dxtData.end(), [](uint16_t& value) { + BufferStream::swap_endian(&value); + }); + break; + } + default: + // SOURCEPP_DEBUG_BREAK; + break; + } +} + } // namespace const std::array& Resource::getOrder() { @@ -139,15 +190,154 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly) : data(std::move(vtfData)) { BufferStreamReadOnly stream{this->data}; - if (stream.read() != VTF_SIGNATURE) { + if (auto signature = stream.read(); signature == VTF_SIGNATURE) { + this->platform = PLATFORM_PC; + stream >> this->majorVersion >> this->minorVersion; + if (this->majorVersion != 7 || this->minorVersion > 6) { + return; + } + } else if (signature == VTFX_SIGNATURE) { + stream.set_big_endian(true); + uint32_t minorConsoleVersion = 0; + stream >> this->platform >> minorConsoleVersion; + if (minorConsoleVersion != 8) { + return; + } + switch (this->platform) { + case PLATFORM_PS3: + case PLATFORM_X360: + this->majorVersion = 7; + this->minorVersion = 4; + break; + default: + this->platform = PLATFORM_UNKNOWN; + return; + } + } else { return; } - stream >> this->majorVersion >> this->minorVersion; - if (this->majorVersion != 7 || this->minorVersion > 6) { + const auto headerSize = stream.read(); + + const auto readResources = [this, &stream](uint32_t resourceCount) { + this->resources.reserve(resourceCount); + + Resource* lastResource = nullptr; + for (int i = 0; i < resourceCount; i++) { + auto& [type, flags_, data_] = this->resources.emplace_back(); + + auto typeAndFlags = stream.read(); + if (stream.is_big_endian()) { + // This field is little-endian + BufferStream::swap_endian(&typeAndFlags); + } + type = static_cast(typeAndFlags & 0xffffff); // last 3 bytes + flags_ = static_cast(typeAndFlags >> 24); // first byte + data_ = stream.read_span(4); + + if (!(flags_ & Resource::FLAG_LOCAL_DATA)) { + if (lastResource) { + auto lastOffset = *reinterpret_cast(lastResource->data.data()); + auto currentOffset = *reinterpret_cast(data_.data()); + if (stream.is_big_endian()) { + // Data is still big-endian + BufferStream::swap_endian(&lastOffset); + BufferStream::swap_endian(¤tOffset); + } + + auto curPos = stream.tell(); + stream.seek(lastOffset); + lastResource->data = stream.read_span(currentOffset - lastOffset); + stream.seek(static_cast(curPos)); + } + lastResource = &this->resources.back(); + } + } + if (lastResource) { + auto offset = *reinterpret_cast(lastResource->data.data()); + if (stream.is_big_endian()) { + // Data is still big-endian + BufferStream::swap_endian(&offset); + } + + auto curPos = stream.tell(); + stream.seek(offset); + lastResource->data = stream.read_span(stream.size() - offset); + stream.seek(static_cast(curPos)); + } + }; + + if (this->platform != PLATFORM_PC) { + uint8_t resourceCount; + stream + .read(this->flags) + .read(this->width) + .read(this->height) + .read(this->sliceCount) + .read(this->frameCount) + .skip() // preload + .skip() // skip high mip levels + .read(resourceCount) + .read(this->reflectivity[0]) + .read(this->reflectivity[1]) + .read(this->reflectivity[2]) + .read(this->bumpMapScale) + .read(this->format) + .skip() // lowResImageSample (replacement for thumbnail resource, presumably linear color) + .skip(); // compressedLength + + this->mipCount = (this->flags & FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height); + + if (parseHeaderOnly) { + this->opened = true; + return; + } + + this->resources.reserve(resourceCount); + readResources(resourceCount); + + this->opened = stream.tell() == headerSize; + + // The resources vector isn't modified by setResourceInternal when we're not adding a new one, so this is fine + for (const auto& resource : this->resources) { + // Decompress LZMA resources + if (BufferStreamReadOnly rsrcStream{resource.data.data(), resource.data.size()}; rsrcStream.read() == compression::VALVE_LZMA_SIGNATURE) { + if (auto decompressedData = compression::decompressValveLZMA(resource.data)) { + this->setResourceInternal(resource.type, *decompressedData); + + if (resource.type == Resource::TYPE_IMAGE_DATA) { + // Do this here because compressionLength in header can be garbage on PS3 + this->compressionMethod = CompressionMethod::CONSOLE_LZMA; + } + } + } + + // Note: LOD, CRC, TSO may be incorrect - I can't find a sample of any in official console VTFs + // If tweaking this switch, tweak the one in bake as well + switch (resource.type) { + default: + case Resource::TYPE_UNKNOWN: + case Resource::TYPE_CRC: + case Resource::TYPE_LOD_CONTROL_INFO: + case Resource::TYPE_AUX_COMPRESSION: // Strata-specific + break; + case Resource::TYPE_THUMBNAIL_DATA: + ::swapImageDataEndianForConsole(resource.data, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, this->platform); + break; + case Resource::TYPE_IMAGE_DATA: + ::swapImageDataEndianForConsole(resource.data, this->format, this->width, this->height, this->platform); + break; + case Resource::TYPE_PARTICLE_SHEET_DATA: + case Resource::TYPE_EXTENDED_FLAGS: + case Resource::TYPE_KEYVALUES_DATA: + if (resource.data.size() >= sizeof(uint32_t)) { + BufferStream::swap_endian(reinterpret_cast(resource.data.data())); + } + break; + } + } return; } - const auto headerSize = stream.read(); stream .read(this->width) @@ -156,7 +346,9 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly) .read(this->frameCount) .read(this->startFrame) .skip(4) - .read(this->reflectivity) + .read(this->reflectivity[0]) + .read(this->reflectivity[1]) + .read(this->reflectivity[2]) .skip(4) .read(this->bumpMapScale) .read(this->format) @@ -186,41 +378,7 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly) stream.skip(3); auto resourceCount = stream.read(); stream.skip(8); - - if (resourceCount > VTF::MAX_RESOURCES) { - resourceCount = VTF::MAX_RESOURCES; - } - this->resources.reserve(resourceCount); - - Resource* lastResource = nullptr; - for (int i = 0; i < resourceCount; i++) { - auto& [type, flags_, data_] = this->resources.emplace_back(); - - auto typeAndFlags = stream.read(); - type = static_cast(typeAndFlags & 0xffffff); // last 3 bytes - flags_ = static_cast(typeAndFlags >> 24); // first byte - data_ = stream.read_span(4); - - if (!(flags_ & Resource::FLAG_LOCAL_DATA)) { - if (lastResource) { - auto lastOffset = *reinterpret_cast(lastResource->data.data()); - auto currentOffset = *reinterpret_cast(data_.data()); - - auto curPos = stream.tell(); - stream.seek(lastOffset); - lastResource->data = stream.read_span(currentOffset - lastOffset); - stream.seek(static_cast(curPos)); - } - lastResource = &this->resources.back(); - } - } - if (lastResource) { - auto offset = *reinterpret_cast(lastResource->data.data()); - auto curPos = stream.tell(); - stream.seek(offset); - lastResource->data = stream.read_span(stream.size() - offset); - stream.seek(static_cast(curPos)); - } + readResources(resourceCount); this->opened = stream.tell() == headerSize; @@ -253,6 +411,10 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly) return; } break; + case CONSOLE_LZMA: + // Shouldn't be here! + SOURCEPP_DEBUG_BREAK; + break; } } oldOffset += oldLength; @@ -329,6 +491,7 @@ VTF& VTF::operator=(const VTF& other) { data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()}; } + this->platform = other.platform; this->compressionLevel = other.compressionLevel; this->compressionMethod = other.compressionMethod; this->imageWidthResizeMethod = other.imageWidthResizeMethod; @@ -377,6 +540,7 @@ void VTF::createInternal(VTF& writer, CreationOptions options) { writer.setFormat(options.outputFormat); writer.setCompressionLevel(options.compressionLevel); writer.setCompressionMethod(options.compressionMethod); + writer.setPlatform(options.platform); } void VTF::create(std::span imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, CreationOptions options) { @@ -431,6 +595,18 @@ VTF VTF::create(const std::string& imagePath, CreationOptions options) { return writer; } +VTF::Platform VTF::getPlatform() const { + return this->platform; +} + +void VTF::setPlatform(Platform newPlatform) { + if (this->platform == PLATFORM_X360 || this->platform == PLATFORM_PS3) { + this->setVersion(7, 4); + } + this->platform = newPlatform; + this->setCompressionMethod(this->compressionMethod); +} + uint32_t VTF::getMajorVersion() const { return this->majorVersion; } @@ -445,10 +621,16 @@ void VTF::setVersion(uint32_t newMajorVersion, uint32_t newMinorVersion) { } void VTF::setMajorVersion(uint32_t newMajorVersion) { + if (this->platform != PLATFORM_PC) { + return; + } this->majorVersion = newMajorVersion; } void VTF::setMinorVersion(uint32_t newMinorVersion) { + if (this->platform != PLATFORM_PC) { + return; + } if (this->hasImageData()) { auto faceCount = this->getFaceCount(); if (faceCount == 7 && (newMinorVersion < 1 || newMinorVersion > 4)) { @@ -679,16 +861,6 @@ bool VTF::setFaceCount(bool isCubemap, bool hasSphereMap) { return true; } -/* -bool VTF::computeSphereMap() { - if (this->getFaceCount() < 7) { - return false; - } - // compute spheremap here - return true; -} -*/ - uint16_t VTF::getSliceCount() const { return this->sliceCount; } @@ -839,8 +1011,8 @@ void VTF::setResourceInternal(Resource::Type type, std::span da // Store resource data std::unordered_map, uint64_t>> resourceData; - for (const auto& [type, flags, data] : this->resources) { - resourceData[type] = {std::vector{data.begin(), data.end()}, 0}; + for (const auto& [type_, flags_, dataSpan] : this->resources) { + resourceData[type_] = {std::vector{dataSpan.begin(), dataSpan.end()}, 0}; } // Set new resource @@ -878,10 +1050,10 @@ void VTF::setResourceInternal(Resource::Type type, std::span da } this->data.resize(writer.size()); - for (auto& [type, flags, data] : this->resources) { - if (resourceData.contains(type)) { - const auto& [specificResourceData, offset] = resourceData[type]; - data = {this->data.data() + offset, specificResourceData.size()}; + for (auto& [type_, flags_, dataSpan] : this->resources) { + if (resourceData.contains(type_)) { + const auto& [specificResourceData, offset] = resourceData[type_]; + dataSpan = {this->data.data() + offset, specificResourceData.size()}; } } } @@ -1089,7 +1261,13 @@ CompressionMethod VTF::getCompressionMethod() const { } void VTF::setCompressionMethod(CompressionMethod newCompressionMethod) { - this->compressionMethod = newCompressionMethod; + if (newCompressionMethod == CompressionMethod::CONSOLE_LZMA && this->platform == VTF::PLATFORM_PC) { + this->compressionMethod = CompressionMethod::ZSTD; + } else if (newCompressionMethod != CompressionMethod::CONSOLE_LZMA && this->platform != VTF::PLATFORM_PC) { + this->compressionMethod = CompressionMethod::CONSOLE_LZMA; + } else { + this->compressionMethod = newCompressionMethod; + } } bool VTF::hasImageData() const { @@ -1272,6 +1450,133 @@ std::vector VTF::bake() const { std::vector out; BufferStream writer{out}; + static constexpr auto writeNonLocalResource = [](BufferStream& writer_, Resource::Type type, std::span data, VTF::Platform platform) { + if (platform != VTF::PLATFORM_PC) { + BufferStream::swap_endian(reinterpret_cast(&type)); + } + writer_.write(type); + const auto resourceOffsetPos = writer_.tell(); + writer_.seek(0, std::ios::end); + const auto resourceOffsetValue = writer_.tell(); + writer_.write(data); + writer_.seek_u(resourceOffsetPos).write(resourceOffsetValue); + }; + + if (this->platform != PLATFORM_PC) { + writer << VTFX_SIGNATURE; + writer.set_big_endian(true); + + writer + .write(this->platform) + .write(8); + const auto headerLengthPos = writer.tell(); + writer + .write(0) + .write(this->flags) + .write(this->width) + .write(this->height) + .write(this->sliceCount) + .write(this->frameCount); + const auto preloadPos = writer.tell(); + writer + .write(0) // preload size + .write(0) // skip higher mips + .write(this->resources.size()) + .write(this->reflectivity[0]) + .write(this->reflectivity[1]) + .write(this->reflectivity[2]) + .write(this->bumpMapScale) + .write(this->format) + .write(std::clamp(static_cast(std::roundf(this->reflectivity[0] * 255)), 0, 255)) + .write(std::clamp(static_cast(std::roundf(this->reflectivity[1] * 255)), 0, 255)) + .write(std::clamp(static_cast(std::roundf(this->reflectivity[2] * 255)), 0, 255)) + .write(255); + const auto compressionPos = writer.tell(); + writer.write(0); // compressed length + + std::vector imageResourceData; + bool hasCompression = false; + if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) { + imageResourceData.assign(imageResource->data.begin(), imageResource->data.end()); + ::swapImageDataEndianForConsole(imageResourceData, this->format, this->width, this->height, this->platform); + + // Compression has only been observed in X360 VTFs so far + // todo(vtfpp): go through all PS3 VTFs and check this is correct + if (this->platform == VTF::PLATFORM_X360 && (hasCompression = this->compressionMethod == CompressionMethod::CONSOLE_LZMA)) { + auto fixedCompressionLevel = this->compressionLevel; + if (this->compressionLevel == 0) { + // Compression level defaults to 0, so it works differently on console. + // Rather than not compress on 0, 0 will be replaced with the default + // compression level (6) if the compression method is LZMA. + fixedCompressionLevel = 6; + } + auto compressedData = compression::compressValveLZMA(imageResourceData, fixedCompressionLevel); + if (compressedData) { + imageResourceData.assign(compressedData->begin(), compressedData->end()); + } else { + hasCompression = false; + } + } + } + + const auto resourceStart = writer.tell(); + const auto headerSize = resourceStart + (this->getResources().size() * sizeof(uint64_t)); + writer.seek_u(headerLengthPos).write(headerSize).seek_u(resourceStart); + while (writer.tell() < headerSize) { + writer.write(0); + } + writer.seek_u(resourceStart); + + for (const auto resourceType : Resource::getOrder()) { + if (resourceType == Resource::TYPE_IMAGE_DATA) { + auto curPos = writer.tell(); + const auto imagePos = writer.seek(0, std::ios::end).tell(); + writer.seek_u(preloadPos).write(imagePos).seek_u(curPos); + + writeNonLocalResource(writer, resourceType, imageResourceData, this->platform); + + if (hasCompression) { + curPos = writer.tell(); + writer.seek_u(compressionPos).write(imageResourceData.size()).seek_u(curPos); + } + } else if (const auto* resource = this->getResource(resourceType)) { + std::vector resData{resource->data.begin(), resource->data.end()}; + + // If tweaking this switch, tweak the one in ctor as well + switch (resource->type) { + default: + case Resource::TYPE_UNKNOWN: + case Resource::TYPE_CRC: + case Resource::TYPE_LOD_CONTROL_INFO: + case Resource::TYPE_AUX_COMPRESSION: // Strata-specific + break; + case Resource::TYPE_THUMBNAIL_DATA: + ::swapImageDataEndianForConsole(resData, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, this->platform); + break; + case Resource::TYPE_PARTICLE_SHEET_DATA: + case Resource::TYPE_EXTENDED_FLAGS: + case Resource::TYPE_KEYVALUES_DATA: + if (resData.size() >= sizeof(uint32_t)) { + BufferStream::swap_endian(reinterpret_cast(resData.data())); + } + break; + } + + if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) { + writer.set_big_endian(false); + writer.write((Resource::FLAG_LOCAL_DATA << 24) | resource->type); + writer.set_big_endian(true); + writer.write(resource->data); + } else { + writeNonLocalResource(writer, resource->type, resource->data, this->platform); + } + } + } + + out.resize(writer.size()); + return out; + } + writer << VTF_SIGNATURE << this->majorVersion << this->minorVersion; const auto headerLengthPos = writer.tell(); writer.write(0); @@ -1359,25 +1664,17 @@ std::vector VTF::bake() const { } writer.seek_u(resourceStart); - static constexpr auto writeNonLocalResource = [](BufferStream& writer_, Resource::Type type, std::span data) { - writer_.write(type); - const auto resourceOffsetPos = writer_.tell(); - writer_.seek(0, std::ios::end); - const auto resourceOffsetValue = writer_.tell(); - writer_.write(data); - writer_.seek_u(resourceOffsetPos).write(resourceOffsetValue); - }; for (const auto resourceType : Resource::getOrder()) { if (hasAuxCompression && resourceType == Resource::TYPE_AUX_COMPRESSION) { - writeNonLocalResource(writer, resourceType, auxCompressionResourceData); + writeNonLocalResource(writer, resourceType, auxCompressionResourceData, this->platform); } else if (hasAuxCompression && resourceType == Resource::TYPE_IMAGE_DATA) { - writeNonLocalResource(writer, resourceType, compressedImageResourceData); + writeNonLocalResource(writer, resourceType, compressedImageResourceData, this->platform); } else if (const auto* resource = this->getResource(resourceType)) { if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) { writer.write((Resource::FLAG_LOCAL_DATA << 24) | resource->type); writer.write(resource->data); } else { - writeNonLocalResource(writer, resource->type, resource->data); + writeNonLocalResource(writer, resource->type, resource->data, this->platform); } } } diff --git a/src/vtfpp/_vtfpp.cmake b/src/vtfpp/_vtfpp.cmake index b19f2af41..89e3d49eb 100644 --- a/src/vtfpp/_vtfpp.cmake +++ b/src/vtfpp/_vtfpp.cmake @@ -1,5 +1,5 @@ add_pretty_parser(vtfpp - DEPS miniz libzstd_static sourcepp_parser sourcepp_stb sourcepp_tinyexr + DEPS miniz libzstd_static sourcepp_compression sourcepp_parser sourcepp_stb sourcepp_tinyexr PRECOMPILED_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageConversion.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageFormats.h" From 24483ced5d762893e9d380d0f5aae6ff0c7d6a45 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Tue, 21 Jan 2025 17:38:20 -0500 Subject: [PATCH 3/7] vtfpp: rename create -> compute in VTF::CreationOptions --- include/vtfpp/VTF.h | 6 +++--- lang/python/src/vtfpp.h | 6 +++--- src/vtfpp/VTF.cpp | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/vtfpp/VTF.h b/include/vtfpp/VTF.h index c5c259850..58b63e944 100644 --- a/include/vtfpp/VTF.h +++ b/include/vtfpp/VTF.h @@ -182,9 +182,9 @@ class VTF { bool isCubeMap = false; bool hasSphereMap = false; uint16_t initialSliceCount = 1; - bool createMips = true; - bool createThumbnail = true; - bool createReflectivity = true; + bool computeMips = true; + bool computeThumbnail = true; + bool computeReflectivity = true; Platform platform = PLATFORM_PC; int16_t compressionLevel = -1; CompressionMethod compressionMethod = CompressionMethod::ZSTD; diff --git a/lang/python/src/vtfpp.h b/lang/python/src/vtfpp.h index 575a42abc..80bb00c55 100644 --- a/lang/python/src/vtfpp.h +++ b/lang/python/src/vtfpp.h @@ -388,9 +388,9 @@ void register_python(py::module_& m) { .def_rw("is_cubemap", &VTF::CreationOptions::isCubeMap) .def_rw("has_spheremap", &VTF::CreationOptions::hasSphereMap) .def_rw("initial_slice_count", &VTF::CreationOptions::initialSliceCount) - .def_rw("create_mips", &VTF::CreationOptions::createMips) - .def_rw("create_thumbnail", &VTF::CreationOptions::createThumbnail) - .def_rw("create_reflectivity", &VTF::CreationOptions::createReflectivity) + .def_rw("compute_mips", &VTF::CreationOptions::computeMips) + .def_rw("compute_thumbnail", &VTF::CreationOptions::computeThumbnail) + .def_rw("compute_reflectivity", &VTF::CreationOptions::computeReflectivity) .def_rw("compression_level", &VTF::CreationOptions::compressionLevel) .def_rw("compression_method", &VTF::CreationOptions::compressionMethod) .def_rw("bumpmap_scale", &VTF::CreationOptions::bumpMapScale); diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp index 8eae932e6..0696d0325 100644 --- a/src/vtfpp/VTF.cpp +++ b/src/vtfpp/VTF.cpp @@ -522,10 +522,7 @@ void VTF::createInternal(VTF& writer, CreationOptions options) { } writer.setStartFrame(options.startFrame); writer.setBumpMapScale(options.bumpMapScale); - if (options.createReflectivity) { - writer.computeReflectivity(); - } - if (options.createThumbnail) { + if (options.computeThumbnail) { writer.computeThumbnail(); } if (options.outputFormat == VTF::FORMAT_UNCHANGED) { @@ -533,10 +530,13 @@ void VTF::createInternal(VTF& writer, CreationOptions options) { } else if (options.outputFormat == VTF::FORMAT_DEFAULT) { options.outputFormat = writer.getDefaultFormat(); } - if (options.createMips) { + if (options.computeMips) { writer.setMipCount(ImageDimensions::getRecommendedMipCountForDims(options.outputFormat, writer.getWidth(), writer.getHeight())); writer.computeMips(); } + if (options.computeReflectivity) { + writer.computeReflectivity(); + } writer.setFormat(options.outputFormat); writer.setCompressionLevel(options.compressionLevel); writer.setCompressionMethod(options.compressionMethod); From 77bd5977c5ef968074481c46fda58918aa5bcd95 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Sat, 25 Jan 2025 01:51:56 -0500 Subject: [PATCH 4/7] vtfpp: fix crashing when resource offsets are out of order in the header (rare occurrence in official VTFs) --- src/vtfpp/VTF.cpp | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp index 0696d0325..55cb67014 100644 --- a/src/vtfpp/VTF.cpp +++ b/src/vtfpp/VTF.cpp @@ -220,9 +220,8 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly) const auto headerSize = stream.read(); const auto readResources = [this, &stream](uint32_t resourceCount) { + // Read resource header info this->resources.reserve(resourceCount); - - Resource* lastResource = nullptr; for (int i = 0; i < resourceCount; i++) { auto& [type, flags_, data_] = this->resources.emplace_back(); @@ -235,31 +234,44 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly) flags_ = static_cast(typeAndFlags >> 24); // first byte data_ = stream.read_span(4); - if (!(flags_ & Resource::FLAG_LOCAL_DATA)) { + if (stream.is_big_endian() && !(flags_ & Resource::FLAG_LOCAL_DATA)) { + BufferStream::swap_endian(reinterpret_cast(data_.data())); + } + } + + // Sort resources by their offset, in case certain VTFs are written + // weirdly and have resource data written out of order. So far I have + // found only one VTF in an official Valve game where this is the case + std::sort(this->resources.begin(), this->resources.end(), [](const Resource& lhs, const Resource& rhs) { + if ((lhs.flags & Resource::FLAG_LOCAL_DATA) && (rhs.flags & Resource::FLAG_LOCAL_DATA)) { + return lhs.type < rhs.type; + } + if ((lhs.flags & Resource::FLAG_LOCAL_DATA) && !(rhs.flags & Resource::FLAG_LOCAL_DATA)) { + return true; + } + if (!(lhs.flags & Resource::FLAG_LOCAL_DATA) && (rhs.flags & Resource::FLAG_LOCAL_DATA)) { + return false; + } + return *reinterpret_cast(lhs.data.data()) < *reinterpret_cast(rhs.data.data()); + }); + + // Fix up data spans to point to the actual data + Resource* lastResource = nullptr; + for (auto& resource : this->resources) { + if (!(resource.flags & Resource::FLAG_LOCAL_DATA)) { if (lastResource) { auto lastOffset = *reinterpret_cast(lastResource->data.data()); - auto currentOffset = *reinterpret_cast(data_.data()); - if (stream.is_big_endian()) { - // Data is still big-endian - BufferStream::swap_endian(&lastOffset); - BufferStream::swap_endian(¤tOffset); - } - + auto currentOffset = *reinterpret_cast(resource.data.data()); auto curPos = stream.tell(); stream.seek(lastOffset); lastResource->data = stream.read_span(currentOffset - lastOffset); stream.seek(static_cast(curPos)); } - lastResource = &this->resources.back(); + lastResource = &resource; } } if (lastResource) { auto offset = *reinterpret_cast(lastResource->data.data()); - if (stream.is_big_endian()) { - // Data is still big-endian - BufferStream::swap_endian(&offset); - } - auto curPos = stream.tell(); stream.seek(offset); lastResource->data = stream.read_span(stream.size() - offset); From 4fc936ebe7b22c740ce94b817bc8f5df93929a39 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Sun, 26 Jan 2025 18:27:32 -0500 Subject: [PATCH 5/7] vtfpp: use correct mip count for console VTFs --- src/vtfpp/VTF.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp index 55cb67014..a6ea1767d 100644 --- a/src/vtfpp/VTF.cpp +++ b/src/vtfpp/VTF.cpp @@ -529,6 +529,7 @@ ImageFormat VTF::getDefaultFormat() const { } void VTF::createInternal(VTF& writer, CreationOptions options) { + writer.setPlatform(options.platform); if (options.initialFrameCount > 1 || options.isCubeMap || options.initialSliceCount > 1) { writer.setFrameFaceAndSliceCount(options.initialFrameCount, options.isCubeMap, options.hasSphereMap, options.initialSliceCount); } @@ -552,7 +553,6 @@ void VTF::createInternal(VTF& writer, CreationOptions options) { writer.setFormat(options.outputFormat); writer.setCompressionLevel(options.compressionLevel); writer.setCompressionMethod(options.compressionMethod); - writer.setPlatform(options.platform); } void VTF::create(std::span imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, CreationOptions options) { @@ -614,6 +614,11 @@ VTF::Platform VTF::getPlatform() const { void VTF::setPlatform(Platform newPlatform) { if (this->platform == PLATFORM_X360 || this->platform == PLATFORM_PS3) { this->setVersion(7, 4); + + const auto recommendedCount = (this->flags & VTF::FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height); + if (this->mipCount != recommendedCount) { + this->setMipCount(recommendedCount); + } } this->platform = newPlatform; this->setCompressionMethod(this->compressionMethod); @@ -697,8 +702,12 @@ void VTF::setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::Resize return; } auto newMipCount = this->mipCount; - if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(this->format, newWidth, newHeight); newMipCount > recommendedCount) { - newMipCount = recommendedCount; + if (this->platform == VTF::PLATFORM_PC) { + if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(this->format, newWidth, newHeight); newMipCount > recommendedCount) { + newMipCount = recommendedCount; + } + } else { + newMipCount = (this->flags & VTF::FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(newWidth, newHeight); } this->regenerateImageData(this->format, newWidth, newHeight, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter); } else { @@ -779,7 +788,10 @@ bool VTF::setMipCount(uint8_t newMipCount) { } bool VTF::setRecommendedMipCount() { - return this->setMipCount(ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height)); + if (this->platform == VTF::PLATFORM_PC) { + return this->setMipCount(ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height)); + } + return this->setMipCount(ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height)); } void VTF::computeMips(ImageConversion::ResizeFilter filter) { From 6458b68fdaae93e1d406b0fe7dd9927a4dd928b8 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Sun, 26 Jan 2025 19:01:18 -0500 Subject: [PATCH 6/7] vtfpp: add ps3/x360 read tests --- test/vtfpp.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/vtfpp.cpp b/test/vtfpp.cpp index 425be5391..626b63e4f 100644 --- a/test/vtfpp.cpp +++ b/test/vtfpp.cpp @@ -812,6 +812,72 @@ TEST(vtfpp, read_v75_nothumb_nomip) { EXPECT_EQ(image->data.size(), ImageFormatDetails::getDataLength(vtf.getFormat(), vtf.getMipCount(), vtf.getFrameCount(), vtf.getFaceCount(), vtf.getWidth(), vtf.getHeight(), vtf.getSliceCount())); } +TEST(vtfpp, read_ps3) { + VTF vtf{fs::readFileBuffer(ASSET_ROOT "vtfpp/ps3/portal.vtf")}; + ASSERT_TRUE(vtf); + + // Header + EXPECT_EQ(vtf.getMajorVersion(), 7); + EXPECT_EQ(vtf.getMinorVersion(), 4); + EXPECT_EQ(vtf.getWidth(), 1024); + EXPECT_EQ(vtf.getHeight(), 1024); + EXPECT_EQ(vtf.getFlags(), VTF::FLAG_NO_MIP | VTF::FLAG_NO_LOD); + EXPECT_EQ(vtf.getFormat(), ImageFormat::DXT1); + EXPECT_EQ(vtf.getMipCount(), 1); + EXPECT_EQ(vtf.getFrameCount(), 1); + EXPECT_EQ(vtf.getFaceCount(), 1); + EXPECT_EQ(vtf.getSliceCount(), 1); + EXPECT_EQ(vtf.getStartFrame(), 0); + EXPECT_FLOAT_EQ(vtf.getReflectivity()[0], 0.037193343f); + EXPECT_FLOAT_EQ(vtf.getReflectivity()[1], 0.020529008f); + EXPECT_FLOAT_EQ(vtf.getReflectivity()[2], 0.016482241f); + EXPECT_FLOAT_EQ(vtf.getBumpMapScale(), 1.f); + EXPECT_EQ(vtf.getThumbnailFormat(), ImageFormat::EMPTY); + EXPECT_EQ(vtf.getThumbnailWidth(), 0); + EXPECT_EQ(vtf.getThumbnailHeight(), 0); + + // Resources + EXPECT_EQ(vtf.getResources().size(), 1); + + const auto* image = vtf.getResource(Resource::TYPE_IMAGE_DATA); + ASSERT_TRUE(image); + EXPECT_EQ(image->flags, Resource::FLAG_NONE); + EXPECT_EQ(image->data.size(), ImageFormatDetails::getDataLength(vtf.getFormat(), vtf.getMipCount(), vtf.getFrameCount(), vtf.getFaceCount(), vtf.getWidth(), vtf.getHeight(), vtf.getSliceCount())); +} + +TEST(vtfpp, read_x360) { + VTF vtf{fs::readFileBuffer(ASSET_ROOT "vtfpp/x360/metalwall048b.360.vtf")}; + ASSERT_TRUE(vtf); + + // Header + EXPECT_EQ(vtf.getMajorVersion(), 7); + EXPECT_EQ(vtf.getMinorVersion(), 4); + EXPECT_EQ(vtf.getWidth(), 512); + EXPECT_EQ(vtf.getHeight(), 512); + EXPECT_EQ(vtf.getFlags(), VTF::FLAG_SRGB | VTF::FLAG_MULTI_BIT_ALPHA); + EXPECT_EQ(vtf.getFormat(), ImageFormat::DXT5); + EXPECT_EQ(vtf.getMipCount(), 10); + EXPECT_EQ(vtf.getFrameCount(), 1); + EXPECT_EQ(vtf.getFaceCount(), 1); + EXPECT_EQ(vtf.getSliceCount(), 1); + EXPECT_EQ(vtf.getStartFrame(), 0); + EXPECT_FLOAT_EQ(vtf.getReflectivity()[0], 0.0389036387f); + EXPECT_FLOAT_EQ(vtf.getReflectivity()[1], 0.0300185774f); + EXPECT_FLOAT_EQ(vtf.getReflectivity()[2], 0.0235184804f); + EXPECT_FLOAT_EQ(vtf.getBumpMapScale(), 1.f); + EXPECT_EQ(vtf.getThumbnailFormat(), ImageFormat::EMPTY); + EXPECT_EQ(vtf.getThumbnailWidth(), 0); + EXPECT_EQ(vtf.getThumbnailHeight(), 0); + + // Resources + EXPECT_EQ(vtf.getResources().size(), 1); + + const auto* image = vtf.getResource(Resource::TYPE_IMAGE_DATA); + ASSERT_TRUE(image); + EXPECT_EQ(image->flags, Resource::FLAG_NONE); + EXPECT_EQ(image->data.size(), ImageFormatDetails::getDataLength(vtf.getFormat(), vtf.getMipCount(), vtf.getFrameCount(), vtf.getFaceCount(), vtf.getWidth(), vtf.getHeight(), vtf.getSliceCount())); +} + TEST(vtfpp, read_v76_c9) { VTF vtf{fs::readFileBuffer(ASSET_ROOT "vtfpp/ver/v76_c9.vtf")}; ASSERT_TRUE(vtf); From ca995c0bbd44c19fe95a0479e13736c0a7d01237 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Sun, 26 Jan 2025 19:05:27 -0500 Subject: [PATCH 7/7] docs: document vtfpp VTFX support --- README.md | 12 ++++++++++-- docs/index.md | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90ad2ba1c..f4e516965 100644 --- a/README.md +++ b/README.md @@ -237,11 +237,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one - vtfpp + vtfpp APNG ✅ ❌ - Python + Python @@ -324,6 +324,14 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ✅ ✅ + + + + VTFX (X360, PS3) + + ✅ + ✅ + (\*) These libraries are incomplete and still in development. Their interfaces are unstable and will likely change in the future. diff --git a/docs/index.md b/docs/index.md index 76d35d51a..a04089eba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -207,11 +207,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ✅ - vtfpp + vtfpp APNG ✅ ❌ - Python + Python BMP @@ -281,6 +281,13 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ✅ ✅ + + + VTFX (X360, PS3) + + ✅ + ✅ + \endhtmlonly