From 5a002a5e8dcb8bb846ac33bc1f00caa0f21cb95b Mon Sep 17 00:00:00 2001 From: AWildErin Date: Thu, 3 Apr 2025 02:57:18 +0100 Subject: [PATCH 1/4] feat(lang): Add base vtfpp test project --- lang/csharp/test/vtfpp.test/vtfpp.test.csproj | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/csharp/test/vtfpp.test/vtfpp.test.csproj diff --git a/lang/csharp/test/vtfpp.test/vtfpp.test.csproj b/lang/csharp/test/vtfpp.test/vtfpp.test.csproj new file mode 100644 index 00000000..b606c730 --- /dev/null +++ b/lang/csharp/test/vtfpp.test/vtfpp.test.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + True + True + True + + + + + + + + + + + + + + + + + + From 4f433d430ec8fd2c198a8a1417e1680adc9149d8 Mon Sep 17 00:00:00 2001 From: AWildErin Date: Thu, 10 Apr 2025 18:13:51 +0100 Subject: [PATCH 2/4] fix(cmake): Add quick VS hack to `AddSourceppLibrary` --- cmake/AddSourcePPLibrary.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmake/AddSourcePPLibrary.cmake b/cmake/AddSourcePPLibrary.cmake index 39bdfe2b..56bc4c99 100644 --- a/cmake/AddSourcePPLibrary.cmake +++ b/cmake/AddSourcePPLibrary.cmake @@ -19,7 +19,14 @@ function(add_sourcepp_library TARGET) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/lang/csharp/src/sourcepp/TARGET.csproj.in" "${CMAKE_CURRENT_SOURCE_DIR}/lang/csharp/src/${TARGET}/${TARGET}.csproj") add_custom_target(sourcepp_${TARGET}_csharp DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/lang/csharp/src/${TARGET}/${TARGET}.csproj") add_dependencies(sourcepp_${TARGET}_csharp sourcepp::${TARGET}c) - add_custom_command(TARGET sourcepp::${TARGET}c POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/sourcepp_${TARGET}c${CMAKE_SHARED_LIBRARY_SUFFIX}" "${CMAKE_CURRENT_SOURCE_DIR}/lang/csharp/src/${TARGET}") + + # Quick hack to let tell VS to place the dlls in the right spot + # Can be removed/dropped if needed + if(CMAKE_GENERATOR MATCHES "Visual Studio") + add_custom_command(TARGET sourcepp::${TARGET}c POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/sourcepp_${TARGET}c${CMAKE_SHARED_LIBRARY_SUFFIX}" "${CMAKE_CURRENT_SOURCE_DIR}/lang/csharp/src/${TARGET}") + else() + add_custom_command(TARGET sourcepp::${TARGET}c POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/sourcepp_${TARGET}c${CMAKE_SHARED_LIBRARY_SUFFIX}" "${CMAKE_CURRENT_SOURCE_DIR}/lang/csharp/src/${TARGET}") + endif() endif() # Add Python From ed5e3c8afe2ef9cdf656378c2eb841fa36444c9a Mon Sep 17 00:00:00 2001 From: AWildErin Date: Thu, 10 Apr 2025 18:14:49 +0100 Subject: [PATCH 3/4] feat(lang): Add initial C# bindings for `ImageFormats` from VPKPP --- lang/csharp/src/vtfpp/ImageFormats.cs | 318 ++++++++++++++++++ .../test/vtfpp.test/ImageFormatDetailsTest.cs | 229 +++++++++++++ 2 files changed, 547 insertions(+) create mode 100644 lang/csharp/src/vtfpp/ImageFormats.cs create mode 100644 lang/csharp/test/vtfpp.test/ImageFormatDetailsTest.cs diff --git a/lang/csharp/src/vtfpp/ImageFormats.cs b/lang/csharp/src/vtfpp/ImageFormats.cs new file mode 100644 index 00000000..c52c220a --- /dev/null +++ b/lang/csharp/src/vtfpp/ImageFormats.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace vtfpp +{ + public enum ImageFormat + { + RGBA8888 = 0, + ABGR8888, + RGB888, + BGR888, + RGB565, + I8, + IA88, + P8, + A8, + RGB888_BLUESCREEN, + BGR888_BLUESCREEN, + ARGB8888, + BGRA8888, + DXT1, + DXT3, + DXT5, + BGRX8888, + BGR565, + BGRX5551, + BGRA4444, + DXT1_ONE_BIT_ALPHA, + BGRA5551, + UV88, + UVWQ8888, + RGBA16161616F, + RGBA16161616, + UVLX8888, + R32F, + RGB323232F, + RGBA32323232F, + RG1616F, + RG3232F, + RGBX8888, + EMPTY, + ATI2N, + ATI1N, + RGBA1010102, + BGRA1010102, + R16F, + + 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, + + R8 = 69, + BC7, + BC6H, + } + + + internal static unsafe partial class Extern + { + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_red")] + public static partial sbyte ImageFormatDetailsRed(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_decompressed_red")] + public static partial sbyte ImageFormatDetailsDecompressedRed(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_green")] + public static partial sbyte ImageFormatDetailsGreen(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_decompressed_green")] + public static partial sbyte ImageFormatDetailsDecompressedGreen(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_blue")] + public static partial sbyte ImageFormatDetailsBlue(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_decompressed_blue")] + public static partial sbyte ImageFormatDetailsDecompressedBlue(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_alpha")] + public static partial sbyte ImageFormatDetailsAlpha(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_decompressed_alpha")] + public static partial sbyte ImageFormatDetailsDecompressedAlpha(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_bpp")] + public static partial byte ImageFormatDetailsBPP(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_container_format")] + public static partial ImageFormat ImageFormatDetailsContainerFormat(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_large")] + public static partial int ImageFormatDetailsLarge(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_decimal")] + public static partial int ImageFormatDetailsDecimal(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_compressed")] + public static partial int ImageFormatDetailsCompressed(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_transparent")] + public static partial int ImageFormatDetailsTransparent(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_opaque")] + public static partial int ImageFormatDetailsOpaque(ImageFormat format); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_dimensions_get_mip_dim")] + public static partial uint ImageDimensionsGetMipDim(byte mip, ushort dim); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_dimensions_get_recommended_mip_count_for_dim")] + public static partial byte ImageDimensionsGetRecommendedMipCountForDim(ImageFormat format, ushort width, ushort height); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_dimensions_get_actual_mip_count_for_dims_on_console")] + public static partial byte ImageDimensionsGetActualMipCountForDimsOnConsole(ushort width, ushort height); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_get_data_length")] + public static partial uint ImageFormatDetailsGetDataLength(ImageFormat format, ushort width, ushort height, ushort sliceCount); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_get_data_length_ex")] + public static partial uint ImageFormatDetailsGetDataLengthEx(ImageFormat format, byte mipCount, ushort frameCount, byte faceCount, ushort width, ushort height, ushort sliceCount); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_image_format_details_get_data_position")] + public static partial int ImageFormatGetDataPosition(uint* offset, uint* length, ImageFormat format, byte mip, byte mipCount, ushort frame, ushort frameCount, byte face, byte faceCount, ushort width, ushort height, ushort slice, ushort sliceCount); + } + + public class ImageFormatDetails + { + public static sbyte Red(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsRed(format); + } + } + + public static sbyte DecompressedRed(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsDecompressedRed(format); + } + } + + public static sbyte Green(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsGreen(format); + } + } + + public static sbyte DecompressedGreen(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsDecompressedGreen(format); + } + } + + public static sbyte Blue(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsBlue(format); + } + } + + public static sbyte DecompressedBlue(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsDecompressedBlue(format); + } + } + + public static sbyte Alpha(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsAlpha(format); + } + } + + public static sbyte DecompressedAlpha(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsDecompressedAlpha(format); + } + } + + public static byte BPP(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsBPP(format); + } + } + + public static ImageFormat ContainerFormat(ImageFormat format) + { + unsafe + { + return Extern.ImageFormatDetailsContainerFormat(format); + } + } + + public static bool Large(ImageFormat format) + { + unsafe + { + return Convert.ToBoolean(Extern.ImageFormatDetailsLarge(format)); + } + } + + public static bool Decimal(ImageFormat format) + { + unsafe + { + return Convert.ToBoolean(Extern.ImageFormatDetailsDecimal(format)); + } + } + + public static bool Compressed(ImageFormat format) + { + unsafe + { + return Convert.ToBoolean(Extern.ImageFormatDetailsCompressed(format)); + } + } + + public static bool Transparent(ImageFormat format) + { + unsafe + { + return Convert.ToBoolean(Extern.ImageFormatDetailsTransparent(format)); + } + } + + public static bool Opaque(ImageFormat format) + { + unsafe + { + return Convert.ToBoolean(Extern.ImageFormatDetailsOpaque(format)); + } + } + + public static uint GetDataLength(ImageFormat format, ushort width, ushort height, ushort sliceCount) + { + unsafe + { + return Extern.ImageFormatDetailsGetDataLength(format, width, height, sliceCount); + } + } + + public static uint GetDataLength(ImageFormat format, byte mipCount, ushort frameCount, byte faceCount, ushort width, ushort height, ushort sliceCount) + { + unsafe + { + return Extern.ImageFormatDetailsGetDataLengthEx(format, mipCount, frameCount, faceCount, width, height, sliceCount); + } + } + + public static bool GetDataPosition(ref uint offset, ref uint length, ImageFormat format, byte mip, byte mipCount, ushort frame, byte face, byte faceCount, ushort frameCount, ushort width, ushort height, ushort slice = 0, ushort sliceCount = 1) + { + unsafe + { + /// @todo Is there a better way for this? + fixed (uint* offsetPtr = &offset) + { + fixed (uint* lengthPtr = &length) + { + return Convert.ToBoolean(Extern.ImageFormatGetDataPosition(offsetPtr, lengthPtr, format, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)); + } + } + } + } + } + + class ImageDimensions + { + public static uint GetMipDim(byte mip, ushort dim) + { + unsafe + { + return Extern.ImageDimensionsGetMipDim(mip, dim); + } + } + + public static byte GetRecommendedMipCountForDim(ImageFormat format, ushort width, ushort height) + { + unsafe + { + return Extern.ImageDimensionsGetRecommendedMipCountForDim(format, width, height); + } + } + + public static byte GetActualMipCountForDimsOnConsole(ushort width, ushort height) + { + unsafe + { + return Extern.ImageDimensionsGetActualMipCountForDimsOnConsole(width, height); + } + } + + } +} diff --git a/lang/csharp/test/vtfpp.test/ImageFormatDetailsTest.cs b/lang/csharp/test/vtfpp.test/ImageFormatDetailsTest.cs new file mode 100644 index 00000000..f5f99cf5 --- /dev/null +++ b/lang/csharp/test/vtfpp.test/ImageFormatDetailsTest.cs @@ -0,0 +1,229 @@ +namespace vtfpp.test +{ + [TestClass] + public class ImageFormatDetailsTest + { + [TestMethod] + public void Red() + { + foreach (var dataTuple in RedData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.Red(format)); + } + } + } + + + [TestMethod] + public void DecompressedRed() + { + foreach (var dataTuple in DecompressedRedData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.DecompressedRed(format)); + } + } + } + + [TestMethod] + public void Green() + { + foreach (var dataTuple in GreenData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.Green(format)); + } + } + } + + + [TestMethod] + public void DecompressedGreen() + { + foreach (var dataTuple in DecompressedGreenData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.DecompressedGreen(format)); + } + } + } + + [TestMethod] + public void Blue() + { + foreach (var dataTuple in BlueData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.Blue(format)); + } + } + } + + + [TestMethod] + public void DecompressedBlue() + { + foreach (var dataTuple in DecompressedBlueData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.DecompressedBlue(format)); + } + } + } + + [TestMethod] + public void Alpha() + { + foreach (var dataTuple in AlphaData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.Alpha(format)); + } + } + } + + + [TestMethod] + public void DecompressedAlpha() + { + foreach (var dataTuple in DecompressedAlphaData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.DecompressedAlpha(format)); + } + } + } + + [TestMethod] + public void BPP() + { + foreach (var dataTuple in BPPData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.BPP(format)); + } + } + } + + [TestMethod] + public void ContainerFormat() + { + foreach (var dataTuple in ContainerFormatData) + { + foreach (var format in dataTuple.Formats) + { + Assert.AreEqual(dataTuple.ExpectedValue, ImageFormatDetails.ContainerFormat(format)); + } + } + } + + // EXPECTED DATA + private readonly List<(List Formats, sbyte ExpectedValue)> RedData = new() + { + (new List { ImageFormat.R32F, ImageFormat.RG3232F, ImageFormat.RGB323232F, ImageFormat.RGBA32323232F }, 32), + (new List { ImageFormat.R16F, ImageFormat.RG1616F, ImageFormat.RGBA16161616F, ImageFormat.RGBA16161616, ImageFormat.CONSOLE_RGBA16161616_LINEAR }, 16), + (new List { ImageFormat.RGBA1010102, ImageFormat.BGRA1010102 }, 10), + (new List { ImageFormat.RGBA8888, ImageFormat.CONSOLE_RGBA8888_LINEAR, ImageFormat.ABGR8888, ImageFormat.CONSOLE_ABGR8888_LINEAR, ImageFormat.RGB888, ImageFormat.CONSOLE_RGB888_LINEAR, ImageFormat.BGR888, ImageFormat.CONSOLE_BGR888_LINEAR, ImageFormat.I8, ImageFormat.CONSOLE_I8_LINEAR, ImageFormat.IA88, ImageFormat.P8, ImageFormat.RGB888_BLUESCREEN, ImageFormat.BGR888_BLUESCREEN, ImageFormat.ARGB8888, ImageFormat.CONSOLE_ARGB8888_LINEAR, ImageFormat.BGRA8888, ImageFormat.CONSOLE_BGRA8888_LINEAR, ImageFormat.CONSOLE_BGRA8888_LE, ImageFormat.BGRX8888, ImageFormat.CONSOLE_BGRX8888_LINEAR, ImageFormat.CONSOLE_BGRX8888_LE, ImageFormat.UV88, ImageFormat.UVWQ8888, ImageFormat.UVLX8888, ImageFormat.RGBX8888, ImageFormat.R8 }, 8), + (new List { ImageFormat.RGB565, ImageFormat.BGR565, ImageFormat.BGRX5551, ImageFormat.CONSOLE_BGRX5551_LINEAR, ImageFormat.BGRA5551 }, 5), + (new List { ImageFormat.BGRA4444 }, 4), + (new List { ImageFormat.A8, ImageFormat.EMPTY }, 0), + (new List { ImageFormat.DXT1, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7, ImageFormat.BC6H }, -1) + }; + + private readonly List<(List Formats, sbyte ExpectedValue)> DecompressedRedData = new() + { + (new List { ImageFormat.DXT1, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7 }, 8 ), + (new List { ImageFormat.BC6H }, 16), + }; + + + private readonly List<(List Formats, sbyte ExpectedValue)> GreenData = new() + { + (new List { ImageFormat.RG3232F, ImageFormat.RGB323232F, ImageFormat.RGBA32323232F, }, 32), + (new List { ImageFormat.RG1616F, ImageFormat.RGBA16161616F, ImageFormat.RGBA16161616, ImageFormat.CONSOLE_RGBA16161616_LINEAR, }, 16), + (new List { ImageFormat.RGBA1010102, ImageFormat.BGRA1010102, }, 10), + (new List { ImageFormat.RGBA8888, ImageFormat.CONSOLE_RGBA8888_LINEAR, ImageFormat.ABGR8888, ImageFormat.CONSOLE_ABGR8888_LINEAR, ImageFormat.RGB888, ImageFormat.CONSOLE_RGB888_LINEAR, ImageFormat.BGR888, ImageFormat.CONSOLE_BGR888_LINEAR, ImageFormat.RGB888_BLUESCREEN, ImageFormat.BGR888_BLUESCREEN, ImageFormat.ARGB8888, ImageFormat.CONSOLE_ARGB8888_LINEAR, ImageFormat.BGRA8888, ImageFormat.CONSOLE_BGRA8888_LINEAR, ImageFormat.CONSOLE_BGRA8888_LE, ImageFormat.BGRX8888, ImageFormat.CONSOLE_BGRX8888_LINEAR, ImageFormat.CONSOLE_BGRX8888_LE, ImageFormat.UV88, ImageFormat.UVWQ8888, ImageFormat.UVLX8888, ImageFormat.RGBX8888, }, 8), + (new List { ImageFormat.RGB565, ImageFormat.BGR565, }, 6), + (new List { ImageFormat.BGRX5551, ImageFormat.CONSOLE_BGRX5551_LINEAR, ImageFormat.BGRA5551, }, 5), + (new List { ImageFormat.BGRA4444, }, 4), + (new List { ImageFormat.I8, ImageFormat.CONSOLE_I8_LINEAR, ImageFormat.IA88, ImageFormat.P8, ImageFormat.R32F, ImageFormat.A8, ImageFormat.EMPTY, ImageFormat.R16F, ImageFormat.R8, }, 0), + (new List { ImageFormat.DXT1, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7, ImageFormat.BC6H, }, -1), + }; + + + private readonly List<(List Formats, sbyte ExpectedValue)> DecompressedGreenData = new() + { + (new List { ImageFormat.DXT1, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7, }, 8), + (new List { ImageFormat.BC6H, }, 16), + }; + + private readonly List<(List Formats, sbyte ExpectedValue)> BlueData = new() + { + (new List { ImageFormat.RGB323232F, ImageFormat.RGBA32323232F, }, 32), + (new List { ImageFormat.RGBA16161616F, ImageFormat.RGBA16161616, ImageFormat.CONSOLE_RGBA16161616_LINEAR, }, 16), + (new List { ImageFormat.RGBA1010102, ImageFormat.BGRA1010102, }, 10), + (new List { ImageFormat.RGBA8888, ImageFormat.CONSOLE_RGBA8888_LINEAR, ImageFormat.ABGR8888, ImageFormat.CONSOLE_ABGR8888_LINEAR, ImageFormat.RGB888, ImageFormat.CONSOLE_RGB888_LINEAR, ImageFormat.BGR888, ImageFormat.CONSOLE_BGR888_LINEAR, ImageFormat.RGB888_BLUESCREEN, ImageFormat.BGR888_BLUESCREEN, ImageFormat.ARGB8888, ImageFormat.CONSOLE_ARGB8888_LINEAR, ImageFormat.BGRA8888, ImageFormat.CONSOLE_BGRA8888_LINEAR, ImageFormat.CONSOLE_BGRA8888_LE, ImageFormat.BGRX8888, ImageFormat.CONSOLE_BGRX8888_LINEAR, ImageFormat.CONSOLE_BGRX8888_LE, ImageFormat.UVWQ8888, ImageFormat.UVLX8888, ImageFormat.RGBX8888, }, 8), + (new List { ImageFormat.RGB565, ImageFormat.BGR565, ImageFormat.BGRX5551, ImageFormat.CONSOLE_BGRX5551_LINEAR, ImageFormat.BGRA5551, }, 5), + (new List { ImageFormat.BGRA4444, }, 4), + (new List { ImageFormat.I8, ImageFormat.CONSOLE_I8_LINEAR, ImageFormat.IA88, ImageFormat.P8, ImageFormat.UV88, ImageFormat.R32F, ImageFormat.A8, ImageFormat.EMPTY, ImageFormat.RG3232F, ImageFormat.RG1616F, ImageFormat.R16F, ImageFormat.R8, }, 0), + (new List { ImageFormat.DXT1, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7, ImageFormat.BC6H, }, -1), + }; + + private readonly List<(List Formats, sbyte ExpectedValue)> DecompressedBlueData = new() + { + (new List { ImageFormat.DXT1, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7, }, 8), + (new List { ImageFormat.BC6H, }, 16), + }; + + private readonly List<(List Formats, sbyte ExpectedValue)> AlphaData = new() + { + (new List { ImageFormat.RGBA32323232F, }, 32), + (new List { ImageFormat.RGBA16161616F, ImageFormat.RGBA16161616, ImageFormat.CONSOLE_RGBA16161616_LINEAR, }, 16), + (new List { ImageFormat.RGBA8888, ImageFormat.CONSOLE_RGBA8888_LINEAR, ImageFormat.ABGR8888, ImageFormat.CONSOLE_ABGR8888_LINEAR, ImageFormat.IA88, ImageFormat.ARGB8888, ImageFormat.CONSOLE_ARGB8888_LINEAR, ImageFormat.BGRA8888, ImageFormat.CONSOLE_BGRA8888_LINEAR, ImageFormat.CONSOLE_BGRA8888_LE, ImageFormat.BGRX8888, ImageFormat.CONSOLE_BGRX8888_LINEAR, ImageFormat.CONSOLE_BGRX8888_LE, ImageFormat.UVWQ8888, ImageFormat.UVLX8888, ImageFormat.RGBX8888, }, 8), + (new List { ImageFormat.BGRA4444, }, 4), + (new List { ImageFormat.RGBA1010102, ImageFormat.BGRA1010102, }, 2), + (new List { ImageFormat.BGRX5551, ImageFormat.CONSOLE_BGRX5551_LINEAR, ImageFormat.BGRA5551, }, 1), + (new List { ImageFormat.RGB888, ImageFormat.CONSOLE_RGB888_LINEAR, ImageFormat.BGR888, ImageFormat.CONSOLE_BGR888_LINEAR, ImageFormat.P8, ImageFormat.I8, ImageFormat.CONSOLE_I8_LINEAR, ImageFormat.RGB888_BLUESCREEN, ImageFormat.BGR888_BLUESCREEN, ImageFormat.UV88, ImageFormat.RGB565, ImageFormat.BGR565, ImageFormat.R32F, ImageFormat.RGB323232F, ImageFormat.A8, ImageFormat.EMPTY, ImageFormat.RG3232F, ImageFormat.RG1616F, ImageFormat.R16F, ImageFormat.R8, }, 0), + (new List { ImageFormat.DXT1, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC7, ImageFormat.BC6H, }, -1), + }; + + private readonly List<(List Formats, sbyte ExpectedValue)> DecompressedAlphaData = new() + { + (new List { ImageFormat.DXT5, ImageFormat.BC7, }, 8), + (new List { ImageFormat.DXT3, }, 4), + (new List { ImageFormat.DXT1_ONE_BIT_ALPHA, }, 1), + (new List { ImageFormat.DXT1, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.BC6H, }, 0), + }; + + private readonly List<(List Formats, byte ExpectedValue)> BPPData = new() + { + (new List { ImageFormat.RGBA32323232F, }, 128), + (new List { ImageFormat.RGB323232F, }, 96), + (new List { ImageFormat.RGBA16161616F, ImageFormat.RGBA16161616, ImageFormat.CONSOLE_RGBA16161616_LINEAR, ImageFormat.RG3232F, }, 64), + (new List { ImageFormat.RGBA8888, ImageFormat.CONSOLE_RGBA8888_LINEAR, ImageFormat.ABGR8888, ImageFormat.CONSOLE_ABGR8888_LINEAR, ImageFormat.ARGB8888, ImageFormat.CONSOLE_ARGB8888_LINEAR, ImageFormat.BGRA8888, ImageFormat.CONSOLE_BGRA8888_LINEAR, ImageFormat.CONSOLE_BGRA8888_LE, ImageFormat.BGRX8888, ImageFormat.CONSOLE_BGRX8888_LINEAR, ImageFormat.CONSOLE_BGRX8888_LE, ImageFormat.UVLX8888, ImageFormat.R32F, ImageFormat.UVWQ8888, ImageFormat.RGBX8888, ImageFormat.RGBA1010102, ImageFormat.BGRA1010102, ImageFormat.RG1616F, }, 32), + (new List { ImageFormat.RGB888, ImageFormat.CONSOLE_RGB888_LINEAR, ImageFormat.BGR888, ImageFormat.CONSOLE_BGR888_LINEAR, ImageFormat.RGB888_BLUESCREEN, ImageFormat.BGR888_BLUESCREEN, }, 24), + (new List { ImageFormat.RGB565, ImageFormat.BGR565, ImageFormat.IA88, ImageFormat.BGRX5551, ImageFormat.CONSOLE_BGRX5551_LINEAR, ImageFormat.BGRA4444, ImageFormat.BGRA5551, ImageFormat.UV88, ImageFormat.R16F, }, 16), + (new List { ImageFormat.I8, ImageFormat.CONSOLE_I8_LINEAR, ImageFormat.P8, ImageFormat.A8, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.BC7, ImageFormat.BC6H, ImageFormat.ATI2N, ImageFormat.R8, }, 8), + (new List { ImageFormat.ATI1N, ImageFormat.DXT1, ImageFormat.DXT1_ONE_BIT_ALPHA, }, 4), + (new List { ImageFormat.EMPTY, }, 0), + }; + + private readonly List<(List Formats, ImageFormat ExpectedValue)> ContainerFormatData = new() + { + (new List { ImageFormat.R32F, ImageFormat.RG3232F, ImageFormat.RGB323232F, ImageFormat.R16F, ImageFormat.RG1616F, ImageFormat.RGBA16161616F, ImageFormat.RGBA32323232F, ImageFormat.BC6H, }, ImageFormat.RGBA32323232F), + (new List { ImageFormat.RGBA16161616, ImageFormat.CONSOLE_RGBA16161616_LINEAR, ImageFormat.RGBA1010102, ImageFormat.BGRA1010102, }, ImageFormat.RGBA16161616), + (new List { ImageFormat.RGBA8888, ImageFormat.CONSOLE_RGBA8888_LINEAR, ImageFormat.ABGR8888, ImageFormat.CONSOLE_ABGR8888_LINEAR, ImageFormat.RGB888, ImageFormat.CONSOLE_RGB888_LINEAR, ImageFormat.BGR888, ImageFormat.CONSOLE_BGR888_LINEAR, ImageFormat.RGB888_BLUESCREEN, ImageFormat.BGR888_BLUESCREEN, ImageFormat.ARGB8888, ImageFormat.CONSOLE_ARGB8888_LINEAR, ImageFormat.BGRA8888, ImageFormat.CONSOLE_BGRA8888_LINEAR, ImageFormat.CONSOLE_BGRA8888_LE, ImageFormat.BGRX8888, ImageFormat.CONSOLE_BGRX8888_LINEAR, ImageFormat.CONSOLE_BGRX8888_LE, ImageFormat.UVWQ8888, ImageFormat.UVLX8888, ImageFormat.RGB565, ImageFormat.BGR565, ImageFormat.BGRX5551, ImageFormat.CONSOLE_BGRX5551_LINEAR, ImageFormat.BGRA5551, ImageFormat.BGRA4444, ImageFormat.I8, ImageFormat.CONSOLE_I8_LINEAR, ImageFormat.IA88, ImageFormat.P8, ImageFormat.UV88, ImageFormat.A8, ImageFormat.DXT1, ImageFormat.DXT3, ImageFormat.DXT5, ImageFormat.DXT1_ONE_BIT_ALPHA, ImageFormat.ATI2N, ImageFormat.ATI1N, ImageFormat.RGBX8888, ImageFormat.R8, ImageFormat.BC7, }, ImageFormat.RGBA8888), + (new List { ImageFormat.EMPTY, }, ImageFormat.EMPTY), + }; + } +} From 1921586dca753dd1561dd54ee2480a5834530092 Mon Sep 17 00:00:00 2001 From: AWildErin Date: Thu, 10 Apr 2025 18:20:23 +0100 Subject: [PATCH 4/4] feat(lang): Add very basic VTF bindings --- lang/csharp/src/vtfpp/VTF.cs | 170 +++++++++++++++++++++++++ lang/csharp/test/vtfpp.test/VTFTest.cs | 51 ++++++++ 2 files changed, 221 insertions(+) create mode 100644 lang/csharp/src/vtfpp/VTF.cs create mode 100644 lang/csharp/test/vtfpp.test/VTFTest.cs diff --git a/lang/csharp/src/vtfpp/VTF.cs b/lang/csharp/src/vtfpp/VTF.cs new file mode 100644 index 00000000..8a0a67ab --- /dev/null +++ b/lang/csharp/src/vtfpp/VTF.cs @@ -0,0 +1,170 @@ +using System; +using System.Runtime.InteropServices; + +namespace vtfpp +{ + public enum CompressionMethod + { + DEFLATE = 8, + ZSTD = 93, + CONSOLE_LZMA = 0x360, + } + + public enum ResourceType + { + UNKNOWN, + THUMBNAIL_DATA, + IMAGE_DATA, + PARTICLE_SHEET_DATA, + CRC, + LOD_CONTROL_INFO, + EXTENDED_FLAGS, + KEYVALUES_DATA, + AUX_COMPRESSION, + } + + [Flags] + public enum ResourceFlags + { + NONE = 0, + LOCAL_DATA = 1 << 1, + } + + [Flags] + public enum VTFFlags + { + NONE = 0, + POINT_SAMPLE = 1 << 0, + TRILINEAR = 1 << 1, + CLAMP_S = 1 << 2, + CLAMP_T = 1 << 3, + ANISOTROPIC = 1 << 4, + HINT_DXT5 = 1 << 5, + PWL_CORRECTED = 1 << 6, + NORMAL = 1 << 7, + NO_MIP = 1 << 8, + NO_LOD = 1 << 9, + LOAD_ALL_MIPS = 1 << 10, + PROCEDURAL = 1 << 11, + ONE_BIT_ALPHA = 1 << 12, + MULTI_BIT_ALPHA = 1 << 13, + ENVMAP = 1 << 14, + RENDERTARGET = 1 << 15, + DEPTH_RENDERTARGET = 1 << 16, + NO_DEBUG_OVERRIDE = 1 << 17, + SINGLE_COPY = 1 << 18, + SRGB = 1 << 19, + DEFAULT_POOL = 1 << 20, + COMBINED = 1 << 21, + ASYNC_DOWNLOAD = 1 << 22, + NO_DEPTH_BUFFER = 1 << 23, + SKIP_INITIAL_DOWNLOAD = 1 << 24, + CLAMP_U = 1 << 25, + VERTEX_TEXTURE = 1 << 26, + XBOX_PRESWIZZLED = 1 << 26, + SSBUMP = 1 << 27, + XBOX_CACHEABLE = 1 << 27, + LOAD_MOST_MIPS = 1 << 28, + BORDER = 1 << 29, + YCOCG = 1 << 30, + ASYNC_SKIP_INITIAL_LOW_RES = 1 << 31, + } + + public enum VTFPlatform + { + UNKNOWN = 0x000, + PC = 0x001, + PS3_PORTAL2 = 0x003, + PS3_ORANGEBOX = 0x333, + X360 = 0x360, + } + + internal static unsafe partial class Extern + { + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_open_from_mem")] + public static partial void* VTFOpenFromMemory(byte* buffer, ulong bufferLen, int parseHeaderOnly); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_open_from_file")] + public static partial void* VTFOpenFromFile([MarshalAs(UnmanagedType.LPStr)] string vtfPath); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_image_data_as_rgba8888")] + public static partial sourcepp.Buffer VTFGetImageDataAsRGBA8888(void* handle, byte mip, ushort frame, byte face, ushort slice); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_width")] + public static partial ushort VTFGetWidth(void* handle, byte mip); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_height")] + public static partial ushort VTFGetHeight(void* handle, byte mip); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_close")] + public static partial void VTFClose(void** handle); + + } + + public class VTF + { + private protected unsafe VTF(void* handle) + { + Handle = handle; + } + + ~VTF() + { + unsafe + { + fixed (void** handlePtr = &Handle) + { + Extern.VTFClose(handlePtr); + } + } + } + + public static VTF? OpenFromMemory(byte[] buffer, int parseHeaderOnly) + { + unsafe + { + fixed (byte* bufferPtr = buffer) + { + var handle = Extern.VTFOpenFromMemory(bufferPtr, (ulong)buffer.LongLength, parseHeaderOnly); + return handle == null ? null : new VTF(handle); + } + } + } + + public static VTF? OpenFromFile(string path) + { + unsafe + { + var handle = Extern.VTFOpenFromFile(path); + return handle == null ? null : new VTF(handle); + } + } + + public byte[]? GetImageDataAsRGBA8888(byte mip, ushort frame, byte face, ushort slice) + { + unsafe + { + var buffer = Extern.VTFGetImageDataAsRGBA8888(Handle, mip, frame, face, slice); + return buffer.size < 0 ? null : sourcepp.BufferUtils.ConvertToArrayAndDelete(ref buffer); + } + } + + public ushort GetWidth(byte mip = 0) + { + unsafe + { + return Extern.VTFGetWidth(Handle, mip); + } + } + + public ushort GetHeight(byte mip = 0) + { + unsafe + { + return Extern.VTFGetHeight(Handle, mip); + } + } + + private protected readonly unsafe void* Handle; + } +} diff --git a/lang/csharp/test/vtfpp.test/VTFTest.cs b/lang/csharp/test/vtfpp.test/VTFTest.cs new file mode 100644 index 00000000..e618e4b7 --- /dev/null +++ b/lang/csharp/test/vtfpp.test/VTFTest.cs @@ -0,0 +1,51 @@ +using System.Runtime.InteropServices; + +namespace vtfpp.test +{ + [TestClass] + public class VTFTest + { + [TestMethod] + public void OpenFromFile() + { + string vtfPath = Path.Combine(BasePortalPath, "materials/signage/overlay_aperture_logo_worn.vtf"); + + var vtf = VTF.OpenFromFile(vtfPath); + Assert.IsNotNull(vtf); + + var data = vtf.GetImageDataAsRGBA8888(0, 0, 0, 0); + Assert.IsNotNull(data); + } + + [TestMethod] + public void OpenFromMemory() + { + string vtfPath = Path.Combine(BasePortalPath, "materials/signage/overlay_aperture_logo_worn.vtf"); + byte[] vtfRawData = File.ReadAllBytes(vtfPath); + + + var vtf = VTF.OpenFromMemory(vtfRawData, 0); + Assert.IsNotNull(vtf); + + var data = vtf.GetImageDataAsRGBA8888(0, 0, 0, 0); + Assert.IsNotNull(data); + } + + private static string BasePortalPath + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // CHANGE BEFORE COMMIT!!! + return @"F:/SteamLibrary/steamapps/common/Portal/portal/"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Environment.GetEnvironmentVariable("HOME") + "/.steam/steam/steamapps/common/Portal/portal/"; + } + throw new FileLoadException("Unable to find Steam install directory!"); + } + } + } +}