From 91a1c67ae1782f57a222b66b5b5c79df8fed5925 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:23:13 +0000 Subject: [PATCH 1/5] Initial plan From d630b3f9cb75be70c46a10be753ce73e38b19c93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:30:38 +0000 Subject: [PATCH 2/5] Add partial C# bindings for vtfpp from PR #72 Co-authored-by: tsa96 <35813309+tsa96@users.noreply.github.com> --- cmake/AddSourcePPLibrary.cmake | 29 +- lang/csharp/src/vtfpp/ImageFormats.cs | 318 ++++++++++++++++++ lang/csharp/src/vtfpp/VTF.cs | 170 ++++++++++ .../test/vtfpp.test/ImageFormatDetailsTest.cs | 229 +++++++++++++ lang/csharp/test/vtfpp.test/VTFTest.cs | 51 +++ lang/csharp/test/vtfpp.test/vtfpp.test.csproj | 30 ++ 6 files changed, 811 insertions(+), 16 deletions(-) create mode 100644 lang/csharp/src/vtfpp/ImageFormats.cs create mode 100644 lang/csharp/src/vtfpp/VTF.cs create mode 100644 lang/csharp/test/vtfpp.test/ImageFormatDetailsTest.cs create mode 100644 lang/csharp/test/vtfpp.test/VTFTest.cs create mode 100644 lang/csharp/test/vtfpp.test/vtfpp.test.csproj diff --git a/cmake/AddSourcePPLibrary.cmake b/cmake/AddSourcePPLibrary.cmake index b1eb5579a..56bc4c993 100644 --- a/cmake/AddSourcePPLibrary.cmake +++ b/cmake/AddSourcePPLibrary.cmake @@ -1,7 +1,5 @@ -include_guard(GLOBAL) - function(add_sourcepp_library TARGET) - cmake_parse_arguments(PARSE_ARGV 1 OPTIONS "C;CSHARP;PYTHON;WASM;TEST;BENCH" "" "") + cmake_parse_arguments(PARSE_ARGV 1 OPTIONS "C;CSHARP;PYTHON;TEST;BENCH" "" "") string(TOUPPER ${TARGET} TARGET_UPPER) if(SOURCEPP_USE_${TARGET_UPPER}) set(PROPAGATE_VARS "") @@ -21,23 +19,22 @@ 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 if(SOURCEPP_BUILD_PYTHON_WRAPPERS AND OPTIONS_PYTHON) - list(APPEND ${${PROJECT_NAME}_PYTHON}_DEPS sourcepp::${TARGET}) - list(APPEND ${${PROJECT_NAME}_PYTHON}_DEFINES ${TARGET_UPPER}) - list(APPEND ${${PROJECT_NAME}_PYTHON}_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/${TARGET}.h") - list(APPEND PROPAGATE_VARS ${${PROJECT_NAME}_PYTHON}_DEPS ${${PROJECT_NAME}_PYTHON}_DEFINES ${${PROJECT_NAME}_PYTHON}_SOURCES) - endif() - - # Add WASM - if(SOURCEPP_BUILD_WASM_WRAPPERS AND OPTIONS_WASM) - list(APPEND ${${PROJECT_NAME}_WASM}_DEPS sourcepp::${TARGET}) - list(APPEND ${${PROJECT_NAME}_WASM}_DEFINES ${TARGET_UPPER}) - list(APPEND ${${PROJECT_NAME}_WASM}_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/lang/wasm/src/${TARGET}.h") - list(APPEND PROPAGATE_VARS ${${PROJECT_NAME}_WASM}_DEPS ${${PROJECT_NAME}_WASM}_DEFINES ${${PROJECT_NAME}_WASM}_SOURCES) + list(APPEND ${SOURCEPP_PYTHON_NAME}_DEPS sourcepp::${TARGET}) + list(APPEND ${SOURCEPP_PYTHON_NAME}_DEFINES ${TARGET_UPPER}) + list(APPEND ${SOURCEPP_PYTHON_NAME}_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/${TARGET}.h") + list(APPEND PROPAGATE_VARS ${SOURCEPP_PYTHON_NAME}_DEPS ${SOURCEPP_PYTHON_NAME}_DEFINES ${SOURCEPP_PYTHON_NAME}_SOURCES) endif() # Add tests diff --git a/lang/csharp/src/vtfpp/ImageFormats.cs b/lang/csharp/src/vtfpp/ImageFormats.cs new file mode 100644 index 000000000..c52c220a8 --- /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/src/vtfpp/VTF.cs b/lang/csharp/src/vtfpp/VTF.cs new file mode 100644 index 000000000..8a0a67ab8 --- /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/ImageFormatDetailsTest.cs b/lang/csharp/test/vtfpp.test/ImageFormatDetailsTest.cs new file mode 100644 index 000000000..f5f99cf51 --- /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), + }; + } +} diff --git a/lang/csharp/test/vtfpp.test/VTFTest.cs b/lang/csharp/test/vtfpp.test/VTFTest.cs new file mode 100644 index 000000000..e618e4b73 --- /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!"); + } + } + } +} 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 000000000..b606c7309 --- /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 2e83f2655a5fb9a88b9044178098af9cda2b391a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:32:37 +0000 Subject: [PATCH 3/5] Add comprehensive C# bindings for vtfpp - all VTF properties and methods Co-authored-by: tsa96 <35813309+tsa96@users.noreply.github.com> --- CMakeLists.txt | 2 +- lang/csharp/src/vtfpp/VTF.cs | 591 +++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a50f5312..f13c5a9f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,7 +216,7 @@ add_sourcepp_library(steampp C PYTHON TEST ) # sourcepp::steam add_sourcepp_library(toolpp PYTHON TEST ) # sourcepp::toolpp add_sourcepp_library(vcryptpp C CSHARP PYTHON TEST ) # sourcepp::vcryptpp add_sourcepp_library(vpkpp C CSHARP PYTHON TEST ) # sourcepp::vpkpp -add_sourcepp_library(vtfpp C PYTHON TEST BENCH) # sourcepp::vtfpp +add_sourcepp_library(vtfpp C CSHARP PYTHON TEST BENCH) # sourcepp::vtfpp # Tests, part 2 diff --git a/lang/csharp/src/vtfpp/VTF.cs b/lang/csharp/src/vtfpp/VTF.cs index 8a0a67ab8..052da0d46 100644 --- a/lang/csharp/src/vtfpp/VTF.cs +++ b/lang/csharp/src/vtfpp/VTF.cs @@ -99,6 +99,193 @@ internal static unsafe partial class Extern [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_close")] public static partial void VTFClose(void** handle); + // Version and platform + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_version")] + public static partial uint VTFGetVersion(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_version")] + public static partial void VTFSetVersion(void* handle, uint version); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_platform")] + public static partial VTFPlatform VTFGetPlatform(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_platform")] + public static partial void VTFSetPlatform(void* handle, VTFPlatform platform); + + // Flags + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_flags")] + public static partial uint VTFGetFlags(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_flags")] + public static partial void VTFSetFlags(void* handle, uint flags); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_add_flags")] + public static partial void VTFAddFlags(void* handle, uint flags); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_remove_flags")] + public static partial void VTFRemoveFlags(void* handle, uint flags); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_is_srgb")] + public static partial int VTFIsSRGB(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_srgb")] + public static partial void VTFSetSRGB(void* handle, int srgb); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_compute_transparency_flags")] + public static partial void VTFComputeTransparencyFlags(void* handle); + + // Format + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_format")] + public static partial ImageFormat VTFGetFormat(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_format")] + public static partial void VTFSetFormat(void* handle, ImageFormat format, byte filter, float quality); + + // Mip count + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_mip_count")] + public static partial byte VTFGetMipCount(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_mip_count")] + public static partial int VTFSetMipCount(void* handle, byte mipCount); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_recommended_mip_count")] + public static partial int VTFSetRecommendedMipCount(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_compute_mips")] + public static partial void VTFComputeMips(void* handle, byte filter); + + // Frame count + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_frame_count")] + public static partial ushort VTFGetFrameCount(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_frame_count")] + public static partial int VTFSetFrameCount(void* handle, ushort frameCount); + + // Face count + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_face_count")] + public static partial byte VTFGetFaceCount(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_face_count")] + public static partial int VTFSetFaceCount(void* handle, int isCubeMap); + + // Depth + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_depth")] + public static partial ushort VTFGetDepth(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_depth")] + public static partial int VTFSetDepth(void* handle, ushort depth); + + // Start frame + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_start_frame")] + public static partial ushort VTFGetStartFrame(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_start_frame")] + public static partial void VTFSetStartFrame(void* handle, ushort startFrame); + + // Reflectivity + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_reflectivity")] + public static partial void VTFGetReflectivity(void* handle, float* r, float* g, float* b); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_reflectivity")] + public static partial void VTFSetReflectivity(void* handle, float r, float g, float b); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_compute_reflectivity")] + public static partial void VTFComputeReflectivity(void* handle); + + // Bumpmap scale + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_bumpmap_scale")] + public static partial float VTFGetBumpmapScale(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_bumpmap_scale")] + public static partial void VTFSetBumpmapScale(void* handle, float bumpMapScale); + + // Thumbnail + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_thumbnail_format")] + public static partial ImageFormat VTFGetThumbnailFormat(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_thumbnail_width")] + public static partial byte VTFGetThumbnailWidth(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_thumbnail_height")] + public static partial byte VTFGetThumbnailHeight(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_has_thumbnail_data")] + public static partial int VTFHasThumbnailData(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_thumbnail_data_as_rgba8888")] + public static partial sourcepp.Buffer VTFGetThumbnailDataAsRGBA8888(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_thumbnail")] + public static partial void VTFSetThumbnail(void* handle, byte* imageData, ulong imageLen, ImageFormat format, ushort width, ushort height, float quality); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_compute_thumbnail")] + public static partial void VTFComputeThumbnail(void* handle, byte filter, float quality); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_remove_thumbnail")] + public static partial void VTFRemoveThumbnail(void* handle); + + // Resources + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_resources_count")] + public static partial uint VTFGetResourcesCount(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_resource_at_index")] + public static partial void* VTFGetResourceAtIndex(void* handle, uint index); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_resource_with_type")] + public static partial void* VTFGetResourceWithType(void* handle, ResourceType type); + + // Compression + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_compression_level")] + public static partial short VTFGetCompressionLevel(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_compression_level")] + public static partial void VTFSetCompressionLevel(void* handle, short compressionLevel); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_compression_method")] + public static partial CompressionMethod VTFGetCompressionMethod(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_set_compression_method")] + public static partial void VTFSetCompressionMethod(void* handle, CompressionMethod compressionMethod); + + // Image data + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_has_image_data")] + public static partial int VTFHasImageData(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_padded_width")] + public static partial ushort VTFGetPaddedWidth(void* handle, byte mip); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_get_padded_height")] + public static partial ushort VTFGetPaddedHeight(void* handle, byte mip); + + // Save/Bake + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_bake")] + public static partial sourcepp.Buffer VTFBake(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_vtf_bake_to_file")] + public static partial int VTFBakeToFile(void* handle, [MarshalAs(UnmanagedType.LPStr)] string vtfPath); + + // Resource functions + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_type")] + public static partial ResourceType ResourceGetType(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_flags")] + public static partial ResourceFlags ResourceGetFlags(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_data")] + public static partial byte* ResourceGetData(void* handle, ulong* dataLen); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_data_as_crc")] + public static partial uint ResourceGetDataAsCRC(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_data_as_extended_flags")] + public static partial uint ResourceGetDataAsExtendedFlags(void* handle); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_data_as_lod")] + public static partial void ResourceGetDataAsLOD(void* handle, byte* u, byte* v, byte* u360, byte* v360); + + [LibraryImport("sourcepp_vtfppc", EntryPoint = "vtfpp_resource_get_data_as_keyvalues_data")] + public static partial sourcepp.String ResourceGetDataAsKeyvaluesData(void* handle); + } public class VTF @@ -165,6 +352,410 @@ public ushort GetHeight(byte mip = 0) } } + public uint Version + { + get + { + unsafe { return Extern.VTFGetVersion(Handle); } + } + set + { + unsafe { Extern.VTFSetVersion(Handle, value); } + } + } + + public VTFPlatform Platform + { + get + { + unsafe { return Extern.VTFGetPlatform(Handle); } + } + set + { + unsafe { Extern.VTFSetPlatform(Handle, value); } + } + } + + public VTFFlags Flags + { + get + { + unsafe { return (VTFFlags)Extern.VTFGetFlags(Handle); } + } + set + { + unsafe { Extern.VTFSetFlags(Handle, (uint)value); } + } + } + + public void AddFlags(VTFFlags flags) + { + unsafe { Extern.VTFAddFlags(Handle, (uint)flags); } + } + + public void RemoveFlags(VTFFlags flags) + { + unsafe { Extern.VTFRemoveFlags(Handle, (uint)flags); } + } + + public bool IsSRGB + { + get + { + unsafe { return Extern.VTFIsSRGB(Handle) != 0; } + } + set + { + unsafe { Extern.VTFSetSRGB(Handle, value ? 1 : 0); } + } + } + + public void ComputeTransparencyFlags() + { + unsafe { Extern.VTFComputeTransparencyFlags(Handle); } + } + + public ImageFormat Format + { + get + { + unsafe { return Extern.VTFGetFormat(Handle); } + } + } + + public void SetFormat(ImageFormat format, byte filter = 0, float quality = 0.105f) + { + unsafe { Extern.VTFSetFormat(Handle, format, filter, quality); } + } + + public byte MipCount + { + get + { + unsafe { return Extern.VTFGetMipCount(Handle); } + } + set + { + unsafe { Extern.VTFSetMipCount(Handle, value); } + } + } + + public bool SetRecommendedMipCount() + { + unsafe { return Extern.VTFSetRecommendedMipCount(Handle) != 0; } + } + + public void ComputeMips(byte filter = 0) + { + unsafe { Extern.VTFComputeMips(Handle, filter); } + } + + public ushort FrameCount + { + get + { + unsafe { return Extern.VTFGetFrameCount(Handle); } + } + set + { + unsafe { Extern.VTFSetFrameCount(Handle, value); } + } + } + + public byte FaceCount + { + get + { + unsafe { return Extern.VTFGetFaceCount(Handle); } + } + } + + public void SetFaceCount(bool isCubeMap) + { + unsafe { Extern.VTFSetFaceCount(Handle, isCubeMap ? 1 : 0); } + } + + public ushort Depth + { + get + { + unsafe { return Extern.VTFGetDepth(Handle); } + } + set + { + unsafe { Extern.VTFSetDepth(Handle, value); } + } + } + + public ushort StartFrame + { + get + { + unsafe { return Extern.VTFGetStartFrame(Handle); } + } + set + { + unsafe { Extern.VTFSetStartFrame(Handle, value); } + } + } + + public (float r, float g, float b) GetReflectivity() + { + unsafe + { + float r, g, b; + Extern.VTFGetReflectivity(Handle, &r, &g, &b); + return (r, g, b); + } + } + + public void SetReflectivity(float r, float g, float b) + { + unsafe { Extern.VTFSetReflectivity(Handle, r, g, b); } + } + + public void ComputeReflectivity() + { + unsafe { Extern.VTFComputeReflectivity(Handle); } + } + + public float BumpmapScale + { + get + { + unsafe { return Extern.VTFGetBumpmapScale(Handle); } + } + set + { + unsafe { Extern.VTFSetBumpmapScale(Handle, value); } + } + } + + public ImageFormat ThumbnailFormat + { + get + { + unsafe { return Extern.VTFGetThumbnailFormat(Handle); } + } + } + + public byte ThumbnailWidth + { + get + { + unsafe { return Extern.VTFGetThumbnailWidth(Handle); } + } + } + + public byte ThumbnailHeight + { + get + { + unsafe { return Extern.VTFGetThumbnailHeight(Handle); } + } + } + + public bool HasThumbnailData + { + get + { + unsafe { return Extern.VTFHasThumbnailData(Handle) != 0; } + } + } + + public byte[]? GetThumbnailDataAsRGBA8888() + { + unsafe + { + var buffer = Extern.VTFGetThumbnailDataAsRGBA8888(Handle); + return buffer.size < 0 ? null : sourcepp.BufferUtils.ConvertToArrayAndDelete(ref buffer); + } + } + + public void SetThumbnail(byte[] imageData, ImageFormat format, ushort width, ushort height, float quality = 0.105f) + { + unsafe + { + fixed (byte* imageDataPtr = imageData) + { + Extern.VTFSetThumbnail(Handle, imageDataPtr, (ulong)imageData.LongLength, format, width, height, quality); + } + } + } + + public void ComputeThumbnail(byte filter = 0, float quality = 0.105f) + { + unsafe { Extern.VTFComputeThumbnail(Handle, filter, quality); } + } + + public void RemoveThumbnail() + { + unsafe { Extern.VTFRemoveThumbnail(Handle); } + } + + public uint ResourcesCount + { + get + { + unsafe { return Extern.VTFGetResourcesCount(Handle); } + } + } + + public Resource? GetResourceAtIndex(uint index) + { + unsafe + { + var handle = Extern.VTFGetResourceAtIndex(Handle, index); + return handle == null ? null : new Resource(handle); + } + } + + public Resource? GetResourceWithType(ResourceType type) + { + unsafe + { + var handle = Extern.VTFGetResourceWithType(Handle, type); + return handle == null ? null : new Resource(handle); + } + } + + public short CompressionLevel + { + get + { + unsafe { return Extern.VTFGetCompressionLevel(Handle); } + } + set + { + unsafe { Extern.VTFSetCompressionLevel(Handle, value); } + } + } + + public CompressionMethod CompressionMethod + { + get + { + unsafe { return Extern.VTFGetCompressionMethod(Handle); } + } + set + { + unsafe { Extern.VTFSetCompressionMethod(Handle, value); } + } + } + + public bool HasImageData + { + get + { + unsafe { return Extern.VTFHasImageData(Handle) != 0; } + } + } + + public ushort GetPaddedWidth(byte mip = 0) + { + unsafe { return Extern.VTFGetPaddedWidth(Handle, mip); } + } + + public ushort GetPaddedHeight(byte mip = 0) + { + unsafe { return Extern.VTFGetPaddedHeight(Handle, mip); } + } + + public byte[]? Bake() + { + unsafe + { + var buffer = Extern.VTFBake(Handle); + return buffer.size < 0 ? null : sourcepp.BufferUtils.ConvertToArrayAndDelete(ref buffer); + } + } + + public bool BakeToFile(string vtfPath) + { + unsafe { return Extern.VTFBakeToFile(Handle, vtfPath) != 0; } + } + private protected readonly unsafe void* Handle; } + + public class Resource + { + private readonly unsafe void* Handle; + + internal unsafe Resource(void* handle) + { + Handle = handle; + } + + public ResourceType Type + { + get + { + unsafe { return Extern.ResourceGetType(Handle); } + } + } + + public ResourceFlags Flags + { + get + { + unsafe { return Extern.ResourceGetFlags(Handle); } + } + } + + public byte[]? GetData() + { + unsafe + { + ulong dataLen; + byte* data = Extern.ResourceGetData(Handle, &dataLen); + if (data == null || dataLen == 0) + return null; + + byte[] result = new byte[dataLen]; + for (ulong i = 0; i < dataLen; i++) + { + result[i] = data[i]; + } + return result; + } + } + + public uint? GetDataAsCRC() + { + if (Type != ResourceType.CRC) + return null; + unsafe { return Extern.ResourceGetDataAsCRC(Handle); } + } + + public uint? GetDataAsExtendedFlags() + { + if (Type != ResourceType.EXTENDED_FLAGS) + return null; + unsafe { return Extern.ResourceGetDataAsExtendedFlags(Handle); } + } + + public (byte u, byte v, byte u360, byte v360)? GetDataAsLOD() + { + if (Type != ResourceType.LOD_CONTROL_INFO) + return null; + unsafe + { + byte u, v, u360, v360; + Extern.ResourceGetDataAsLOD(Handle, &u, &v, &u360, &v360); + return (u, v, u360, v360); + } + } + + public string? GetDataAsKeyvaluesData() + { + if (Type != ResourceType.KEYVALUES_DATA) + return null; + unsafe + { + var str = Extern.ResourceGetDataAsKeyvaluesData(Handle); + return sourcepp.StringUtils.ConvertToStringAndDelete(ref str); + } + } + } } From 72e0deadacaed2acf0a5e227d803431635890ba0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:37:04 +0000 Subject: [PATCH 4/5] Complete C# bindings for vtfpp - all files generated and verified to compile Co-authored-by: tsa96 <35813309+tsa96@users.noreply.github.com> --- lang/csharp/src/vtfpp.sln | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lang/csharp/src/vtfpp.sln diff --git a/lang/csharp/src/vtfpp.sln b/lang/csharp/src/vtfpp.sln new file mode 100644 index 000000000..1194aa33d --- /dev/null +++ b/lang/csharp/src/vtfpp.sln @@ -0,0 +1,18 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vtfpp", "vtfpp\vtfpp.csproj", "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}" +EndProject +Global +GlobalSection(SolutionConfigurationPlatforms) = preSolution +Debug|Any CPU = Debug|Any CPU +Release|Any CPU = Release|Any CPU +EndGlobalSection +GlobalSection(ProjectConfigurationPlatforms) = postSolution +{9A19103F-16F7-4668-BE54-9A1E7A4F7556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +{9A19103F-16F7-4668-BE54-9A1E7A4F7556}.Debug|Any CPU.Build.0 = Debug|Any CPU +{9A19103F-16F7-4668-BE54-9A1E7A4F7556}.Release|Any CPU.ActiveCfg = Release|Any CPU +{9A19103F-16F7-4668-BE54-9A1E7A4F7556}.Release|Any CPU.Build.0 = Release|Any CPU +EndGlobalSection +EndGlobal From 92ee1590682c8ca09545986fc216f5ea2321c8a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:40:32 +0000 Subject: [PATCH 5/5] Address code review feedback - fix test path and make ImageDimensions public Co-authored-by: tsa96 <35813309+tsa96@users.noreply.github.com> --- lang/csharp/src/vtfpp/ImageFormats.cs | 4 ++-- lang/csharp/test/vtfpp.test/VTFTest.cs | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lang/csharp/src/vtfpp/ImageFormats.cs b/lang/csharp/src/vtfpp/ImageFormats.cs index c52c220a8..8c31cf64d 100644 --- a/lang/csharp/src/vtfpp/ImageFormats.cs +++ b/lang/csharp/src/vtfpp/ImageFormats.cs @@ -276,7 +276,7 @@ public static bool GetDataPosition(ref uint offset, ref uint length, ImageFormat { unsafe { - /// @todo Is there a better way for this? + // Nested fixed statements are the standard pattern for pinning multiple ref parameters fixed (uint* offsetPtr = &offset) { fixed (uint* lengthPtr = &length) @@ -288,7 +288,7 @@ public static bool GetDataPosition(ref uint offset, ref uint length, ImageFormat } } - class ImageDimensions + public class ImageDimensions { public static uint GetMipDim(byte mip, ushort dim) { diff --git a/lang/csharp/test/vtfpp.test/VTFTest.cs b/lang/csharp/test/vtfpp.test/VTFTest.cs index e618e4b73..0d24ae8f1 100644 --- a/lang/csharp/test/vtfpp.test/VTFTest.cs +++ b/lang/csharp/test/vtfpp.test/VTFTest.cs @@ -37,14 +37,19 @@ private static string BasePortalPath { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // CHANGE BEFORE COMMIT!!! - return @"F:/SteamLibrary/steamapps/common/Portal/portal/"; + // Use environment variable or try common Steam location + var testPath = Environment.GetEnvironmentVariable("SOURCEPP_TEST_DATA_PATH"); + if (!string.IsNullOrEmpty(testPath)) + return testPath; + var steamPath = @"C:\Program Files (x86)\Steam\steamapps\common\Portal\portal\"; + if (Directory.Exists(steamPath)) + return steamPath; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return Environment.GetEnvironmentVariable("HOME") + "/.steam/steam/steamapps/common/Portal/portal/"; } - throw new FileLoadException("Unable to find Steam install directory!"); + throw new FileNotFoundException("Portal test data not found. Set SOURCEPP_TEST_DATA_PATH or install Portal."); } } }