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