mirror of
				https://github.com/sampletext32/ParkanPlayground.git
				synced 2025-11-04 07:19:45 +03:00 
			
		
		
		
	msh and cp converters. Mesh broken.
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
using System.Diagnostics;
 | 
					using System.Diagnostics;
 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MissionTmaLib;
 | 
					namespace Common;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
 | 
					[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
 | 
				
			||||||
public class IntFloatValue(Span<byte> span)
 | 
					public class IntFloatValue(Span<byte> span)
 | 
				
			||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
namespace MissionTmaLib;
 | 
					using Common;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace MissionTmaLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
 | 
					public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
 | 
				
			||||||
@@ -26,7 +26,7 @@ namespace ParkanPlayground;
 | 
				
			|||||||
public class CpDatEntryConverter
 | 
					public class CpDatEntryConverter
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    const string gameRoot = "C:\\Program Files (x86)\\Nikita\\Iron Strategy";
 | 
					    const string gameRoot = "C:\\Program Files (x86)\\Nikita\\Iron Strategy";
 | 
				
			||||||
    const string missionTmaPath = $"{gameRoot}\\MISSIONS\\Single.01\\data.tma";
 | 
					    const string missionTmaPath = $"{gameRoot}\\MISSIONS\\Campaign\\Campaign.01\\Mission.01\\data.tma";
 | 
				
			||||||
    const string staticRlbPath = $"{gameRoot}\\static.rlb";
 | 
					    const string staticRlbPath = $"{gameRoot}\\static.rlb";
 | 
				
			||||||
    const string objectsRlbPath = $"{gameRoot}\\objects.rlb";
 | 
					    const string objectsRlbPath = $"{gameRoot}\\objects.rlb";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								ParkanPlayground/Msh01.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								ParkanPlayground/Msh01.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					using System.Buffers.Binary;
 | 
				
			||||||
 | 
					using NResLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ParkanPlayground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class Msh01
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static Msh01Component ReadComponent(FileStream mshFs, NResArchive archive)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var headerFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "01 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (headerFileEntry is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Archive doesn't contain header file (01)");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = new byte[headerFileEntry.ElementCount * headerFileEntry.ElementSize];
 | 
				
			||||||
 | 
					        mshFs.Seek(headerFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
 | 
					        mshFs.ReadExactly(data, 0, data.Length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var dataSpan = data.AsSpan();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elements = new List<SubMesh>((int)headerFileEntry.ElementCount);
 | 
				
			||||||
 | 
					        for (var i = 0; i < headerFileEntry.ElementCount; i++)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var element = new SubMesh()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Type1 = dataSpan[i * headerFileEntry.ElementSize + 0],
 | 
				
			||||||
 | 
					                Type2 = dataSpan[i * headerFileEntry.ElementSize + 1],
 | 
				
			||||||
 | 
					                ParentIndex =
 | 
				
			||||||
 | 
					                    BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 2)),
 | 
				
			||||||
 | 
					                OffsetIntoFile13 =
 | 
				
			||||||
 | 
					                    BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 4)),
 | 
				
			||||||
 | 
					                IndexInFile08 =
 | 
				
			||||||
 | 
					                    BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 6)),
 | 
				
			||||||
 | 
					                State00 = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 8)),
 | 
				
			||||||
 | 
					                State01 = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 10)),
 | 
				
			||||||
 | 
					                State10 = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 12)),
 | 
				
			||||||
 | 
					                State11 = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 14)),
 | 
				
			||||||
 | 
					                State20 = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 16)),
 | 
				
			||||||
 | 
					                State21 = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 18)),
 | 
				
			||||||
 | 
					                S20 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 20)),
 | 
				
			||||||
 | 
					                S22 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 22)),
 | 
				
			||||||
 | 
					                S24 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 24)),
 | 
				
			||||||
 | 
					                S26 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 26)),
 | 
				
			||||||
 | 
					                S28 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 28)),
 | 
				
			||||||
 | 
					                S30 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 30)),
 | 
				
			||||||
 | 
					                S32 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 32)),
 | 
				
			||||||
 | 
					                S34 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 34)),
 | 
				
			||||||
 | 
					                S36 = BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 36))
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            elements.Add(element);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new Msh01Component()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Elements = elements
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Msh01Component
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public List<SubMesh> Elements { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class SubMesh
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public byte Type1 { get; set; }
 | 
				
			||||||
 | 
					        public byte Type2 { get; set; }
 | 
				
			||||||
 | 
					        public short ParentIndex { get; set; }
 | 
				
			||||||
 | 
					        public short OffsetIntoFile13 { get; set; }
 | 
				
			||||||
 | 
					        public short IndexInFile08 { get; set; }
 | 
				
			||||||
 | 
					        public ushort State00 { get; set; }
 | 
				
			||||||
 | 
					        public ushort State01 { get; set; }
 | 
				
			||||||
 | 
					        public ushort State10 { get; set; }
 | 
				
			||||||
 | 
					        public ushort State11 { get; set; }
 | 
				
			||||||
 | 
					        public ushort State20 { get; set; }
 | 
				
			||||||
 | 
					        public ushort State21 { get; set; }
 | 
				
			||||||
 | 
					        public short S20 { get; set; }
 | 
				
			||||||
 | 
					        public short S22 { get; set; }
 | 
				
			||||||
 | 
					        public short S24 { get; set; }
 | 
				
			||||||
 | 
					        public short S26 { get; set; }
 | 
				
			||||||
 | 
					        public short S28 { get; set; }
 | 
				
			||||||
 | 
					        public short S30 { get; set; }
 | 
				
			||||||
 | 
					        public short S32 { get; set; }
 | 
				
			||||||
 | 
					        public short S34 { get; set; }
 | 
				
			||||||
 | 
					        public short S36 { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										186
									
								
								ParkanPlayground/Msh02.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								ParkanPlayground/Msh02.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					using System.Buffers.Binary;
 | 
				
			||||||
 | 
					using Common;
 | 
				
			||||||
 | 
					using NResLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ParkanPlayground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class Msh02
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static Msh02Component ReadComponent(FileStream mshFs, NResArchive archive)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var fileEntry = archive.Files.FirstOrDefault(x => x.FileType == "02 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fileEntry is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Archive doesn't contain 02 component");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = new byte[fileEntry.FileLength];
 | 
				
			||||||
 | 
					        mshFs.Seek(fileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
 | 
					        mshFs.ReadExactly(data, 0, data.Length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var header = data.AsSpan(0, 0x8c); // 140 bytes header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var center = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x60)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x64)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x68))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var bb = new BoundingBox();
 | 
				
			||||||
 | 
					        bb.Vec1 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec2 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec3 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec4 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec5 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec6 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec7 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        bb.Vec8 = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var bottom = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(112)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(116)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(120))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var top = new Vector3(
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(124)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(128)),
 | 
				
			||||||
 | 
					            BinaryPrimitives.ReadSingleLittleEndian(header.Slice(132))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var xyRadius = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(136));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<Msh02Element> elements = new List<Msh02Element>();
 | 
				
			||||||
 | 
					        var skippedHeader = data.AsSpan(0x8c); // skip header
 | 
				
			||||||
 | 
					        for (var i = 0; i < fileEntry.ElementCount; i++)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var element = new Msh02Element();
 | 
				
			||||||
 | 
					            element.StartIndexIn07 =
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 0));
 | 
				
			||||||
 | 
					            element.CountIn07 =
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 2));
 | 
				
			||||||
 | 
					            element.StartOffsetIn0d =
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 4));
 | 
				
			||||||
 | 
					            element.ByteLengthIn0D =
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 6));
 | 
				
			||||||
 | 
					            element.LocalMinimum = new Vector3(
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 8)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 12)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 16))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            element.LocalMaximum = new Vector3(
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 20)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 24)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 28))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            element.Center = new Vector3(
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 32)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 36)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 40))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            element.Vector4 = new Vector3(
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 44)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 48)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 52))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            element.Vector5 = new Vector3(
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 56)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 60)),
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 64))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            elements.Add(element);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _ = 5;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new Msh02Component()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Header = new Msh02Header()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                BoundingBox = bb,
 | 
				
			||||||
 | 
					                Center = center,
 | 
				
			||||||
 | 
					                CenterW = centerW,
 | 
				
			||||||
 | 
					                Bottom = bottom,
 | 
				
			||||||
 | 
					                Top = top,
 | 
				
			||||||
 | 
					                XYRadius = xyRadius
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Elements = elements
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Msh02Component
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public Msh02Header Header { get; set; }
 | 
				
			||||||
 | 
					        public List<Msh02Element> Elements { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Msh02Header
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public BoundingBox BoundingBox { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Center { get; set; }
 | 
				
			||||||
 | 
					        public float CenterW { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Bottom { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Top { get; set; }
 | 
				
			||||||
 | 
					        public float XYRadius { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class BoundingBox
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public Vector3 Vec1 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec2 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec3 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec4 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec5 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec6 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec7 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vec8 { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Msh02Element
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public ushort StartIndexIn07 { get; set; }
 | 
				
			||||||
 | 
					        public ushort CountIn07 { get; set; }
 | 
				
			||||||
 | 
					        public ushort StartOffsetIn0d { get; set; }
 | 
				
			||||||
 | 
					        public ushort ByteLengthIn0D { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 LocalMinimum { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 LocalMaximum { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Center { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vector4 { get; set; }
 | 
				
			||||||
 | 
					        public Vector3 Vector5 { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								ParkanPlayground/Msh06.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								ParkanPlayground/Msh06.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					using System.Buffers.Binary;
 | 
				
			||||||
 | 
					using NResLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ParkanPlayground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class Msh06
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static List<ushort> ReadComponent(
 | 
				
			||||||
 | 
					        FileStream mshFs, NResArchive archive)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var entry = archive.Files.FirstOrDefault(x => x.FileType == "06 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (entry is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Archive doesn't contain file (06)");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = new byte[entry.ElementCount * entry.ElementSize];
 | 
				
			||||||
 | 
					        mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
 | 
					        mshFs.ReadExactly(data, 0, data.Length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elements = new List<ushort>((int)entry.ElementCount);
 | 
				
			||||||
 | 
					        for (var i = 0; i < entry.ElementCount; i++)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            elements.Add(
 | 
				
			||||||
 | 
					                BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(i * 2))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return elements;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								ParkanPlayground/Msh07.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								ParkanPlayground/Msh07.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					using System.Buffers.Binary;
 | 
				
			||||||
 | 
					using NResLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ParkanPlayground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class Msh07
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static List<Msh07Element> ReadComponent(
 | 
				
			||||||
 | 
					        FileStream mshFs, NResArchive archive)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var entry = archive.Files.FirstOrDefault(x => x.FileType == "07 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (entry is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Archive doesn't contain file (07)");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = new byte[entry.ElementCount * entry.ElementSize];
 | 
				
			||||||
 | 
					        mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
 | 
					        mshFs.ReadExactly(data, 0, data.Length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elementBytes = data.Chunk(16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elements = elementBytes.Select(x => new Msh07Element()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Flags = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)),
 | 
				
			||||||
 | 
					            Magic02 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(2)),
 | 
				
			||||||
 | 
					            Magic04 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)),
 | 
				
			||||||
 | 
					            OffsetX = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(8)),
 | 
				
			||||||
 | 
					            OffsetY = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(10)),
 | 
				
			||||||
 | 
					            OffsetZ = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(12)),
 | 
				
			||||||
 | 
					            Magic14 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(14)),
 | 
				
			||||||
 | 
					        }).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return elements;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Msh07Element
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public ushort Flags { get; set; }
 | 
				
			||||||
 | 
					        public ushort Magic02 { get; set; }
 | 
				
			||||||
 | 
					        public uint Magic04 { get; set; }
 | 
				
			||||||
 | 
					        // normalized vector X, need to divide by 32767 to get float in range -1..1
 | 
				
			||||||
 | 
					        public short OffsetX { get; set; }
 | 
				
			||||||
 | 
					        // normalized vector Y, need to divide by 32767 to get float in range -1..1
 | 
				
			||||||
 | 
					        public short OffsetY { get; set; }
 | 
				
			||||||
 | 
					        // normalized vector Z, need to divide by 32767 to get float in range -1..1
 | 
				
			||||||
 | 
					        public short OffsetZ { get; set; }
 | 
				
			||||||
 | 
					        public ushort Magic14 { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								ParkanPlayground/Msh0A.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								ParkanPlayground/Msh0A.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					using System.Buffers.Binary;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using NResLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ParkanPlayground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class Msh0A
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static List<string> ReadComponent(FileStream mshFs, NResArchive archive)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var aFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "0A 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (aFileEntry is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Archive doesn't contain 0A component");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = new byte[aFileEntry.FileLength];
 | 
				
			||||||
 | 
					        mshFs.Seek(aFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
 | 
					        mshFs.ReadExactly(data, 0, data.Length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int pos = 0;
 | 
				
			||||||
 | 
					        var strings = new List<string>();
 | 
				
			||||||
 | 
					        while (pos < data.Length)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var len = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(pos));
 | 
				
			||||||
 | 
					            if (len == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                pos += 4; // empty entry, no string attached
 | 
				
			||||||
 | 
					                strings.Add(""); // add empty string
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // len is not 0, we need to read it
 | 
				
			||||||
 | 
					                var strBytes = data.AsSpan(pos + 4, len);
 | 
				
			||||||
 | 
					                var str = Encoding.UTF8.GetString(strBytes);
 | 
				
			||||||
 | 
					                strings.Add(str);
 | 
				
			||||||
 | 
					                pos += len + 4 + 1; // skip length prefix and string itself, +1, because it's null-terminated
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (strings.Count != aFileEntry.ElementCount)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("String count mismatch in 0A component");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return strings;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								ParkanPlayground/Msh0D.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								ParkanPlayground/Msh0D.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					using System.Buffers.Binary;
 | 
				
			||||||
 | 
					using NResLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ParkanPlayground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class Msh0D
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public const int ElementSize = 20;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public static List<Msh0DElement> ReadComponent(
 | 
				
			||||||
 | 
					        FileStream mshFs, NResArchive archive)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var entry = archive.Files.FirstOrDefault(x => x.FileType == "0D 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (entry is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Archive doesn't contain file (0D)");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var data = new byte[entry.ElementCount * entry.ElementSize];
 | 
				
			||||||
 | 
					        mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
 | 
					        mshFs.ReadExactly(data, 0, data.Length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elementBytes = data.Chunk(ElementSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elements = elementBytes.Select(x => new Msh0DElement()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Flags = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)),
 | 
				
			||||||
 | 
					            Magic04 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)),
 | 
				
			||||||
 | 
					            number_of_triangles = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)),
 | 
				
			||||||
 | 
					            IndexInto06 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(10)),
 | 
				
			||||||
 | 
					            Magic0C = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(14)),
 | 
				
			||||||
 | 
					            IndexInto03 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(16)),
 | 
				
			||||||
 | 
					        }).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return elements;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class Msh0DElement
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public uint Flags { get; set; }
 | 
				
			||||||
 | 
					        public uint Magic04 { get; set; }
 | 
				
			||||||
 | 
					        public ushort number_of_triangles { get; set; }
 | 
				
			||||||
 | 
					        public int IndexInto06 { get; set; }
 | 
				
			||||||
 | 
					        public ushort Magic0C { get; set; }
 | 
				
			||||||
 | 
					        public int IndexInto03 { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,9 +10,188 @@ public class MshConverter
 | 
				
			|||||||
    public void Convert(string mshPath)
 | 
					    public void Convert(string mshPath)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var mshNresResult = NResParser.ReadFile(mshPath);
 | 
					        var mshNresResult = NResParser.ReadFile(mshPath);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var mshNres = mshNresResult.Archive!;
 | 
					        var mshNres = mshNresResult.Archive!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        using var mshFs = new FileStream(mshPath, FileMode.Open, FileAccess.Read, FileShare.Read);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var component01 = Msh01.ReadComponent(mshFs, mshNres);
 | 
				
			||||||
 | 
					        var component02 = Msh02.ReadComponent(mshFs, mshNres);
 | 
				
			||||||
 | 
					        var component0A = Msh0A.ReadComponent(mshFs, mshNres);
 | 
				
			||||||
 | 
					        var component07 = Msh07.ReadComponent(mshFs, mshNres);
 | 
				
			||||||
 | 
					        var component0D = Msh0D.ReadComponent(mshFs, mshNres);
 | 
				
			||||||
 | 
					        var component06 = Msh06.ReadComponent(mshFs, mshNres);
 | 
				
			||||||
 | 
					        var component03 = Read03Component(mshFs, mshNres);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // --- Write OBJ ---
 | 
				
			||||||
 | 
					        using var sw = new StreamWriter("test.obj", false, Encoding.UTF8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach (var v in component03)
 | 
				
			||||||
 | 
					            sw.WriteLine($"v {v.X:F8} {v.Y:F8} {v.Z:F8}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var vertices = new List<Vector3>();
 | 
				
			||||||
 | 
					        var faces = new List<(int, int, int)>(); // store indices into vertices list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (var pieceIndex = 0; pieceIndex < component01.Elements.Count; pieceIndex++)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var piece = component01.Elements[pieceIndex];
 | 
				
			||||||
 | 
					            var state = (piece.State00 == 0xffff) ? 0 : piece.State00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var mesh02Element = component02.Elements[state];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int indexInto07 = mesh02Element.StartIndexIn07;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var element0Dstart = mesh02Element.StartOffsetIn0d;
 | 
				
			||||||
 | 
					            var element0Dcount = mesh02Element.ByteLengthIn0D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Console.WriteLine($"Started piece {pieceIndex}. State={state}. 0D start={element0Dstart}, count={element0Dcount}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (var comp0Dindex = 0; comp0Dindex < element0Dcount; comp0Dindex++)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var element0D = component0D[element0Dstart + comp0Dindex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var indexInto03 = element0D.IndexInto03;
 | 
				
			||||||
 | 
					                var indexInto06 = element0D.IndexInto06;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var count = (uint)element0D.number_of_triangles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Convert IndexInto06 to ushort array index (3 ushorts per triangle)
 | 
				
			||||||
 | 
					                Console.WriteLine(
 | 
				
			||||||
 | 
					                    $"Processing 0D element[{element0Dstart + comp0Dindex}]. IndexInto03={indexInto03}, IndexInto06={indexInto06}. Number of triangles={count}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (count != 0)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    sw.WriteLine($"o piece_{pieceIndex++}_of_mesh_{comp0Dindex}");
 | 
				
			||||||
 | 
					                    if (count % 3 != 0)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Console.WriteLine("Number of triangles is not multiple of 3");
 | 
				
			||||||
 | 
					                        _ = 5;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    count = (count + 2) / 3; // number of triangles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    for (int tri = 0; tri < count; tri++)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        // Each triangle uses 3 consecutive ushorts in component06
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var comp07 = component07[indexInto07];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var i1 = indexInto03 + component06[indexInto06];
 | 
				
			||||||
 | 
					                        var i2 = indexInto03 + component06[indexInto06 + 1];
 | 
				
			||||||
 | 
					                        var i3 = indexInto03 + component06[indexInto06 + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var v1 = component03[i1];
 | 
				
			||||||
 | 
					                        var v2 = component03[i2];
 | 
				
			||||||
 | 
					                        var v3 = component03[i3];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        sw.WriteLine($"f {i1 + 1} {i2 + 1} {i3 + 1}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // push vertices to global list
 | 
				
			||||||
 | 
					                        vertices.Add(v1);
 | 
				
			||||||
 | 
					                        vertices.Add(v2);
 | 
				
			||||||
 | 
					                        vertices.Add(v3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        int baseIndex = vertices.Count;
 | 
				
			||||||
 | 
					                        // record face (OBJ is 1-based indexing!)
 | 
				
			||||||
 | 
					                        faces.Add((baseIndex - 2, baseIndex - 1, baseIndex));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        indexInto07++;
 | 
				
			||||||
 | 
					                        indexInto06 += 3; // step by 3 since each triangle uses 3 ushorts
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _ = 5;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public record Face(Vector3 P1, Vector3 P2, Vector3 P3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static void ExportCube(string filePath, Vector3[] points)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (points.Length != 8)
 | 
				
			||||||
 | 
					            throw new ArgumentException("Cube must have exactly 8 points.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        using (StreamWriter writer = new StreamWriter(filePath))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Write vertices
 | 
				
			||||||
 | 
					            foreach (var p in points)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                writer.WriteLine($"v {p.X} {p.Y} {p.Z}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write faces (each face defined by 4 vertices, using 1-based indices)
 | 
				
			||||||
 | 
					            int[][] faces = new int[][]
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                new int[] { 1, 2, 3, 4 }, // bottom
 | 
				
			||||||
 | 
					                new int[] { 5, 6, 7, 8 }, // top
 | 
				
			||||||
 | 
					                new int[] { 1, 2, 6, 5 }, // front
 | 
				
			||||||
 | 
					                new int[] { 2, 3, 7, 6 }, // right
 | 
				
			||||||
 | 
					                new int[] { 3, 4, 8, 7 }, // back
 | 
				
			||||||
 | 
					                new int[] { 4, 1, 5, 8 } // left
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var f in faces)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                writer.WriteLine($"f {f[0]} {f[1]} {f[2]} {f[3]}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static void ExportCubesAtPositions(string filePath, List<Vector3> centers, float size = 2f)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        float half = size / 2f;
 | 
				
			||||||
 | 
					        using (StreamWriter writer = new StreamWriter(filePath))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            int vertexOffset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var c in centers)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Generate 8 vertices for this cube
 | 
				
			||||||
 | 
					                Vector3[] vertices = new Vector3[]
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    new Vector3(c.X - half, c.Y - half, c.Z - half),
 | 
				
			||||||
 | 
					                    new Vector3(c.X + half, c.Y - half, c.Z - half),
 | 
				
			||||||
 | 
					                    new Vector3(c.X + half, c.Y - half, c.Z + half),
 | 
				
			||||||
 | 
					                    new Vector3(c.X - half, c.Y - half, c.Z + half),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    new Vector3(c.X - half, c.Y + half, c.Z - half),
 | 
				
			||||||
 | 
					                    new Vector3(c.X + half, c.Y + half, c.Z - half),
 | 
				
			||||||
 | 
					                    new Vector3(c.X + half, c.Y + half, c.Z + half),
 | 
				
			||||||
 | 
					                    new Vector3(c.X - half, c.Y + half, c.Z + half)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Write vertices
 | 
				
			||||||
 | 
					                foreach (var v in vertices)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    writer.WriteLine($"v {v.X} {v.Y} {v.Z}");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Define faces (1-based indices, counter-clockwise)
 | 
				
			||||||
 | 
					                int[][] faces = new int[][]
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    new int[] { 1, 2, 3, 4 }, // bottom
 | 
				
			||||||
 | 
					                    new int[] { 5, 6, 7, 8 }, // top
 | 
				
			||||||
 | 
					                    new int[] { 1, 2, 6, 5 }, // front
 | 
				
			||||||
 | 
					                    new int[] { 2, 3, 7, 6 }, // right
 | 
				
			||||||
 | 
					                    new int[] { 3, 4, 8, 7 }, // back
 | 
				
			||||||
 | 
					                    new int[] { 4, 1, 5, 8 } // left
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Write faces with offset
 | 
				
			||||||
 | 
					                foreach (var f in faces)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    writer.WriteLine(
 | 
				
			||||||
 | 
					                        $"f {f[0] + vertexOffset} {f[1] + vertexOffset} {f[2] + vertexOffset} {f[3] + vertexOffset}");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                vertexOffset += 8;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static List<Vector3> Read03Component(FileStream mshFs, NResArchive mshNres)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        var verticesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "03 00 00 00");
 | 
					        var verticesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "03 00 00 00");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (verticesFileEntry is null)
 | 
					        if (verticesFileEntry is null)
 | 
				
			||||||
@@ -25,103 +204,6 @@ public class MshConverter
 | 
				
			|||||||
            throw new Exception("Vertices file (03) element size is not 12");
 | 
					            throw new Exception("Vertices file (03) element size is not 12");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        using var mshFs = new FileStream(mshPath, FileMode.Open, FileAccess.Read, FileShare.Read);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        ReadComponent01(mshFs, mshNres);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var vertices = ReadVertices(verticesFileEntry, mshFs);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var edgesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "06 00 00 00");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (edgesFileEntry is null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            throw new Exception("Archive doesn't contain edges file (06)");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        var edgesFile = new byte[edgesFileEntry.ElementCount * edgesFileEntry.ElementSize];
 | 
					 | 
				
			||||||
        mshFs.Seek(edgesFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
					 | 
				
			||||||
        mshFs.ReadExactly(edgesFile, 0, edgesFile.Length);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var edges = new List<IndexedEdge>((int)edgesFileEntry.ElementCount / 2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (int i = 0; i < edgesFileEntry.ElementCount / 2; i++)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var index1 = BinaryPrimitives.ReadUInt16LittleEndian(edgesFile.AsSpan().Slice(i * 2));
 | 
					 | 
				
			||||||
            var index2 = BinaryPrimitives.ReadUInt16LittleEndian(edgesFile.AsSpan().Slice(i * 2 + 2));
 | 
					 | 
				
			||||||
            edges.Add(new IndexedEdge(index1, index2));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Export($"{Path.GetFileNameWithoutExtension(mshPath)}.obj", vertices, edges);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static List<string> TryRead0AComponent(FileStream mshFs, NResArchive archive)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var aFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "0A 00 00 00");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (aFileEntry is null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return [];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        var data = new byte[aFileEntry.FileLength];
 | 
					 | 
				
			||||||
        mshFs.Seek(aFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
					 | 
				
			||||||
        mshFs.ReadExactly(data, 0, data.Length);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int pos = 0;
 | 
					 | 
				
			||||||
        var strings = new List<string>();
 | 
					 | 
				
			||||||
        while (pos < data.Length)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var len = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(pos));
 | 
					 | 
				
			||||||
            if (len == 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                pos += 4; // empty entry, no string attached
 | 
					 | 
				
			||||||
                strings.Add(""); // add empty string
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // len is not 0, we need to read it
 | 
					 | 
				
			||||||
                var strBytes = data.AsSpan(pos + 4, len);
 | 
					 | 
				
			||||||
                var str = Encoding.UTF8.GetString(strBytes);
 | 
					 | 
				
			||||||
                strings.Add(str);
 | 
					 | 
				
			||||||
                pos += len + 4 + 1; // skip length prefix and string itself, +1, because it's null-terminated
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (strings.Count != aFileEntry.ElementCount)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            throw new Exception("String count mismatch in 0A component");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return strings;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static void ReadComponent01(FileStream mshFs, NResArchive archive)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var headerFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "01 00 00 00");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (headerFileEntry is null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            throw new Exception("Archive doesn't contain header file (01)");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        var headerData = new byte[headerFileEntry.ElementCount * headerFileEntry.ElementSize];
 | 
					 | 
				
			||||||
        mshFs.Seek(headerFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
					 | 
				
			||||||
        mshFs.ReadExactly(headerData, 0, headerData.Length);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        var descriptions = TryRead0AComponent(mshFs, archive);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        var chunks = headerData.Chunk(headerFileEntry.ElementSize).ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var converted = chunks.Select(x => new
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Byte00 = x[0],
 | 
					 | 
				
			||||||
            Byte01 = x[1],
 | 
					 | 
				
			||||||
            Bytes0204 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(2)),
 | 
					 | 
				
			||||||
        }).ToList();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        _ = 5;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static List<Vector3> ReadVertices(ListMetadataItem verticesFileEntry, FileStream mshFs)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var verticesFile = new byte[verticesFileEntry.ElementCount * verticesFileEntry.ElementSize];
 | 
					        var verticesFile = new byte[verticesFileEntry.ElementCount * verticesFileEntry.ElementSize];
 | 
				
			||||||
        mshFs.Seek(verticesFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
					        mshFs.Seek(verticesFileEntry.OffsetInFile, SeekOrigin.Begin);
 | 
				
			||||||
        mshFs.ReadExactly(verticesFile, 0, verticesFile.Length);
 | 
					        mshFs.ReadExactly(verticesFile, 0, verticesFile.Length);
 | 
				
			||||||
@@ -135,7 +217,7 @@ public class MshConverter
 | 
				
			|||||||
        return vertices;
 | 
					        return vertices;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void Export(string filePath, List<Vector3> vertices, List<IndexedEdge> edges)
 | 
					    void Export(string filePath, IEnumerable<Vector3> vertices, List<IndexedEdge> edges)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        using (var writer = new StreamWriter(filePath))
 | 
					        using (var writer = new StreamWriter(filePath))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,6 @@ using ParkanPlayground;
 | 
				
			|||||||
var converter = new MshConverter();
 | 
					var converter = new MshConverter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\133_fr_m_bunker.msh");
 | 
					converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\133_fr_m_bunker.msh");
 | 
				
			||||||
 | 
					// converter.Convert("E:\\ParkanUnpacked\\intsys.rlb\\277_MESH_o_pws_l_01.msh");
 | 
				
			||||||
 | 
					// converter.Convert("E:\\ParkanUnpacked\\static.rlb\\2_MESH_s_stn_0_01.msh");
 | 
				
			||||||
 | 
					// converter.Convert("E:\\ParkanUnpacked\\bases.rlb\\25_MESH_R_H_02.msh");
 | 
				
			||||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							@@ -214,14 +214,14 @@ grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
 | 
				
			|||||||
- Тип 01 - заголовок
 | 
					- Тип 01 - заголовок
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
  нулевому элементу добавляется флаг 0x1000000
 | 
					  нулевому элементу добавляется флаг 0x1000000
 | 
				
			||||||
  Хранит куски меша.
 | 
					  Хранит стейты меша (в один стейт может входить несколько submesh)
 | 
				
			||||||
  Содержит 2 ссылки на файлы анимаций (короткие - файл 13, длинные - файл 08)
 | 
					  Содержит 2 ссылки на файлы анимаций (короткие - файл 13, длинные - файл 08)
 | 
				
			||||||
  Если интерполируется анимация -0.5s короче чем magic1 у файла 13
 | 
					  Если интерполируется анимация -0.5s короче чем magic1 у файла 13
 | 
				
			||||||
  И у файла есть OffsetIntoFile13
 | 
					  И у файла есть OffsetIntoFile13
 | 
				
			||||||
  И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда)
 | 
					  И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда)
 | 
				
			||||||
  Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт)
 | 
					  Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт)
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
- Тип 02
 | 
					- Тип 02 - описание submesh
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
  Вначале идёт заголовок 0x8C (140) байт
 | 
					  Вначале идёт заголовок 0x8C (140) байт
 | 
				
			||||||
  В заголовке:
 | 
					  В заголовке:
 | 
				
			||||||
@@ -230,9 +230,10 @@ grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
 | 
				
			|||||||
  1 Vector3 - bottom
 | 
					  1 Vector3 - bottom
 | 
				
			||||||
  1 Vector3 - top
 | 
					  1 Vector3 - top
 | 
				
			||||||
  1 float - xy_radius
 | 
					  1 float - xy_radius
 | 
				
			||||||
 | 
					  Далее инфа про куски меша
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
- Тип 03 - это вершины (vertex)
 | 
					- Тип 03 - это вершины (vertex)
 | 
				
			||||||
- Тип 06 - это то ли рёбра, то ли треугольники - не понятно
 | 
					- Тип 06 - 
 | 
				
			||||||
- Тип 04 - скорее всего какие-то цвета RGBA или типа того
 | 
					- Тип 04 - скорее всего какие-то цвета RGBA или типа того
 | 
				
			||||||
- Тип 08 - меш-анимации (см файл 01)
 | 
					- Тип 08 - меш-анимации (см файл 01)
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
@@ -251,7 +252,7 @@ grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
 | 
				
			|||||||
  Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time)
 | 
					  Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time)
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
- Тип 12 - microtexture mapping
 | 
					- Тип 12 - microtexture mapping
 | 
				
			||||||
- Тип 13 - короткие меш-анимации
 | 
					- Тип 13 - короткие меш-анимации (почему я это не дописал?)
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
  Буквально (hex)
 | 
					  Буквально (hex)
 | 
				
			||||||
  00 01 01 02 ...
 | 
					  00 01 01 02 ...
 | 
				
			||||||
@@ -275,10 +276,6 @@ grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Загружается в `World3D.dll/LoadMatManager`
 | 
					Загружается в `World3D.dll/LoadMatManager`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## `.wea`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Загружается в `World3D.dll/LoadMatManager`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Внутренняя система ID
 | 
					# Внутренняя система ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `1` - IMesh2 ???
 | 
					- `1` - IMesh2 ???
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user