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..53fd6f0607 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,24 @@ 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
+ // 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; }
+
+ // 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)
{
Id = reader.ReadUInt32();
- Unknown = reader.ReadInt32();
- UnknownByte = reader.ReadByte();
+ DataCategory = reader.ReadUInt32();
+ TextureType = (TextureType)reader.ReadByte();
Textures.Unpack(reader);
}
}