From 09bc0110d0021e49924bc551ba6f06b493c6f3a1 Mon Sep 17 00:00:00 2001 From: bird_egop Date: Mon, 11 May 2026 06:44:49 +0300 Subject: [PATCH] msh 0x14 and different flags --- NResLib/NResParser.cs | 2 +- ParkanPlayground/Msh0x01.cs | 5 ++- ParkanPlayground/Msh0x0D.cs | 17 ++++++- ParkanPlayground/Msh0x14.cs | 90 +++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 ParkanPlayground/Msh0x14.cs diff --git a/NResLib/NResParser.cs b/NResLib/NResParser.cs index 11e0bdd..61fbdeb 100644 --- a/NResLib/NResParser.cs +++ b/NResLib/NResParser.cs @@ -119,7 +119,7 @@ public static class NResParser break; } } - if (!formattable) + if (formattable) { return Encoding.ASCII.GetString(bytes); } diff --git a/ParkanPlayground/Msh0x01.cs b/ParkanPlayground/Msh0x01.cs index 4ebd15c..db43e0c 100644 --- a/ParkanPlayground/Msh0x01.cs +++ b/ParkanPlayground/Msh0x01.cs @@ -89,7 +89,10 @@ public static class Msh0x01 } } -public enum NodeFlags +public enum NodeFlags : ushort { + // very uncertain + MSH01_NODE_FLAG_UNKNOWN_SKIP_RECURSE_0x04 = 0x4, + MSH01_NODE_FLAG_STOP_CHILD_TRAVERSAL = 0x10, MSH01_NODE_FLAG_NO_SHADOW = 0x40 } \ No newline at end of file diff --git a/ParkanPlayground/Msh0x0D.cs b/ParkanPlayground/Msh0x0D.cs index b058cf5..730d4f6 100644 --- a/ParkanPlayground/Msh0x0D.cs +++ b/ParkanPlayground/Msh0x0D.cs @@ -37,7 +37,7 @@ public static class Msh0x0D var elementBytes = data.Chunk(ElementSize); var elements = elementBytes.Select(x => new Batch( - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)), + (BatchFlags)BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(2)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(4)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)), @@ -59,7 +59,7 @@ public static class Msh0x0D /// [0x0E..0x10] Opaque поле. Старое локальное имя: CountOf03; не подтверждено. /// [0x10..0x14] Базовая вершина в position stream 0x03. У FParkan: baseVertex. public readonly record struct Batch( - ushort BatchFlags, + BatchFlags BatchFlags, ushort MaterialIndex, ushort Opaque4, ushort Opaque6, @@ -68,3 +68,16 @@ public static class Msh0x0D ushort Opaque14, uint BaseVertex); } + +public enum BatchFlags : ushort +{ + MSH0D_BATCH_SPECIAL_SUBMIT = 1, + MSH0D_BATCH_INTERSECT_TWO_SIDED = 2, + MSH0D_BATCH_SPECIAL_PASS_OR_CONDITIONAL = 5, + MSH0D_BATCH_USE_MATERIAL_PHASE = 8, + MSH0D_BATCH_SUPPRESS_BATCH = 32, + MSH0D_BATCH_FORCE_PASS2 = 256, + MSH0D_BATCH_EMULATE_POINT_LIGHTS = 2048, + MSH0D_BATCH_HAS_LIGHTMAP_OR_TEXCOORD1 = 8192, + MSH0D_BATCH_FACING_TEST_EARLY_OUT = 16384, +} \ No newline at end of file diff --git a/ParkanPlayground/Msh0x14.cs b/ParkanPlayground/Msh0x14.cs new file mode 100644 index 0000000..00105fe --- /dev/null +++ b/ParkanPlayground/Msh0x14.cs @@ -0,0 +1,90 @@ +using System.Buffers.Binary; +using System.Numerics; +using NResLib; + +namespace ParkanPlayground; + +/// +/// MSH-компонент 0x14: таблица локальных directional/probe light entries. +/// Используется CAniMesh_IJointMesh::SampleMsh14Lights / AccumulateMsh14LightContributions. +/// +public static class Msh0x14 +{ + public const int ElementSize = 48; + + public static List ReadComponent( + FileStream mshFs, NResArchive archive) + { + var entry = archive.Files.FirstOrDefault(x => x.FileType == "14 00 00 00"); + + if (entry is null) + { + throw new Exception("Archive doesn't contain file (14)"); + } + + if (entry.ElementSize != ElementSize) + { + throw new Exception("Light probe component (0x14) element size is not 48"); + } + + if (entry.FileLength % entry.ElementSize != 0) + { + throw new Exception("Light probe component (0x14) payload size is not divisible by element size"); + } + + var data = new byte[entry.FileLength]; + mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin); + mshFs.ReadExactly(data, 0, data.Length); + + var elementBytes = data.Chunk(ElementSize); + + var elements = elementBytes.Select(x => new LightProbe( + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x00)), + new Vector3( + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x04)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x08)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x0C))), + new Vector3( + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x10)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x14)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x18))), + new Vector4( + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x1C)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x20)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x24)), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x28))), + BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x2C)))).ToList(); + + return elements; + } + + /// + /// Component 0x14 is optional in the game reader. + /// This helper mirrors that behavior and returns an empty list when absent. + /// + public static List ReadComponentOrEmpty( + FileStream mshFs, NResArchive archive) + { + var entry = archive.Files.FirstOrDefault(x => x.FileType == "14 00 00 00"); + + if (entry is null) + { + return []; + } + + return ReadComponent(mshFs, archive); + } + + /// Light/probe entry 0x14. + /// [0x00..0x04] Индекс MSH_piece, чью world matrix используют для transform position/direction. + /// [0x04..0x10] Локальная позиция источника/probe относительно PieceIndex. + /// [0x10..0x1C] Локальное направление, transform direction через matrix piece. + /// [0x1C..0x2C] RGBA/intensity color multiplier. В коде умножается на вычисленный strength. + /// [0x2C..0x30] Scalar intensity. В коде участвует как * Intensity * 3.0. + public readonly record struct LightProbe( + int PieceIndex, + Vector3 LocalPosition, + Vector3 LocalDirection, + Vector4 Color, + int Intensity); +} \ No newline at end of file