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)