using System.Buffers.Binary; using System.Text; namespace TexmLib; public class TexmParser { public static TexmParseResult ReadFromStream(Stream stream, string file) { if (stream.Length < 32) { return new TexmParseResult(null, "Файл короче 32 байт - точно не текстура"); } Span headerBytes = stackalloc byte[32]; stream.ReadExactly(headerBytes); var texmHeader = headerBytes[0..4]; var widthBytes = headerBytes[4..8]; var heightBytes = headerBytes[8..12]; var mipmapCountBytes = headerBytes[12..16]; var flags4Bytes = headerBytes[16..20]; var flags5Bytes = headerBytes[20..24]; var unk6Bytes = headerBytes[24..28]; var formatBytes = headerBytes[28..32]; var texmAscii = Encoding.ASCII.GetString(texmHeader).Trim('\0'); var width = BinaryPrimitives.ReadInt32LittleEndian(widthBytes); var height = BinaryPrimitives.ReadInt32LittleEndian(heightBytes); var mipmapCount = BinaryPrimitives.ReadInt32LittleEndian(mipmapCountBytes); var flags4 = BinaryPrimitives.ReadInt32LittleEndian(flags4Bytes); var flags5 = BinaryPrimitives.ReadInt32LittleEndian(flags5Bytes); var unk6 = BinaryPrimitives.ReadInt32LittleEndian(unk6Bytes); var format = BinaryPrimitives.ReadInt32LittleEndian(formatBytes); if (texmAscii != "Texm") { return new TexmParseResult(null, "Файл не начинается с Texm"); } var header = new TexmHeader( texmAscii, width, height, mipmapCount, flags4, flags5, unk6, format ); List mipmapBytesList; var isIndexed = false; byte[] lookupColors = []; if (format == 0) { // если формат 0, то текстура использует lookup таблицу в первых 1024 байтах (256 разных цветов в формате ARGB 888) lookupColors = new byte[1024]; stream.ReadExactly(lookupColors, 0, lookupColors.Length); mipmapBytesList = ReadMipmapsAsIndexes( stream, mipmapCount, width, height ); isIndexed = true; } else { mipmapBytesList = ReadMipmaps( stream, format.AsStride(), mipmapCount, width, height ); } PageHeader? pages = null; if (stream.Position < stream.Length) { // has PAGE data pages = ReadPage(stream); } var textureFile = new TexmFile(file, header, mipmapBytesList, pages, isIndexed, lookupColors); return new TexmParseResult(textureFile); } private static PageHeader ReadPage(Stream stream) { Span pageBytes = stackalloc byte[4]; stream.ReadExactly(pageBytes); var pageHeaderAscii = Encoding.ASCII.GetString(pageBytes); stream.ReadExactly(pageBytes); var pageCount = BinaryPrimitives.ReadInt32LittleEndian(pageBytes); List pageItems = []; Span itemBytes = stackalloc byte[2]; for (int i = 0; i < pageCount; i++) { stream.ReadExactly(itemBytes); var x = BinaryPrimitives.ReadInt16LittleEndian(itemBytes); stream.ReadExactly(itemBytes); var pageWidth = BinaryPrimitives.ReadInt16LittleEndian(itemBytes); stream.ReadExactly(itemBytes); var y = BinaryPrimitives.ReadInt16LittleEndian(itemBytes); stream.ReadExactly(itemBytes); var pageHeight = BinaryPrimitives.ReadInt16LittleEndian(itemBytes); pageItems.Add( new PageItem( x, pageWidth, y, pageHeight ) ); } var pageHeader = new PageHeader(pageHeaderAscii, pageCount, pageItems); return pageHeader; } private static List ReadMipmaps(Stream stream, int stride, int mipmapCount, int topWidth, int topHeight) { List mipmapByteLengths = []; for (int i = 0; i < mipmapCount; i++) { var mipWidth = Math.Max(1, topWidth >> i); var mipHeight = Math.Max(1, topHeight >> i); var imageByteLength = mipWidth * mipHeight * (stride / 8); mipmapByteLengths.Add(imageByteLength); } List mipmapBytesList = []; foreach (var mipmapByteLength in mipmapByteLengths) { var mipmapBuffer = new byte[mipmapByteLength]; stream.ReadExactly(mipmapBuffer, 0, mipmapByteLength); mipmapBytesList.Add(mipmapBuffer); } return mipmapBytesList; } private static List ReadMipmapsAsIndexes(Stream stream, int mipmapCount, int topWidth, int topHeight) { List mipmapByteLengths = []; for (int i = 0; i < mipmapCount; i++) { var mipWidth = Math.Max(1, topWidth >> i); var mipHeight = Math.Max(1, topHeight >> i); var imageByteLength = mipWidth * mipHeight; mipmapByteLengths.Add(imageByteLength); } List mipmapBytesList = []; foreach (var mipmapByteLength in mipmapByteLengths) { var mipmapBuffer = new byte[mipmapByteLength]; stream.ReadExactly(mipmapBuffer, 0, mipmapByteLength); mipmapBytesList.Add(mipmapBuffer); } return mipmapBytesList; } }