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
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..58b63e944 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;
@@ -170,9 +182,10 @@ 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;
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..80bb00c55 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)
@@ -381,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);
@@ -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/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);
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..a6ea1767d 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,166 @@ 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) {
+ // Read resource header info
+ this->resources.reserve(resourceCount);
+ 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 (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(resource.data.data());
+ auto curPos = stream.tell();
+ stream.seek(lastOffset);
+ lastResource->data = stream.read_span(currentOffset - lastOffset);
+ stream.seek(static_cast(curPos));
+ }
+ lastResource = &resource;
+ }
+ }
+ 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));
+ }
+ };
+
+ 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 +358,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 +390,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 +423,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 +503,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;
@@ -354,15 +529,13 @@ 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);
}
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) {
@@ -370,10 +543,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);
@@ -431,6 +607,23 @@ 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);
+
+ 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);
+}
+
uint32_t VTF::getMajorVersion() const {
return this->majorVersion;
}
@@ -445,10 +638,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)) {
@@ -503,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 {
@@ -585,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) {
@@ -679,16 +885,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 +1035,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 +1074,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 +1285,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 +1474,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 +1688,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"
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);