From 7b42dc8185b1f285e751f22f676bbf666f82af29 Mon Sep 17 00:00:00 2001 From: Galli <99272482+KT053374@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:31:22 -0400 Subject: [PATCH 1/2] Identify SurfaceTexture metadata fields --- .../SurfaceTextureTests.cs | 103 ++++++++++++++++++ .../ACE.DatLoader/FileTypes/SurfaceTexture.cs | 19 ++-- 2 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 Source/ACE.DatLoader.Tests/SurfaceTextureTests.cs diff --git a/Source/ACE.DatLoader.Tests/SurfaceTextureTests.cs b/Source/ACE.DatLoader.Tests/SurfaceTextureTests.cs new file mode 100644 index 0000000000..c0951e5b84 --- /dev/null +++ b/Source/ACE.DatLoader.Tests/SurfaceTextureTests.cs @@ -0,0 +1,103 @@ +using System.IO; +using System.Linq; +using ACE.DatLoader.FileTypes; +using ACE.Entity.Enum; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ACE.DatLoader.Tests +{ + /// + /// Tests the SurfaceTexture header and texture-reference layout without + /// depending on installed DAT archives or the DatManager singleton. + /// + [TestClass] + public class SurfaceTextureTests + { + [TestMethod] + public void Unpack_TwoTextureReferences_PreservesRetailLayout() + { + // The IDs mirror an observed HighRes/Portal pair. Header bytes are + // constructed in-memory so the test can run without client DAT files. + var data = BuildRecord(0x0500000C, 0, TextureType.Texture2D, + 0x06003B9D, 0x06003B9E); + + var result = UnpackCompletely(data); + + Assert.AreEqual(0x0500000Cu, result.Id); + Assert.AreEqual(0u, result.DataCategory); + Assert.AreEqual(TextureType.Texture2D, result.TextureType); + CollectionAssert.AreEqual(new uint[] { 0x06003B9D, 0x06003B9E }, result.Textures.ToArray()); + } + + [TestMethod] + public void Unpack_NonzeroDataCategory_RetainsUnsignedBits() + { + // A synthetic boundary case: retail SurfaceTexture categories + // observed so far were all zero, but the field is a UInt32 on disk. + var data = BuildRecord(0x05000001, 0xF0000001u, TextureType.Cube, + 0x06000001); + + var result = UnpackCompletely(data); + + Assert.AreEqual(0xF0000001u, result.DataCategory); + Assert.AreEqual(TextureType.Cube, result.TextureType); + CollectionAssert.AreEqual(new uint[] { 0x06000001 }, result.Textures.ToArray()); + } + + [TestMethod] + public void Unpack_ZeroTextureReferences_ConsumesCompleteRecord() + { + var data = BuildRecord(0x05000002, 0, TextureType.Texture2D); + + var result = UnpackCompletely(data); + + Assert.AreEqual(0u, result.DataCategory); + Assert.AreEqual(TextureType.Texture2D, result.TextureType); + Assert.AreEqual(0, result.Textures.Count); + } + + [TestMethod] + public void Unpack_HeaderMatchesExistingRenderTextureLayout() + { + // RenderTexture is an independently implemented reader for the + // same binary field layout. Compare their decoded values. + var data = BuildRecord(0x05000003, 0x80000000u, TextureType.Movie2D, + 0x06000010, 0x06000011); + + var surface = UnpackCompletely(data); + var render = UnpackCompletely(data); + + Assert.AreEqual(render.Id, surface.Id); + Assert.AreEqual(render.DataCategory, surface.DataCategory); + Assert.AreEqual(render.TextureType, surface.TextureType); + CollectionAssert.AreEqual(render.Textures.ToArray(), surface.Textures.ToArray()); + } + + private static T UnpackCompletely(byte[] bytes) where T : FileType, new() + { + using var stream = new MemoryStream(bytes, writable: false); + using var reader = new BinaryReader(stream); + var value = new T(); + value.Unpack(reader); + Assert.AreEqual(stream.Length, stream.Position, + "The parser must consume exactly the bytes in the record."); + return value; + } + + private static byte[] BuildRecord(uint id, uint category, + TextureType textureType, params uint[] textureIds) + { + using var stream = new MemoryStream(); + using (var writer = new BinaryWriter(stream, System.Text.Encoding.UTF8, leaveOpen: true)) + { + writer.Write(id); // UInt32 resource ID + writer.Write(category); // UInt32 DataCategory + writer.Write((byte)textureType); // Byte TextureType + writer.Write(textureIds.Length); // Int32 List count + foreach (var textureId in textureIds) + writer.Write(textureId); + } + return stream.ToArray(); + } + } +} diff --git a/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs b/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs index 6bd609820d..25f43a48c8 100644 --- a/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs +++ b/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs @@ -1,5 +1,6 @@ using ACE.Common; using ACE.DatLoader.Entity; +using ACE.Entity.Enum; using System; using System.Collections.Generic; using System.IO; @@ -10,17 +11,21 @@ namespace ACE.DatLoader.FileTypes [DatFileType(DatFileType.SurfaceTexture)] public class SurfaceTexture : FileType { - // public int Id { get; private set; } - public int Unknown { get; private set; } - public byte UnknownByte { get; private set; } - // public int TextureCount { get; private set; } - public List Textures { get; private set; } = new List(); // These values correspond to a Surface (0x06) entry + // These fields use the same serialized layout as RenderTexture. + // DataCategory is zero in the retail SurfaceTexture records examined; + // its broader meaning has not been independently established. + public uint DataCategory { get; private set; } + + // The examined retail SurfaceTexture records use TextureType.Texture2D (2). + public TextureType TextureType { get; private set; } + + public List Textures { get; private set; } = new List(); // References to Texture (0x06) entries. public override void Unpack(BinaryReader reader) { Id = reader.ReadUInt32(); - Unknown = reader.ReadInt32(); - UnknownByte = reader.ReadByte(); + DataCategory = reader.ReadUInt32(); + TextureType = (TextureType)reader.ReadByte(); Textures.Unpack(reader); } } From c84f4c6939605811d4a867e65c49defc7523d131 Mon Sep 17 00:00:00 2001 From: Galli <99272482+KT053374@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:39:27 -0400 Subject: [PATCH 2/2] Clarify SurfaceTexture metadata documentation --- Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs b/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs index 25f43a48c8..53fd6f0607 100644 --- a/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs +++ b/Source/ACE.DatLoader/FileTypes/SurfaceTexture.cs @@ -11,14 +11,17 @@ namespace ACE.DatLoader.FileTypes [DatFileType(DatFileType.SurfaceTexture)] public class SurfaceTexture : FileType { - // These fields use the same serialized layout as RenderTexture. - // DataCategory is zero in the retail SurfaceTexture records examined; - // its broader meaning has not been independently established. + // The original client identifies ImgTex as SURFACETEXTURE (DB type 0x0B). + // DBObj::Serialize reads/writes this field for categorized resource types. + // All examined retail SurfaceTexture records have DataCategory == 0. public uint DataCategory { get; private set; } - // The examined retail SurfaceTexture records use TextureType.Texture2D (2). + // ImgTex::Serialize writes a fixed byte value of 2. + // RenderTexture uses TextureType.Texture2D for this value. + // On read, the original ImgTex serializer skips the byte without interpreting it. public TextureType TextureType { get; private set; } + // References to Texture resources (IDs in the 0x06 and 0x07 ranges). public List Textures { get; private set; } = new List(); // References to Texture (0x06) entries. public override void Unpack(BinaryReader reader)