Files
parkan-playground/TexmLib/TexmParser.cs
T

189 lines
5.7 KiB
C#
Raw Permalink Normal View History

2024-11-18 23:48:42 +03:00
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<byte> 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];
2026-05-09 20:36:43 +03:00
var flags4Bytes = headerBytes[16..20];
var flags5Bytes = headerBytes[20..24];
var unk6Bytes = headerBytes[24..28];
2024-11-18 23:48:42 +03:00
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);
2026-05-09 20:36:43 +03:00
var flags4 = BinaryPrimitives.ReadInt32LittleEndian(flags4Bytes);
var flags5 = BinaryPrimitives.ReadInt32LittleEndian(flags5Bytes);
var unk6 = BinaryPrimitives.ReadInt32LittleEndian(unk6Bytes);
2024-11-18 23:48:42 +03:00
var format = BinaryPrimitives.ReadInt32LittleEndian(formatBytes);
if (texmAscii != "Texm")
{
return new TexmParseResult(null, "Файл не начинается с Texm");
}
var header = new TexmHeader(
texmAscii,
width,
height,
mipmapCount,
2026-05-09 20:36:43 +03:00
flags4,
flags5,
unk6,
2024-11-18 23:48:42 +03:00
format
);
2026-05-09 20:36:43 +03:00
List<byte[]> mipmapBytesList;
var isIndexed = false;
byte[] lookupColors = [];
2024-11-18 23:48:42 +03:00
if (format == 0)
{
// если формат 0, то текстура использует lookup таблицу в первых 1024 байтах (256 разных цветов в формате ARGB 888)
2026-05-09 20:36:43 +03:00
lookupColors = new byte[1024];
2024-11-18 23:48:42 +03:00
stream.ReadExactly(lookupColors, 0, lookupColors.Length);
2026-05-09 20:36:43 +03:00
mipmapBytesList = ReadMipmapsAsIndexes(
2024-11-18 23:48:42 +03:00
stream,
mipmapCount,
width,
height
);
2026-05-09 20:36:43 +03:00
isIndexed = true;
2024-11-18 23:48:42 +03:00
}
else
{
2026-05-09 20:36:43 +03:00
mipmapBytesList = ReadMipmaps(
2024-11-18 23:48:42 +03:00
stream,
format.AsStride(),
mipmapCount,
width,
height
);
}
2026-05-09 20:36:43 +03:00
PageHeader? pages = null;
2024-11-18 23:48:42 +03:00
if (stream.Position < stream.Length)
{
// has PAGE data
2026-05-09 20:36:43 +03:00
pages = ReadPage(stream);
2024-11-18 23:48:42 +03:00
}
2026-05-09 20:36:43 +03:00
var textureFile = new TexmFile(file, header, mipmapBytesList, pages, isIndexed, lookupColors);
2024-11-18 23:48:42 +03:00
return new TexmParseResult(textureFile);
}
private static PageHeader ReadPage(Stream stream)
{
Span<byte> pageBytes = stackalloc byte[4];
stream.ReadExactly(pageBytes);
var pageHeaderAscii = Encoding.ASCII.GetString(pageBytes);
stream.ReadExactly(pageBytes);
var pageCount = BinaryPrimitives.ReadInt32LittleEndian(pageBytes);
List<PageItem> pageItems = [];
Span<byte> 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<byte[]> ReadMipmaps(Stream stream, int stride, int mipmapCount, int topWidth, int topHeight)
{
List<int> mipmapByteLengths = [];
for (int i = 0; i < mipmapCount; i++)
{
2026-05-09 20:36:43 +03:00
var mipWidth = Math.Max(1, topWidth >> i);
var mipHeight = Math.Max(1, topHeight >> i);
2024-11-18 23:48:42 +03:00
var imageByteLength = mipWidth * mipHeight * (stride / 8);
mipmapByteLengths.Add(imageByteLength);
}
List<byte[]> mipmapBytesList = [];
foreach (var mipmapByteLength in mipmapByteLengths)
{
var mipmapBuffer = new byte[mipmapByteLength];
stream.ReadExactly(mipmapBuffer, 0, mipmapByteLength);
mipmapBytesList.Add(mipmapBuffer);
}
return mipmapBytesList;
}
private static List<byte[]> ReadMipmapsAsIndexes(Stream stream, int mipmapCount, int topWidth, int topHeight)
{
List<int> mipmapByteLengths = [];
for (int i = 0; i < mipmapCount; i++)
{
2026-05-09 20:36:43 +03:00
var mipWidth = Math.Max(1, topWidth >> i);
var mipHeight = Math.Max(1, topHeight >> i);
2024-11-18 23:48:42 +03:00
var imageByteLength = mipWidth * mipHeight;
mipmapByteLengths.Add(imageByteLength);
}
List<byte[]> mipmapBytesList = [];
foreach (var mipmapByteLength in mipmapByteLengths)
{
var mipmapBuffer = new byte[mipmapByteLength];
stream.ReadExactly(mipmapBuffer, 0, mipmapByteLength);
mipmapBytesList.Add(mipmapBuffer);
}
return mipmapBytesList;
}
2026-05-09 20:36:43 +03:00
}