mirror of
				https://github.com/sampletext32/ParkanPlayground.git
				synced 2025-10-31 13:29:45 +03:00 
			
		
		
		
	Compare commits
	
		
			27 Commits
		
	
	
		
			backup2611
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a774db37a6 | ||
|   | c1ea70efe0 | ||
|   | be60d8d72f | ||
|   | f2bed4b141 | ||
|   | 7f0246f996 | ||
|   | 055694a4b4 | ||
|   | fca052365f | ||
|   | 5c52ab2b2b | ||
|   | 77e7f7652c | ||
|   | 35af4da326 | ||
|   | 4b1c4bf3aa | ||
|   | 4ea756a1a4 | ||
|   | b9e15541c5 | ||
|   | 67c9020b96 | ||
|   | 476017e9c1 | ||
|   | ee77738713 | ||
|   | 8e31f43abf | ||
|   | f5bacc018c | ||
|   | a6057bf072 | ||
|   | a419be1fce | ||
|   | 8c4fc8f096 | ||
|   | 135777a4c6 | ||
|   | 76ef68635e | ||
|   | d7eb23e9e0 | ||
|   | b47a9aff5d | ||
|   | ba7c2afe2a | ||
|   | c50512ea52 | 
							
								
								
									
										3
									
								
								Common/Common.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Common/Common.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										83
									
								
								Common/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								Common/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | using System.Buffers.Binary; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace Common; | ||||||
|  |  | ||||||
|  | public static class Extensions | ||||||
|  | { | ||||||
|  |     public static int ReadInt32LittleEndian(this Stream fs) | ||||||
|  |     { | ||||||
|  |         Span<byte> buf = stackalloc byte[4]; | ||||||
|  |         fs.ReadExactly(buf); | ||||||
|  |  | ||||||
|  |         return BinaryPrimitives.ReadInt32LittleEndian(buf); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static uint ReadUInt32LittleEndian(this Stream fs) | ||||||
|  |     { | ||||||
|  |         Span<byte> buf = stackalloc byte[4]; | ||||||
|  |         fs.ReadExactly(buf); | ||||||
|  |  | ||||||
|  |         return BinaryPrimitives.ReadUInt32LittleEndian(buf); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static float ReadFloatLittleEndian(this Stream fs) | ||||||
|  |     { | ||||||
|  |         Span<byte> buf = stackalloc byte[4]; | ||||||
|  |         fs.ReadExactly(buf); | ||||||
|  |  | ||||||
|  |         return BinaryPrimitives.ReadSingleLittleEndian(buf); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static string ReadNullTerminatedString(this Stream fs) | ||||||
|  |     { | ||||||
|  |         var sb = new StringBuilder(); | ||||||
|  |  | ||||||
|  |         while (true) | ||||||
|  |         { | ||||||
|  |             var b = fs.ReadByte(); | ||||||
|  |             if (b == 0) | ||||||
|  |             { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             sb.Append((char)b); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return sb.ToString(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static string ReadNullTerminated1251String(this Stream fs) | ||||||
|  |     { | ||||||
|  |         var sb = new StringBuilder(); | ||||||
|  |  | ||||||
|  |         while (true) | ||||||
|  |         { | ||||||
|  |             var b = (byte)fs.ReadByte(); | ||||||
|  |             if (b == 0) | ||||||
|  |             { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             sb.Append(Encoding.GetEncoding("windows-1251").GetString([b])); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return sb.ToString(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static string ReadLengthPrefixedString(this Stream fs) | ||||||
|  |     { | ||||||
|  |         var len = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |         if (len == 0) | ||||||
|  |         { | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var buffer = new byte[len]; | ||||||
|  |  | ||||||
|  |         fs.ReadExactly(buffer, 0, len); | ||||||
|  |  | ||||||
|  |         return Encoding.ASCII.GetString(buffer, 0, len); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								Common/IndexedEdge.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Common/IndexedEdge.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace Common; | ||||||
|  |  | ||||||
|  | public record IndexedEdge(ushort Index1, ushort Index2); | ||||||
| @@ -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) | ||||||
							
								
								
									
										3
									
								
								Common/Vector3.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Common/Vector3.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace Common; | ||||||
|  |  | ||||||
|  | public record Vector3(float X, float Y, float Z); | ||||||
							
								
								
									
										31
									
								
								CpDatLib/CpDatEntry.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								CpDatLib/CpDatEntry.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | namespace CpDatLib; | ||||||
|  |  | ||||||
|  | public record CpDatEntry( | ||||||
|  |     string ArchiveFile, | ||||||
|  |     string ArchiveEntryName, | ||||||
|  |     int Magic1, | ||||||
|  |     int Magic2, | ||||||
|  |     string Description, | ||||||
|  |     DatEntryType Type, | ||||||
|  |     int ChildCount, // игра не хранит это число в объекте, но оно есть в файле | ||||||
|  |     List<CpDatEntry> Children | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | // Magic3 seems to be a type | ||||||
|  | // 0 - chassis | ||||||
|  | // 1 - turret (у зданий почему-то дефлектор тоже 1), может быть потому, что дефлектор вращается так же как башня у юнитов | ||||||
|  | // 2 - armour | ||||||
|  | // 3 - part | ||||||
|  | // 4 - cannon | ||||||
|  | // 5 - ammo | ||||||
|  |  | ||||||
|  | public enum DatEntryType | ||||||
|  | { | ||||||
|  |     Unspecified = -1, | ||||||
|  |     Chassis = 0, | ||||||
|  |     Turret = 1, | ||||||
|  |     Armour = 2, | ||||||
|  |     Part = 3, | ||||||
|  |     Cannon = 4, | ||||||
|  |     Ammo = 5, | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								CpDatLib/CpDatLib.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								CpDatLib/CpDatLib.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\Common\Common.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
							
								
								
									
										3
									
								
								CpDatLib/CpDatParseResult.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								CpDatLib/CpDatParseResult.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace CpDatLib; | ||||||
|  |  | ||||||
|  | public record CpDatParseResult(CpDatScheme? Scheme, string? Error); | ||||||
							
								
								
									
										74
									
								
								CpDatLib/CpDatParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								CpDatLib/CpDatParser.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | using Common; | ||||||
|  |  | ||||||
|  | namespace CpDatLib; | ||||||
|  |  | ||||||
|  | public class CpDatParser | ||||||
|  | { | ||||||
|  |     public static CpDatParseResult Parse(string filePath) | ||||||
|  |     { | ||||||
|  |         using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); | ||||||
|  |  | ||||||
|  |         return Parse(fs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static CpDatParseResult Parse(Stream fs) | ||||||
|  |     { | ||||||
|  |         Span<byte> f0f1 = stackalloc byte[4]; | ||||||
|  |  | ||||||
|  |         if (fs.Length < 8) | ||||||
|  |             return new CpDatParseResult(null, "File too small to be a valid \"cp\" .dat file."); | ||||||
|  |  | ||||||
|  |         fs.ReadExactly(f0f1); | ||||||
|  |  | ||||||
|  |         if (f0f1[0] != 0xf1 || f0f1[1] != 0xf0) | ||||||
|  |         { | ||||||
|  |             return new CpDatParseResult(null, "File does not start with expected header bytes f1_f0"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var schemeType = (SchemeType)fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |         var entryLength = | ||||||
|  |             0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов | ||||||
|  |         if ((fs.Length - 8) % entryLength != 0) | ||||||
|  |         { | ||||||
|  |             return new CpDatParseResult(null, "File size is not valid according to expected entry length."); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         CpDatEntry root = ReadEntryRecursive(fs); | ||||||
|  |  | ||||||
|  |         var scheme = new CpDatScheme(schemeType, root); | ||||||
|  |  | ||||||
|  |         return new CpDatParseResult(scheme, null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static CpDatEntry ReadEntryRecursive(Stream fs) | ||||||
|  |     { | ||||||
|  |         var str1 = fs.ReadNullTerminatedString(); | ||||||
|  |  | ||||||
|  |         fs.Seek(32 - str1.Length - 1, SeekOrigin.Current); // -1 ignore null terminator | ||||||
|  |  | ||||||
|  |         var str2 = fs.ReadNullTerminatedString(); | ||||||
|  |  | ||||||
|  |         fs.Seek(32 - str2.Length - 1, SeekOrigin.Current); // -1 ignore null terminator | ||||||
|  |         var magic1 = fs.ReadInt32LittleEndian(); | ||||||
|  |         var magic2 = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |         var descriptionString = fs.ReadNullTerminated1251String(); | ||||||
|  |  | ||||||
|  |         fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current); // -1 ignore null terminator | ||||||
|  |         var type = (DatEntryType)fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |         // игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов | ||||||
|  |         var childCount = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |         List<CpDatEntry> children = new List<CpDatEntry>(childCount); | ||||||
|  |  | ||||||
|  |         for (var i = 0; i < childCount; i++) | ||||||
|  |         { | ||||||
|  |             var child = ReadEntryRecursive(fs); | ||||||
|  |             children.Add(child); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new CpDatEntry(str1, str2, magic1, magic2, descriptionString, type, childCount, Children: children); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								CpDatLib/CpDatScheme.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								CpDatLib/CpDatScheme.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace CpDatLib; | ||||||
|  |  | ||||||
|  | public record CpDatScheme(SchemeType Type, CpDatEntry Root); | ||||||
							
								
								
									
										29
									
								
								CpDatLib/CpEntryType.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								CpDatLib/CpEntryType.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | namespace CpDatLib; | ||||||
|  |  | ||||||
|  | public enum SchemeType : uint | ||||||
|  | { | ||||||
|  |     ClassBuilding = 0x80000000, | ||||||
|  |     ClassRobot = 0x01000000, | ||||||
|  |     ClassAnimal = 0x20000000, | ||||||
|  |  | ||||||
|  |     BunkerSmall = 0x80010000, | ||||||
|  |     BunkerMedium = 0x80020000, | ||||||
|  |     BunkerLarge = 0x80040000, | ||||||
|  |     Generator = 0x80000002, | ||||||
|  |     Mine = 0x80000004, | ||||||
|  |     Storage = 0x80000008, | ||||||
|  |     Plant = 0x80000010, | ||||||
|  |     Hangar = 0x80000040, | ||||||
|  |     TowerMedium = 0x80100000, | ||||||
|  |     TowerLarge = 0x80200000, | ||||||
|  |     MainTeleport = 0x80000200, | ||||||
|  |     Institute = 0x80000400, | ||||||
|  |     Bridge = 0x80001000, | ||||||
|  |     Ruine = 0x80002000, | ||||||
|  |  | ||||||
|  |     RobotTransport = 0x01002000, | ||||||
|  |     RobotBuilder = 0x01004000, | ||||||
|  |     RobotBattleunit = 0x01008000, | ||||||
|  |     RobotHq = 0x01010000, | ||||||
|  |     RobotHero = 0x01020000, | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								Directory.Build.props
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Directory.Build.props
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <Project> | ||||||
|  |     <PropertyGroup> | ||||||
|  |         <TargetFramework>net9.0</TargetFramework> | ||||||
|  |         <ImplicitUsings>enable</ImplicitUsings> | ||||||
|  |         <Nullable>enable</Nullable> | ||||||
|  |         <LangVersion>latest</LangVersion> | ||||||
|  |         <GenerateDocumentationFile>true</GenerateDocumentationFile> | ||||||
|  |  | ||||||
|  |         <!-- Enable Central Package Management --> | ||||||
|  |         <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> | ||||||
|  |         <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled> | ||||||
|  |  | ||||||
|  |         <!-- Enforce package version consistency --> | ||||||
|  |         <EnablePackageVersionOverride>false</EnablePackageVersionOverride> | ||||||
|  |  | ||||||
|  |         <!-- Suppress package version warnings --> | ||||||
|  |         <NoWarn>$(NoWarn);NU1507;CS1591</NoWarn> | ||||||
|  |     </PropertyGroup> | ||||||
|  | </Project> | ||||||
							
								
								
									
										16
									
								
								Directory.Packages.props
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Directory.Packages.props
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <Project> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> | ||||||
|  |   </PropertyGroup> | ||||||
|  |   <!-- Package versions used across the solution --> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" /> | ||||||
|  |  | ||||||
|  |     <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" /> | ||||||
|  |     <PackageVersion Include="NativeFileDialogSharp" Version="0.5.0" /> | ||||||
|  |     <PackageVersion Include="Silk.NET" Version="2.22.0" /> | ||||||
|  |     <PackageVersion Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" /> | ||||||
|  |  | ||||||
|  |     <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
							
								
								
									
										373
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,373 @@ | |||||||
|  | Mozilla Public License Version 2.0 | ||||||
|  | ================================== | ||||||
|  |  | ||||||
|  | 1. Definitions | ||||||
|  | -------------- | ||||||
|  |  | ||||||
|  | 1.1. "Contributor" | ||||||
|  |     means each individual or legal entity that creates, contributes to | ||||||
|  |     the creation of, or owns Covered Software. | ||||||
|  |  | ||||||
|  | 1.2. "Contributor Version" | ||||||
|  |     means the combination of the Contributions of others (if any) used | ||||||
|  |     by a Contributor and that particular Contributor's Contribution. | ||||||
|  |  | ||||||
|  | 1.3. "Contribution" | ||||||
|  |     means Covered Software of a particular Contributor. | ||||||
|  |  | ||||||
|  | 1.4. "Covered Software" | ||||||
|  |     means Source Code Form to which the initial Contributor has attached | ||||||
|  |     the notice in Exhibit A, the Executable Form of such Source Code | ||||||
|  |     Form, and Modifications of such Source Code Form, in each case | ||||||
|  |     including portions thereof. | ||||||
|  |  | ||||||
|  | 1.5. "Incompatible With Secondary Licenses" | ||||||
|  |     means | ||||||
|  |  | ||||||
|  |     (a) that the initial Contributor has attached the notice described | ||||||
|  |         in Exhibit B to the Covered Software; or | ||||||
|  |  | ||||||
|  |     (b) that the Covered Software was made available under the terms of | ||||||
|  |         version 1.1 or earlier of the License, but not also under the | ||||||
|  |         terms of a Secondary License. | ||||||
|  |  | ||||||
|  | 1.6. "Executable Form" | ||||||
|  |     means any form of the work other than Source Code Form. | ||||||
|  |  | ||||||
|  | 1.7. "Larger Work" | ||||||
|  |     means a work that combines Covered Software with other material, in | ||||||
|  |     a separate file or files, that is not Covered Software. | ||||||
|  |  | ||||||
|  | 1.8. "License" | ||||||
|  |     means this document. | ||||||
|  |  | ||||||
|  | 1.9. "Licensable" | ||||||
|  |     means having the right to grant, to the maximum extent possible, | ||||||
|  |     whether at the time of the initial grant or subsequently, any and | ||||||
|  |     all of the rights conveyed by this License. | ||||||
|  |  | ||||||
|  | 1.10. "Modifications" | ||||||
|  |     means any of the following: | ||||||
|  |  | ||||||
|  |     (a) any file in Source Code Form that results from an addition to, | ||||||
|  |         deletion from, or modification of the contents of Covered | ||||||
|  |         Software; or | ||||||
|  |  | ||||||
|  |     (b) any new file in Source Code Form that contains any Covered | ||||||
|  |         Software. | ||||||
|  |  | ||||||
|  | 1.11. "Patent Claims" of a Contributor | ||||||
|  |     means any patent claim(s), including without limitation, method, | ||||||
|  |     process, and apparatus claims, in any patent Licensable by such | ||||||
|  |     Contributor that would be infringed, but for the grant of the | ||||||
|  |     License, by the making, using, selling, offering for sale, having | ||||||
|  |     made, import, or transfer of either its Contributions or its | ||||||
|  |     Contributor Version. | ||||||
|  |  | ||||||
|  | 1.12. "Secondary License" | ||||||
|  |     means either the GNU General Public License, Version 2.0, the GNU | ||||||
|  |     Lesser General Public License, Version 2.1, the GNU Affero General | ||||||
|  |     Public License, Version 3.0, or any later versions of those | ||||||
|  |     licenses. | ||||||
|  |  | ||||||
|  | 1.13. "Source Code Form" | ||||||
|  |     means the form of the work preferred for making modifications. | ||||||
|  |  | ||||||
|  | 1.14. "You" (or "Your") | ||||||
|  |     means an individual or a legal entity exercising rights under this | ||||||
|  |     License. For legal entities, "You" includes any entity that | ||||||
|  |     controls, is controlled by, or is under common control with You. For | ||||||
|  |     purposes of this definition, "control" means (a) the power, direct | ||||||
|  |     or indirect, to cause the direction or management of such entity, | ||||||
|  |     whether by contract or otherwise, or (b) ownership of more than | ||||||
|  |     fifty percent (50%) of the outstanding shares or beneficial | ||||||
|  |     ownership of such entity. | ||||||
|  |  | ||||||
|  | 2. License Grants and Conditions | ||||||
|  | -------------------------------- | ||||||
|  |  | ||||||
|  | 2.1. Grants | ||||||
|  |  | ||||||
|  | Each Contributor hereby grants You a world-wide, royalty-free, | ||||||
|  | non-exclusive license: | ||||||
|  |  | ||||||
|  | (a) under intellectual property rights (other than patent or trademark) | ||||||
|  |     Licensable by such Contributor to use, reproduce, make available, | ||||||
|  |     modify, display, perform, distribute, and otherwise exploit its | ||||||
|  |     Contributions, either on an unmodified basis, with Modifications, or | ||||||
|  |     as part of a Larger Work; and | ||||||
|  |  | ||||||
|  | (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||||
|  |     for sale, have made, import, and otherwise transfer either its | ||||||
|  |     Contributions or its Contributor Version. | ||||||
|  |  | ||||||
|  | 2.2. Effective Date | ||||||
|  |  | ||||||
|  | The licenses granted in Section 2.1 with respect to any Contribution | ||||||
|  | become effective for each Contribution on the date the Contributor first | ||||||
|  | distributes such Contribution. | ||||||
|  |  | ||||||
|  | 2.3. Limitations on Grant Scope | ||||||
|  |  | ||||||
|  | The licenses granted in this Section 2 are the only rights granted under | ||||||
|  | this License. No additional rights or licenses will be implied from the | ||||||
|  | distribution or licensing of Covered Software under this License. | ||||||
|  | Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||||
|  | Contributor: | ||||||
|  |  | ||||||
|  | (a) for any code that a Contributor has removed from Covered Software; | ||||||
|  |     or | ||||||
|  |  | ||||||
|  | (b) for infringements caused by: (i) Your and any other third party's | ||||||
|  |     modifications of Covered Software, or (ii) the combination of its | ||||||
|  |     Contributions with other software (except as part of its Contributor | ||||||
|  |     Version); or | ||||||
|  |  | ||||||
|  | (c) under Patent Claims infringed by Covered Software in the absence of | ||||||
|  |     its Contributions. | ||||||
|  |  | ||||||
|  | This License does not grant any rights in the trademarks, service marks, | ||||||
|  | or logos of any Contributor (except as may be necessary to comply with | ||||||
|  | the notice requirements in Section 3.4). | ||||||
|  |  | ||||||
|  | 2.4. Subsequent Licenses | ||||||
|  |  | ||||||
|  | No Contributor makes additional grants as a result of Your choice to | ||||||
|  | distribute the Covered Software under a subsequent version of this | ||||||
|  | License (see Section 10.2) or under the terms of a Secondary License (if | ||||||
|  | permitted under the terms of Section 3.3). | ||||||
|  |  | ||||||
|  | 2.5. Representation | ||||||
|  |  | ||||||
|  | Each Contributor represents that the Contributor believes its | ||||||
|  | Contributions are its original creation(s) or it has sufficient rights | ||||||
|  | to grant the rights to its Contributions conveyed by this License. | ||||||
|  |  | ||||||
|  | 2.6. Fair Use | ||||||
|  |  | ||||||
|  | This License is not intended to limit any rights You have under | ||||||
|  | applicable copyright doctrines of fair use, fair dealing, or other | ||||||
|  | equivalents. | ||||||
|  |  | ||||||
|  | 2.7. Conditions | ||||||
|  |  | ||||||
|  | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||||
|  | in Section 2.1. | ||||||
|  |  | ||||||
|  | 3. Responsibilities | ||||||
|  | ------------------- | ||||||
|  |  | ||||||
|  | 3.1. Distribution of Source Form | ||||||
|  |  | ||||||
|  | All distribution of Covered Software in Source Code Form, including any | ||||||
|  | Modifications that You create or to which You contribute, must be under | ||||||
|  | the terms of this License. You must inform recipients that the Source | ||||||
|  | Code Form of the Covered Software is governed by the terms of this | ||||||
|  | License, and how they can obtain a copy of this License. You may not | ||||||
|  | attempt to alter or restrict the recipients' rights in the Source Code | ||||||
|  | Form. | ||||||
|  |  | ||||||
|  | 3.2. Distribution of Executable Form | ||||||
|  |  | ||||||
|  | If You distribute Covered Software in Executable Form then: | ||||||
|  |  | ||||||
|  | (a) such Covered Software must also be made available in Source Code | ||||||
|  |     Form, as described in Section 3.1, and You must inform recipients of | ||||||
|  |     the Executable Form how they can obtain a copy of such Source Code | ||||||
|  |     Form by reasonable means in a timely manner, at a charge no more | ||||||
|  |     than the cost of distribution to the recipient; and | ||||||
|  |  | ||||||
|  | (b) You may distribute such Executable Form under the terms of this | ||||||
|  |     License, or sublicense it under different terms, provided that the | ||||||
|  |     license for the Executable Form does not attempt to limit or alter | ||||||
|  |     the recipients' rights in the Source Code Form under this License. | ||||||
|  |  | ||||||
|  | 3.3. Distribution of a Larger Work | ||||||
|  |  | ||||||
|  | You may create and distribute a Larger Work under terms of Your choice, | ||||||
|  | provided that You also comply with the requirements of this License for | ||||||
|  | the Covered Software. If the Larger Work is a combination of Covered | ||||||
|  | Software with a work governed by one or more Secondary Licenses, and the | ||||||
|  | Covered Software is not Incompatible With Secondary Licenses, this | ||||||
|  | License permits You to additionally distribute such Covered Software | ||||||
|  | under the terms of such Secondary License(s), so that the recipient of | ||||||
|  | the Larger Work may, at their option, further distribute the Covered | ||||||
|  | Software under the terms of either this License or such Secondary | ||||||
|  | License(s). | ||||||
|  |  | ||||||
|  | 3.4. Notices | ||||||
|  |  | ||||||
|  | You may not remove or alter the substance of any license notices | ||||||
|  | (including copyright notices, patent notices, disclaimers of warranty, | ||||||
|  | or limitations of liability) contained within the Source Code Form of | ||||||
|  | the Covered Software, except that You may alter any license notices to | ||||||
|  | the extent required to remedy known factual inaccuracies. | ||||||
|  |  | ||||||
|  | 3.5. Application of Additional Terms | ||||||
|  |  | ||||||
|  | You may choose to offer, and to charge a fee for, warranty, support, | ||||||
|  | indemnity or liability obligations to one or more recipients of Covered | ||||||
|  | Software. However, You may do so only on Your own behalf, and not on | ||||||
|  | behalf of any Contributor. You must make it absolutely clear that any | ||||||
|  | such warranty, support, indemnity, or liability obligation is offered by | ||||||
|  | You alone, and You hereby agree to indemnify every Contributor for any | ||||||
|  | liability incurred by such Contributor as a result of warranty, support, | ||||||
|  | indemnity or liability terms You offer. You may include additional | ||||||
|  | disclaimers of warranty and limitations of liability specific to any | ||||||
|  | jurisdiction. | ||||||
|  |  | ||||||
|  | 4. Inability to Comply Due to Statute or Regulation | ||||||
|  | --------------------------------------------------- | ||||||
|  |  | ||||||
|  | If it is impossible for You to comply with any of the terms of this | ||||||
|  | License with respect to some or all of the Covered Software due to | ||||||
|  | statute, judicial order, or regulation then You must: (a) comply with | ||||||
|  | the terms of this License to the maximum extent possible; and (b) | ||||||
|  | describe the limitations and the code they affect. Such description must | ||||||
|  | be placed in a text file included with all distributions of the Covered | ||||||
|  | Software under this License. Except to the extent prohibited by statute | ||||||
|  | or regulation, such description must be sufficiently detailed for a | ||||||
|  | recipient of ordinary skill to be able to understand it. | ||||||
|  |  | ||||||
|  | 5. Termination | ||||||
|  | -------------- | ||||||
|  |  | ||||||
|  | 5.1. The rights granted under this License will terminate automatically | ||||||
|  | if You fail to comply with any of its terms. However, if You become | ||||||
|  | compliant, then the rights granted under this License from a particular | ||||||
|  | Contributor are reinstated (a) provisionally, unless and until such | ||||||
|  | Contributor explicitly and finally terminates Your grants, and (b) on an | ||||||
|  | ongoing basis, if such Contributor fails to notify You of the | ||||||
|  | non-compliance by some reasonable means prior to 60 days after You have | ||||||
|  | come back into compliance. Moreover, Your grants from a particular | ||||||
|  | Contributor are reinstated on an ongoing basis if such Contributor | ||||||
|  | notifies You of the non-compliance by some reasonable means, this is the | ||||||
|  | first time You have received notice of non-compliance with this License | ||||||
|  | from such Contributor, and You become compliant prior to 30 days after | ||||||
|  | Your receipt of the notice. | ||||||
|  |  | ||||||
|  | 5.2. If You initiate litigation against any entity by asserting a patent | ||||||
|  | infringement claim (excluding declaratory judgment actions, | ||||||
|  | counter-claims, and cross-claims) alleging that a Contributor Version | ||||||
|  | directly or indirectly infringes any patent, then the rights granted to | ||||||
|  | You by any and all Contributors for the Covered Software under Section | ||||||
|  | 2.1 of this License shall terminate. | ||||||
|  |  | ||||||
|  | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||||
|  | end user license agreements (excluding distributors and resellers) which | ||||||
|  | have been validly granted by You or Your distributors under this License | ||||||
|  | prior to termination shall survive termination. | ||||||
|  |  | ||||||
|  | ************************************************************************ | ||||||
|  | *                                                                      * | ||||||
|  | *  6. Disclaimer of Warranty                                           * | ||||||
|  | *  -------------------------                                           * | ||||||
|  | *                                                                      * | ||||||
|  | *  Covered Software is provided under this License on an "as is"       * | ||||||
|  | *  basis, without warranty of any kind, either expressed, implied, or  * | ||||||
|  | *  statutory, including, without limitation, warranties that the       * | ||||||
|  | *  Covered Software is free of defects, merchantable, fit for a        * | ||||||
|  | *  particular purpose or non-infringing. The entire risk as to the     * | ||||||
|  | *  quality and performance of the Covered Software is with You.        * | ||||||
|  | *  Should any Covered Software prove defective in any respect, You     * | ||||||
|  | *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||||
|  | *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||||
|  | *  essential part of this License. No use of any Covered Software is   * | ||||||
|  | *  authorized under this License except under this disclaimer.         * | ||||||
|  | *                                                                      * | ||||||
|  | ************************************************************************ | ||||||
|  |  | ||||||
|  | ************************************************************************ | ||||||
|  | *                                                                      * | ||||||
|  | *  7. Limitation of Liability                                          * | ||||||
|  | *  --------------------------                                          * | ||||||
|  | *                                                                      * | ||||||
|  | *  Under no circumstances and under no legal theory, whether tort      * | ||||||
|  | *  (including negligence), contract, or otherwise, shall any           * | ||||||
|  | *  Contributor, or anyone who distributes Covered Software as          * | ||||||
|  | *  permitted above, be liable to You for any direct, indirect,         * | ||||||
|  | *  special, incidental, or consequential damages of any character      * | ||||||
|  | *  including, without limitation, damages for lost profits, loss of    * | ||||||
|  | *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||||
|  | *  and all other commercial damages or losses, even if such party      * | ||||||
|  | *  shall have been informed of the possibility of such damages. This   * | ||||||
|  | *  limitation of liability shall not apply to liability for death or   * | ||||||
|  | *  personal injury resulting from such party's negligence to the       * | ||||||
|  | *  extent applicable law prohibits such limitation. Some               * | ||||||
|  | *  jurisdictions do not allow the exclusion or limitation of           * | ||||||
|  | *  incidental or consequential damages, so this exclusion and          * | ||||||
|  | *  limitation may not apply to You.                                    * | ||||||
|  | *                                                                      * | ||||||
|  | ************************************************************************ | ||||||
|  |  | ||||||
|  | 8. Litigation | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | Any litigation relating to this License may be brought only in the | ||||||
|  | courts of a jurisdiction where the defendant maintains its principal | ||||||
|  | place of business and such litigation shall be governed by laws of that | ||||||
|  | jurisdiction, without reference to its conflict-of-law provisions. | ||||||
|  | Nothing in this Section shall prevent a party's ability to bring | ||||||
|  | cross-claims or counter-claims. | ||||||
|  |  | ||||||
|  | 9. Miscellaneous | ||||||
|  | ---------------- | ||||||
|  |  | ||||||
|  | This License represents the complete agreement concerning the subject | ||||||
|  | matter hereof. If any provision of this License is held to be | ||||||
|  | unenforceable, such provision shall be reformed only to the extent | ||||||
|  | necessary to make it enforceable. Any law or regulation which provides | ||||||
|  | that the language of a contract shall be construed against the drafter | ||||||
|  | shall not be used to construe this License against a Contributor. | ||||||
|  |  | ||||||
|  | 10. Versions of the License | ||||||
|  | --------------------------- | ||||||
|  |  | ||||||
|  | 10.1. New Versions | ||||||
|  |  | ||||||
|  | Mozilla Foundation is the license steward. Except as provided in Section | ||||||
|  | 10.3, no one other than the license steward has the right to modify or | ||||||
|  | publish new versions of this License. Each version will be given a | ||||||
|  | distinguishing version number. | ||||||
|  |  | ||||||
|  | 10.2. Effect of New Versions | ||||||
|  |  | ||||||
|  | You may distribute the Covered Software under the terms of the version | ||||||
|  | of the License under which You originally received the Covered Software, | ||||||
|  | or under the terms of any subsequent version published by the license | ||||||
|  | steward. | ||||||
|  |  | ||||||
|  | 10.3. Modified Versions | ||||||
|  |  | ||||||
|  | If you create software not governed by this License, and you want to | ||||||
|  | create a new license for such software, you may create and use a | ||||||
|  | modified version of this License if you rename the license and remove | ||||||
|  | any references to the name of the license steward (except to note that | ||||||
|  | such modified license differs from this License). | ||||||
|  |  | ||||||
|  | 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||||
|  | Licenses | ||||||
|  |  | ||||||
|  | If You choose to distribute Source Code Form that is Incompatible With | ||||||
|  | Secondary Licenses under the terms of this version of the License, the | ||||||
|  | notice described in Exhibit B of this License must be attached. | ||||||
|  |  | ||||||
|  | Exhibit A - Source Code Form License Notice | ||||||
|  | ------------------------------------------- | ||||||
|  |  | ||||||
|  |   This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |   file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||||
|  |  | ||||||
|  | If it is not possible or desirable to put the notice in a particular | ||||||
|  | file, then You may include the notice in a location (such as a LICENSE | ||||||
|  | file in a relevant directory) where a recipient would be likely to look | ||||||
|  | for such a notice. | ||||||
|  |  | ||||||
|  | You may add additional accurate notices of copyright ownership. | ||||||
|  |  | ||||||
|  | Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||||
|  | --------------------------------------------------------- | ||||||
|  |  | ||||||
|  |   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||||
|  |   defined by the Mozilla Public License, v. 2.0. | ||||||
| @@ -2,9 +2,6 @@ | |||||||
|  |  | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <OutputType>Exe</OutputType> |         <OutputType>Exe</OutputType> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -2,9 +2,6 @@ | |||||||
|  |  | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <OutputType>Exe</OutputType> |         <OutputType>Exe</OutputType> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
| namespace MissionTmaLib; | using Common; | ||||||
|  |  | ||||||
|  | namespace MissionTmaLib; | ||||||
|  |  | ||||||
| public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords); | public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords); | ||||||
| @@ -12,7 +12,7 @@ public class ClanInfo | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public ClanType ClanType { get; set; } |     public ClanType ClanType { get; set; } | ||||||
|  |  | ||||||
|     public string UnkString2 { get; set; } |     public string ScriptsString { get; set; } | ||||||
|     public int UnknownClanPartCount { get; set; } |     public int UnknownClanPartCount { get; set; } | ||||||
|     public List<UnknownClanTreeInfoPart> UnknownParts { get; set; } |     public List<UnknownClanTreeInfoPart> UnknownParts { get; set; } | ||||||
|      |      | ||||||
| @@ -20,7 +20,7 @@ public class ClanInfo | |||||||
|     /// Игра называет этот путь TreeName |     /// Игра называет этот путь TreeName | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public string ResearchNResPath { get; set; } |     public string ResearchNResPath { get; set; } | ||||||
|     public int UnkInt3 { get; set; } |     public int Brains { get; set; } | ||||||
|     public int AlliesMapCount { get; set; } |     public int AlliesMapCount { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -7,18 +7,3 @@ public enum ClanType | |||||||
|     AI = 2, |     AI = 2, | ||||||
|     Neutral = 3 |     Neutral = 3 | ||||||
| } | } | ||||||
|  |  | ||||||
| public static class Extensions |  | ||||||
| { |  | ||||||
|     public static string ToReadableString(this ClanType clanType) |  | ||||||
|     { |  | ||||||
|         return clanType switch |  | ||||||
|         { |  | ||||||
|             ClanType.Environment => $"Окружение ({clanType:D})", |  | ||||||
|             ClanType.Player => $"Игрок ({clanType:D})", |  | ||||||
|             ClanType.AI => $"AI ({clanType:D})", |  | ||||||
|             ClanType.Neutral => $"Нейтральный ({clanType:D})", |  | ||||||
|             _ => $"Неизвестный ({clanType:D})" |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										28
									
								
								MissionTmaLib/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								MissionTmaLib/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | namespace MissionTmaLib; | ||||||
|  |  | ||||||
|  | public static class Extensions | ||||||
|  | { | ||||||
|  |     public static string ToReadableString(this ClanType clanType) | ||||||
|  |     { | ||||||
|  |         return clanType switch | ||||||
|  |         { | ||||||
|  |             ClanType.Environment => $"Окружение ({clanType:D})", | ||||||
|  |             ClanType.Player => $"Игрок ({clanType:D})", | ||||||
|  |             ClanType.AI => $"AI ({clanType:D})", | ||||||
|  |             ClanType.Neutral => $"Нейтральный ({clanType:D})", | ||||||
|  |             _ => $"Неизвестный ({clanType:D})" | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static string ToReadableString(this GameObjectType type) | ||||||
|  |     { | ||||||
|  |         return type switch | ||||||
|  |         { | ||||||
|  |             GameObjectType.Building => $"Строение {type:D}", | ||||||
|  |             GameObjectType.Warbot => $"Варбот {type:D}", | ||||||
|  |             GameObjectType.Tree => $"Дерево {type:D}", | ||||||
|  |             GameObjectType.Stone => $"Камень {type:D}", | ||||||
|  |             _ => $"Неизвестный ({type:D})" | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,6 @@ | |||||||
| namespace MissionTmaLib; | using Common; | ||||||
|  |  | ||||||
|  | namespace MissionTmaLib; | ||||||
|  |  | ||||||
| public class GameObjectInfo | public class GameObjectInfo | ||||||
| { | { | ||||||
| @@ -22,7 +24,7 @@ public class GameObjectInfo | |||||||
|     /// </remarks> |     /// </remarks> | ||||||
|     public int OwningClanIndex { get; set; } |     public int OwningClanIndex { get; set; } | ||||||
|  |  | ||||||
|     public int UnknownInt3 { get; set; } |     public int Order { get; set; } | ||||||
|  |  | ||||||
|     public Vector3 Position { get; set; } |     public Vector3 Position { get; set; } | ||||||
|     public Vector3 Rotation { get; set; } |     public Vector3 Rotation { get; set; } | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -1,3 +1,5 @@ | |||||||
| namespace MissionTmaLib; | using Common; | ||||||
|  |  | ||||||
| public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownInt2, float UnknownFloat, int UnknownInt3); | namespace MissionTmaLib; | ||||||
|  |  | ||||||
|  | public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3); | ||||||
| @@ -1,9 +1,5 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <ItemGroup> | ||||||
|     <PropertyGroup> |     <ProjectReference Include="..\Common\Common.csproj" /> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |   </ItemGroup> | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |  | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -1,39 +0,0 @@ | |||||||
| using System.Buffers.Binary; |  | ||||||
| using System.Text; |  | ||||||
|  |  | ||||||
| namespace MissionTmaLib.Parsing; |  | ||||||
|  |  | ||||||
| public static class Extensions |  | ||||||
| { |  | ||||||
|     public static int ReadInt32LittleEndian(this FileStream fs) |  | ||||||
|     { |  | ||||||
|         Span<byte> buf = stackalloc byte[4]; |  | ||||||
|         fs.ReadExactly(buf); |  | ||||||
|  |  | ||||||
|         return BinaryPrimitives.ReadInt32LittleEndian(buf); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static float ReadFloatLittleEndian(this FileStream fs) |  | ||||||
|     { |  | ||||||
|         Span<byte> buf = stackalloc byte[4]; |  | ||||||
|         fs.ReadExactly(buf); |  | ||||||
|  |  | ||||||
|         return BinaryPrimitives.ReadSingleLittleEndian(buf); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static string ReadLengthPrefixedString(this FileStream fs) |  | ||||||
|     { |  | ||||||
|         var len = fs.ReadInt32LittleEndian(); |  | ||||||
|  |  | ||||||
|         if (len == 0) |  | ||||||
|         { |  | ||||||
|             return ""; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var buffer = new byte[len]; |  | ||||||
|  |  | ||||||
|         fs.ReadExactly(buffer, 0, len); |  | ||||||
|  |  | ||||||
|         return Encoding.ASCII.GetString(buffer, 0, len); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										3
									
								
								MissionTmaLib/Parsing/MissionTma.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								MissionTmaLib/Parsing/MissionTma.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace MissionTmaLib.Parsing; | ||||||
|  |  | ||||||
|  | public record MissionTma(ArealsFileData ArealData, ClansFileData ClansData, GameObjectsFileData GameObjectsData); | ||||||
| @@ -1,11 +1,18 @@ | |||||||
| namespace MissionTmaLib.Parsing; | using Common; | ||||||
|  |  | ||||||
|  | namespace MissionTmaLib.Parsing; | ||||||
|  |  | ||||||
| public class MissionTmaParser | public class MissionTmaParser | ||||||
| { | { | ||||||
|     public static MissionTmaParseResult ReadFile(string filePath) |     public static MissionTmaParseResult ReadFile(string filePath) | ||||||
|     { |     { | ||||||
|         var fs = new FileStream(filePath, FileMode.Open); |         using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); | ||||||
|  |  | ||||||
|  |         return ReadFile(fs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static MissionTmaParseResult ReadFile(Stream fs) | ||||||
|  |     { | ||||||
|         var arealData = LoadAreals(fs); |         var arealData = LoadAreals(fs); | ||||||
|  |  | ||||||
|         var clansData = LoadClans(fs); |         var clansData = LoadClans(fs); | ||||||
| @@ -18,7 +25,7 @@ public class MissionTmaParser | |||||||
|         return new MissionTmaParseResult(missionDat, null); |         return new MissionTmaParseResult(missionDat, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static ArealsFileData LoadAreals(FileStream fileStream) |     private static ArealsFileData LoadAreals(Stream fileStream) | ||||||
|     { |     { | ||||||
|         var unusedHeader = fileStream.ReadInt32LittleEndian(); |         var unusedHeader = fileStream.ReadInt32LittleEndian(); | ||||||
|         var arealCount = fileStream.ReadInt32LittleEndian(); |         var arealCount = fileStream.ReadInt32LittleEndian(); | ||||||
| @@ -54,7 +61,7 @@ public class MissionTmaParser | |||||||
|         return new ArealsFileData(unusedHeader, arealCount, infos); |         return new ArealsFileData(unusedHeader, arealCount, infos); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static ClansFileData? LoadClans(FileStream fileStream) |     private static ClansFileData? LoadClans(Stream fileStream) | ||||||
|     { |     { | ||||||
|         var clanFeatureSet = fileStream.ReadInt32LittleEndian(); |         var clanFeatureSet = fileStream.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
| @@ -78,7 +85,7 @@ public class MissionTmaParser | |||||||
|                 // MISSIONS\SCRIPTS\default |                 // MISSIONS\SCRIPTS\default | ||||||
|                 // MISSIONS\SCRIPTS\tut1_pl |                 // MISSIONS\SCRIPTS\tut1_pl | ||||||
|                 // MISSIONS\SCRIPTS\tut1_en |                 // MISSIONS\SCRIPTS\tut1_en | ||||||
|                 clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString(); |                 clanTreeInfo.ScriptsString = fileStream.ReadLengthPrefixedString(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (2 < clanFeatureSet) |             if (2 < clanFeatureSet) | ||||||
| @@ -118,7 +125,7 @@ public class MissionTmaParser | |||||||
|  |  | ||||||
|             if (4 < clanFeatureSet) |             if (4 < clanFeatureSet) | ||||||
|             { |             { | ||||||
|                 clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian(); |                 clanTreeInfo.Brains = fileStream.ReadInt32LittleEndian(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (5 < clanFeatureSet) |             if (5 < clanFeatureSet) | ||||||
| @@ -156,7 +163,7 @@ public class MissionTmaParser | |||||||
|         return clanInfo; |         return clanInfo; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static GameObjectsFileData LoadGameObjects(FileStream fileStream) |     private static GameObjectsFileData LoadGameObjects(Stream fileStream) | ||||||
|     { |     { | ||||||
|         var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian(); |         var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
| @@ -185,7 +192,11 @@ public class MissionTmaParser | |||||||
|  |  | ||||||
|             if (3 < gameObjectsFeatureSet) |             if (3 < gameObjectsFeatureSet) | ||||||
|             { |             { | ||||||
|                 gameObjectInfo.UnknownInt3 = fileStream.ReadInt32LittleEndian(); |                 gameObjectInfo.Order = fileStream.ReadInt32LittleEndian(); | ||||||
|  |                 if (gameObjectInfo.Type == GameObjectType.Building) | ||||||
|  |                 { | ||||||
|  |                     gameObjectInfo.Order += int.MaxValue; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // читает 12 байт |             // читает 12 байт | ||||||
| @@ -379,5 +390,3 @@ public class MissionTmaParser | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| public record MissionTma(ArealsFileData ArealData, ClansFileData ClansData, GameObjectsFileData GameObjectsData); |  | ||||||
| @@ -1,3 +1,5 @@ | |||||||
| namespace MissionTmaLib; | using Common; | ||||||
|  |  | ||||||
|  | namespace MissionTmaLib; | ||||||
|  |  | ||||||
| public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3); | public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3); | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| namespace MissionTmaLib; |  | ||||||
|  |  | ||||||
| public record Vector3(float X, float Y, float Z); |  | ||||||
| @@ -1,11 +1,11 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <OutputType>Exe</OutputType> |         <OutputType>Exe</OutputType> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|         <AllowUnsafeBlocks>true</AllowUnsafeBlocks> |  | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
|  |     <ItemGroup> | ||||||
|  |         <PackageReference Include="System.Text.Encoding.CodePages" /> | ||||||
|  |     </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -1,7 +1,40 @@ | |||||||
| using System.Buffers.Binary; | using System.Buffers.Binary; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
| var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont.rlb"); | var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont-1.rlb"); | ||||||
|  |  | ||||||
|  | var header = fileBytes.AsSpan().Slice(0, 32); | ||||||
|  |  | ||||||
|  | var nlHeaderBytes = header.Slice(0, 2); | ||||||
|  | var mustBeZero = header[2]; | ||||||
|  | var mustBeOne = header[3]; | ||||||
|  | var numberOfEntriesBytes = header.Slice(4, 2); | ||||||
|  | var sortingFlagBytes = header.Slice(14, 2); | ||||||
|  | var decryptionKeyBytes = header.Slice(20, 2); | ||||||
|  |  | ||||||
|  | var numberOfEntries = BinaryPrimitives.ReadInt16LittleEndian(numberOfEntriesBytes); | ||||||
|  | var sortingFlag = BinaryPrimitives.ReadInt16LittleEndian(sortingFlagBytes); | ||||||
|  | var decryptionKey = BinaryPrimitives.ReadInt16LittleEndian(decryptionKeyBytes); | ||||||
|  |  | ||||||
|  | var headerSize = numberOfEntries * 32; | ||||||
|  |  | ||||||
|  | var decryptedHeader = new byte[headerSize]; | ||||||
|  |  | ||||||
|  | var keyLow = decryptionKeyBytes[0]; | ||||||
|  | var keyHigh = decryptionKeyBytes[1]; | ||||||
|  | for (var i = 0; i < headerSize; i++) | ||||||
|  | { | ||||||
|  |     byte tmp = (byte)((keyLow << 1) ^ keyHigh); | ||||||
|  |     keyLow = tmp; | ||||||
|  |     keyHigh = (byte)((keyHigh >> 1) ^ tmp); | ||||||
|  |     decryptedHeader[i] = (byte)(fileBytes[32 + i] ^ tmp); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var decryptedHeaderString = Encoding.ASCII.GetString(decryptedHeader, 0, headerSize); | ||||||
|  | var entries = decryptedHeader.Chunk(32).ToArray(); | ||||||
|  | var entriesStrings = entries.Select(x => Encoding.ASCII.GetString(x, 0, x.Length)).ToArray(); | ||||||
|  |  | ||||||
|  | File.WriteAllBytes("export.nl", decryptedHeader); | ||||||
| var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2)); | var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2)); | ||||||
|  |  | ||||||
| var decodedHeader = new byte[fileCount * 32]; | var decodedHeader = new byte[fileCount * 32]; | ||||||
|   | |||||||
| @@ -19,10 +19,11 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot | |||||||
| /// каждый элемент это 64 байта, | /// каждый элемент это 64 байта, | ||||||
| /// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64) | /// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64) | ||||||
| /// </summary> | /// </summary> | ||||||
| /// <param name="FileType">[0..8] ASCII описание типа файла, например TEXM или MAT0</param> | /// <param name="FileType">[0..4] ASCII описание типа файла, например TEXM или MAT0</param> | ||||||
|  | /// <param name="ElementCount">[4..8] Количество элементов в файле (если файл составной, например .trf) </param> | ||||||
| /// <param name="Magic1">[8..12] Неизвестное число</param> | /// <param name="Magic1">[8..12] Неизвестное число</param> | ||||||
| /// <param name="FileLength">[12..16] Длина файла в байтах</param> | /// <param name="FileLength">[12..16] Длина файла в байтах</param> | ||||||
| /// <param name="Magic2">[16..20] Неизвестное число</param> | /// <param name="ElementSize">[16..20] Размер элемента в файле (если файл составной, например .trf) </param> | ||||||
| /// <param name="FileName">[20..40] ASCII имя файла</param> | /// <param name="FileName">[20..40] ASCII имя файла</param> | ||||||
| /// <param name="Magic3">[40..44] Неизвестное число</param> | /// <param name="Magic3">[40..44] Неизвестное число</param> | ||||||
| /// <param name="Magic4">[44..48] Неизвестное число</param> | /// <param name="Magic4">[44..48] Неизвестное число</param> | ||||||
| @@ -32,9 +33,10 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot | |||||||
| /// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param> | /// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param> | ||||||
| public record ListMetadataItem( | public record ListMetadataItem( | ||||||
|     string FileType, |     string FileType, | ||||||
|  |     uint ElementCount, | ||||||
|     int Magic1, |     int Magic1, | ||||||
|     int FileLength, |     int FileLength, | ||||||
|     int Magic2, |     int ElementSize, | ||||||
|     string FileName, |     string FileName, | ||||||
|     int Magic3, |     int Magic3, | ||||||
|     int Magic4, |     int Magic4, | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ public class NResExporter | |||||||
|                 extension = ".bin"; |                 extension = ".bin"; | ||||||
|             } |             } | ||||||
|              |              | ||||||
|             var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{fileName}{extension}"); |             var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{archiveFile.FileType}_{fileName}{extension}"); | ||||||
|              |              | ||||||
|             File.WriteAllBytes(targetFilePath, buffer); |             File.WriteAllBytes(targetFilePath, buffer); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,9 +1,3 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|     <PropertyGroup> |  | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |  | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -7,8 +7,13 @@ public static class NResParser | |||||||
| { | { | ||||||
|     public static NResParseResult ReadFile(string path) |     public static NResParseResult ReadFile(string path) | ||||||
|     { |     { | ||||||
|         using FileStream nResFs = new FileStream(path, FileMode.Open); |         using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); | ||||||
|  |  | ||||||
|  |         return ReadFile(nResFs); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public static NResParseResult ReadFile(Stream nResFs) | ||||||
|  |     { | ||||||
|         if (nResFs.Length < 16) |         if (nResFs.Length < 16) | ||||||
|         { |         { | ||||||
|             return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт"); |             return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт"); | ||||||
| @@ -48,13 +53,32 @@ public static class NResParser | |||||||
|         for (int i = 0; i < header.FileCount; i++) |         for (int i = 0; i < header.FileCount; i++) | ||||||
|         { |         { | ||||||
|             nResFs.ReadExactly(metaDataBuffer); |             nResFs.ReadExactly(metaDataBuffer); | ||||||
|  |             var type = ""; | ||||||
|  |  | ||||||
|  |             for (int j = 0; j < 4; j++) | ||||||
|  |             { | ||||||
|  |                 if (!char.IsLetterOrDigit((char)metaDataBuffer[j])) | ||||||
|  |                 { | ||||||
|  |                     type += metaDataBuffer[j] | ||||||
|  |                         .ToString("X2") + " "; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     type += (char)metaDataBuffer[j]; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var type2 = BinaryPrimitives.ReadUInt32LittleEndian(metaDataBuffer.Slice(4)); | ||||||
|  |  | ||||||
|  |             type = type.Trim(); | ||||||
|              |              | ||||||
|             elements.Add( |             elements.Add( | ||||||
|                 new ListMetadataItem( |                 new ListMetadataItem( | ||||||
|                     FileType: Encoding.ASCII.GetString(metaDataBuffer[..8]).TrimEnd('\0'), |                     FileType: type, | ||||||
|  |                     ElementCount: type2, | ||||||
|                     Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]), |                     Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]), | ||||||
|                     FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]), |                     FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]), | ||||||
|                     Magic2: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]), |                     ElementSize: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]), | ||||||
|                     FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'), |                     FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'), | ||||||
|                     Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]), |                     Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]), | ||||||
|                     Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]), |                     Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]), | ||||||
|   | |||||||
| @@ -32,10 +32,11 @@ | |||||||
| 3. В конце файла есть метаданные. | 3. В конце файла есть метаданные. | ||||||
|   Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта. |   Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта. | ||||||
|  |  | ||||||
|    + [0..8] ASCII описание типа файла, например TEXM или MAT0 |    + [0..4] ASCII описание типа файла, например TEXM или MAT0 | ||||||
|  |    + [4..8] Количество элементов в файле (если файл составной, например .trf) | ||||||
|    + [8..12] Неизвестное число |    + [8..12] Неизвестное число | ||||||
|    + [12..16] Длина файла в байтах |    + [12..16] Длина файла в байтах | ||||||
|    + [16..20] Неизвестное число |    + [16..20] Размер элемента в файле (если файл составной, например .trf) | ||||||
|    + [20..40] ASCII имя файла |    + [20..40] ASCII имя файла | ||||||
|    + [40..44] Неизвестное число |    + [40..44] Неизвестное число | ||||||
|    + [44..48] Неизвестное число |    + [44..48] Неизвестное число | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System.Numerics; | using System.Numerics; | ||||||
|  | using System.Text; | ||||||
| using ImGuiNET; | using ImGuiNET; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using NResUI.Abstractions; | using NResUI.Abstractions; | ||||||
| @@ -33,6 +34,8 @@ public class App | |||||||
|  |  | ||||||
|         public void Init(IWindow window, GL openGl, ImFontPtr openSansFont) |         public void Init(IWindow window, GL openGl, ImFontPtr openSansFont) | ||||||
|         { |         { | ||||||
|  |             // Call this once at program startup | ||||||
|  |             Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); | ||||||
|             ImGui.StyleColorsLight(); |             ImGui.StyleColorsLight(); | ||||||
|  |  | ||||||
|             IServiceCollection serviceCollection = new ServiceCollection(); |             IServiceCollection serviceCollection = new ServiceCollection(); | ||||||
| @@ -53,6 +56,10 @@ public class App | |||||||
|             serviceCollection.AddSingleton(new NResExplorerViewModel()); |             serviceCollection.AddSingleton(new NResExplorerViewModel()); | ||||||
|             serviceCollection.AddSingleton(new TexmExplorerViewModel()); |             serviceCollection.AddSingleton(new TexmExplorerViewModel()); | ||||||
|             serviceCollection.AddSingleton(new MissionTmaViewModel()); |             serviceCollection.AddSingleton(new MissionTmaViewModel()); | ||||||
|  |             serviceCollection.AddSingleton(new BinaryExplorerViewModel()); | ||||||
|  |             serviceCollection.AddSingleton(new ScrViewModel()); | ||||||
|  |             serviceCollection.AddSingleton(new VarsetViewModel()); | ||||||
|  |             serviceCollection.AddSingleton(new CpDatSchemeViewModel()); | ||||||
|  |  | ||||||
|             var serviceProvider = serviceCollection.BuildServiceProvider(); |             var serviceProvider = serviceCollection.BuildServiceProvider(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										262
									
								
								NResUI/ImGuiUI/BinaryExplorerPanel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								NResUI/ImGuiUI/BinaryExplorerPanel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | |||||||
|  | using System.Buffers.Binary; | ||||||
|  | using System.Numerics; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | using System.Runtime.Intrinsics; | ||||||
|  | using System.Text; | ||||||
|  | using System.Text.Json; | ||||||
|  | using ImGuiNET; | ||||||
|  | using NativeFileDialogSharp; | ||||||
|  | using NResUI.Abstractions; | ||||||
|  | using NResUI.Models; | ||||||
|  |  | ||||||
|  | namespace NResUI.ImGuiUI; | ||||||
|  |  | ||||||
|  | public class BinaryExplorerPanel : IImGuiPanel | ||||||
|  | { | ||||||
|  |     private readonly BinaryExplorerViewModel _viewModel; | ||||||
|  |  | ||||||
|  |     public BinaryExplorerPanel(BinaryExplorerViewModel viewModel) | ||||||
|  |     { | ||||||
|  |         _viewModel = viewModel; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void OnImGuiRender() | ||||||
|  |     { | ||||||
|  |         return; | ||||||
|  |         if (ImGui.Begin("Binary Explorer")) | ||||||
|  |         { | ||||||
|  |             if (ImGui.Button("Open File")) | ||||||
|  |             { | ||||||
|  |                 OpenFile(); | ||||||
|  |             } | ||||||
|  |          | ||||||
|  |             if (_viewModel.HasFile) | ||||||
|  |             { | ||||||
|  |                 ImGui.SameLine(); | ||||||
|  |                 ImGui.Text(_viewModel.Path); | ||||||
|  |          | ||||||
|  |                 if (ImGui.Button("Сохранить регионы")) | ||||||
|  |                 { | ||||||
|  |                     File.WriteAllText("preset.json", JsonSerializer.Serialize(_viewModel.Regions)); | ||||||
|  |                 } | ||||||
|  |          | ||||||
|  |                 ImGui.SameLine(); | ||||||
|  |                 if (ImGui.Button("Загрузить регионы")) | ||||||
|  |                 { | ||||||
|  |                     _viewModel.Regions = JsonSerializer.Deserialize<List<Region>>(File.ReadAllText("preset.json"))!; | ||||||
|  |                 } | ||||||
|  |          | ||||||
|  |                 const int bytesPerRow = 16; | ||||||
|  |                 if (ImGui.BeginTable("HexTable", bytesPerRow + 1, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|  |                 { | ||||||
|  |                     ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.WidthFixed); | ||||||
|  |                     for (var i = 0; i < bytesPerRow; i++) | ||||||
|  |                     { | ||||||
|  |                         ImGui.TableSetupColumn(i.ToString()); | ||||||
|  |                     } | ||||||
|  |          | ||||||
|  |                     ImGui.TableHeadersRow(); | ||||||
|  |                     for (int i = 0; i < _viewModel.Data.Length; i += bytesPerRow) | ||||||
|  |                     { | ||||||
|  |                         ImGui.TableNextRow(); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text($"{i:x8} "); | ||||||
|  |          | ||||||
|  |                         for (int j = 0; j < 16; j++) | ||||||
|  |                         { | ||||||
|  |                             var index = i + j; | ||||||
|  |                             ImGui.TableNextColumn(); | ||||||
|  |                             if (index < _viewModel.Data.Length) | ||||||
|  |                             { | ||||||
|  |                                 uint? regionColor = GetRegionColor(i + j); | ||||||
|  |          | ||||||
|  |                                 if (regionColor is not null) | ||||||
|  |                                 { | ||||||
|  |                                     ImGui.PushStyleColor(ImGuiCol.Header, regionColor.Value); | ||||||
|  |                                 } | ||||||
|  |          | ||||||
|  |                                 if (ImGui.Selectable($"{_viewModel.Data[i + j]}##sel{i + j}", regionColor.HasValue)) | ||||||
|  |                                 { | ||||||
|  |                                     HandleRegionSelect(i + j); | ||||||
|  |                                 } | ||||||
|  |          | ||||||
|  |                                 if (regionColor is not null) | ||||||
|  |                                 { | ||||||
|  |                                     ImGui.PopStyleColor(); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 ImGui.Text(" "); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |          | ||||||
|  |                     ImGui.EndTable(); | ||||||
|  |                 } | ||||||
|  |          | ||||||
|  |                 ImGui.SameLine(); | ||||||
|  |          | ||||||
|  |                 ImGui.SetNextItemWidth(200f); | ||||||
|  |                 if (ImGui.ColorPicker4("NextColor", ref _viewModel.NextColor, ImGuiColorEditFlags.Float)) | ||||||
|  |                 { | ||||||
|  |                 } | ||||||
|  |          | ||||||
|  |                 if (ImGui.BeginTable("Регионы", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|  |                 { | ||||||
|  |                     ImGui.TableSetupColumn("Номер"); | ||||||
|  |                     ImGui.TableSetupColumn("Старт"); | ||||||
|  |                     ImGui.TableSetupColumn("Длина"); | ||||||
|  |                     ImGui.TableSetupColumn("Значение"); | ||||||
|  |                     ImGui.TableSetupColumn("Действия"); | ||||||
|  |          | ||||||
|  |                     ImGui.TableHeadersRow(); | ||||||
|  |  | ||||||
|  |                     for (int k = 0; k < _viewModel.Regions.Count; k++) | ||||||
|  |                     { | ||||||
|  |                         var region = _viewModel.Regions[k]; | ||||||
|  |                         ImGui.TableNextRow(); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(k.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(region.Begin.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(region.Length.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(region.Value ?? "unknown"); | ||||||
|  |  | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         if (ImGui.Button($"float##f{k}")) | ||||||
|  |                         { | ||||||
|  |                             region.Value = BinaryPrimitives.ReadSingleLittleEndian(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)]) | ||||||
|  |                                 .ToString("F2"); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         if (ImGui.Button($"int##i{k}")) | ||||||
|  |                         { | ||||||
|  |                             region.Value = BinaryPrimitives.ReadInt32LittleEndian(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)]) | ||||||
|  |                                 .ToString(); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         if (ImGui.Button($"ASCII##a{k}")) | ||||||
|  |                         { | ||||||
|  |                             region.Value = Encoding.ASCII.GetString(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)]); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         if (ImGui.Button($"raw##r{k}")) | ||||||
|  |                         { | ||||||
|  |                             region.Value = string.Join( | ||||||
|  |                                 "", | ||||||
|  |                                 _viewModel.Data[region.Begin..(region.Begin + region.Length)] | ||||||
|  |                                     .Select(x => x.ToString("x2")) | ||||||
|  |                             ); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     ImGui.EndTable(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |          | ||||||
|  |             ImGui.End(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private uint? GetRegionColor(int index) | ||||||
|  |     { | ||||||
|  |         Region? inRegion = _viewModel.Regions.Find(x => x.Begin <= index && x.Begin + x.Length > index); | ||||||
|  |  | ||||||
|  |         return inRegion?.Color; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void HandleRegionSelect(int index) | ||||||
|  |     { | ||||||
|  |         Region? inRegion = _viewModel.Regions.FirstOrDefault(x => x.Begin <= index && index < x.Begin + x.Length); | ||||||
|  |  | ||||||
|  |         if (inRegion is null) | ||||||
|  |         { | ||||||
|  |             // not in region | ||||||
|  |             Region? prependRegion; | ||||||
|  |             Region? appendRegion; | ||||||
|  |  | ||||||
|  |             if ((prependRegion = _viewModel.Regions.Find(x => x.Begin + x.Length == index)) is not null) | ||||||
|  |             { | ||||||
|  |                 if (prependRegion.Color == GetImGuiColor(_viewModel.NextColor)) | ||||||
|  |                 { | ||||||
|  |                     prependRegion.Length += 1; | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if ((appendRegion = _viewModel.Regions.Find(x => x.Begin - 1 == index)) is not null) | ||||||
|  |             { | ||||||
|  |                 if (appendRegion.Color == GetImGuiColor(_viewModel.NextColor)) | ||||||
|  |                 { | ||||||
|  |                     appendRegion.Begin--; | ||||||
|  |                     appendRegion.Length += 1; | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var color = ImGui.ColorConvertFloat4ToU32(_viewModel.NextColor); | ||||||
|  |  | ||||||
|  |             color = unchecked((uint) (0xFF << 24)) | color; | ||||||
|  |             _viewModel.Regions.Add( | ||||||
|  |                 new Region() | ||||||
|  |                 { | ||||||
|  |                     Begin = index, | ||||||
|  |                     Length = 1, | ||||||
|  |                     Color = color | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             if (inRegion.Length == 1) | ||||||
|  |             { | ||||||
|  |                 _viewModel.Regions.Remove(inRegion); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 if (inRegion.Begin == index) | ||||||
|  |                 { | ||||||
|  |                     inRegion.Begin++; | ||||||
|  |                     inRegion.Length--; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (inRegion.Begin + inRegion.Length - 1 == index) | ||||||
|  |                 { | ||||||
|  |                     inRegion.Length--; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static uint GetImGuiColor(Vector4 vec) | ||||||
|  |     { | ||||||
|  |         var color = ImGui.ColorConvertFloat4ToU32(vec); | ||||||
|  |  | ||||||
|  |         color = unchecked((uint) (0xFF << 24)) | color; | ||||||
|  |  | ||||||
|  |         return color; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private bool OpenFile() | ||||||
|  |     { | ||||||
|  |         var result = Dialog.FileOpen("*"); | ||||||
|  |  | ||||||
|  |         if (result.IsOk) | ||||||
|  |         { | ||||||
|  |             var path = result.Path; | ||||||
|  |  | ||||||
|  |             var bytes = File.ReadAllBytes(path); | ||||||
|  |             _viewModel.HasFile = true; | ||||||
|  |             _viewModel.Data = bytes; | ||||||
|  |             _viewModel.Path = path; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										150
									
								
								NResUI/ImGuiUI/CpDatSchemeExplorer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								NResUI/ImGuiUI/CpDatSchemeExplorer.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | using CpDatLib; | ||||||
|  | using ImGuiNET; | ||||||
|  | using NResUI.Abstractions; | ||||||
|  | using NResUI.Models; | ||||||
|  |  | ||||||
|  | namespace NResUI.ImGuiUI; | ||||||
|  |  | ||||||
|  | public class CpDatSchemeExplorer : IImGuiPanel | ||||||
|  | { | ||||||
|  |     private readonly CpDatSchemeViewModel _viewModel; | ||||||
|  |  | ||||||
|  |     public CpDatSchemeExplorer(CpDatSchemeViewModel viewModel) | ||||||
|  |     { | ||||||
|  |         _viewModel = viewModel; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void OnImGuiRender() | ||||||
|  |     { | ||||||
|  |         if (ImGui.Begin("cp .dat Scheme Explorer")) | ||||||
|  |         { | ||||||
|  |             ImGui.Text("cp .dat - это файл схема здания или робота. Их можно найти в папке UNITS"); | ||||||
|  |             ImGui.Separator(); | ||||||
|  |              | ||||||
|  |             var cpDat = _viewModel.CpDatScheme; | ||||||
|  |             if (_viewModel.HasFile && cpDat is not null) | ||||||
|  |             { | ||||||
|  |                 ImGui.Text("Тип объекта в схеме: "); | ||||||
|  |                 ImGui.SameLine(); | ||||||
|  |                 ImGui.Text(cpDat.Type.ToString("G")); | ||||||
|  |  | ||||||
|  |                 var root = cpDat.Root; | ||||||
|  |  | ||||||
|  |                 DrawEntry(root, 0); | ||||||
|  |  | ||||||
|  |                 ImGui.Separator(); | ||||||
|  |  | ||||||
|  |                 if (ImGui.BeginTable("content", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX | ImGuiTableFlags.Sortable)) | ||||||
|  |                 { | ||||||
|  |                     ImGui.TableSetupColumn("Индекс"); | ||||||
|  |                     ImGui.TableSetupColumn("Уровень вложенности"); | ||||||
|  |                     ImGui.TableSetupColumn("Архив"); | ||||||
|  |                     ImGui.TableSetupColumn("Элемент"); | ||||||
|  |                     ImGui.TableSetupColumn("Magic1"); | ||||||
|  |                     ImGui.TableSetupColumn("Magic2"); | ||||||
|  |                     ImGui.TableSetupColumn("Описание"); | ||||||
|  |                     ImGui.TableSetupColumn("Тип"); | ||||||
|  |  | ||||||
|  |                     ImGui.TableHeadersRow(); | ||||||
|  |  | ||||||
|  |                     // Handle sorting | ||||||
|  |                     ImGuiTableSortSpecsPtr sortSpecs = ImGui.TableGetSortSpecs(); | ||||||
|  |                     if (sortSpecs.SpecsDirty) | ||||||
|  |                     { | ||||||
|  |                         // Only handle the first sort spec for simplicity | ||||||
|  |                         var sortSpec = sortSpecs.Specs; | ||||||
|  |  | ||||||
|  |                         if (sortSpec.ColumnIndex == 0) | ||||||
|  |                         { | ||||||
|  |                             _viewModel.RebuildFlatList(); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |  | ||||||
|  |                             _viewModel.FlatList.Sort((a, b) => | ||||||
|  |                             { | ||||||
|  |                                 int result = 0; | ||||||
|  |                                 switch (sortSpec.ColumnIndex) | ||||||
|  |                                 { | ||||||
|  |                                     case 1: result = a.Level.CompareTo(b.Level); break; | ||||||
|  |                                     case 2: result = string.Compare(a.Entry.ArchiveFile, b.Entry.ArchiveFile, StringComparison.Ordinal); break; | ||||||
|  |                                     case 3: result = string.Compare(a.Entry.ArchiveEntryName, b.Entry.ArchiveEntryName, StringComparison.Ordinal); break; | ||||||
|  |                                     case 4: result = a.Entry.Magic1.CompareTo(b.Entry.Magic1); break; | ||||||
|  |                                     case 5: result = a.Entry.Magic2.CompareTo(b.Entry.Magic2); break; | ||||||
|  |                                     case 6: result = string.Compare(a.Entry.Description, b.Entry.Description, StringComparison.Ordinal); break; | ||||||
|  |                                     case 7: result = a.Entry.Type.CompareTo(b.Entry.Type); break; | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 return sortSpec.SortDirection == ImGuiSortDirection.Descending ? -result : result; | ||||||
|  |                             }); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         sortSpecs.SpecsDirty = false; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     for (int i = 0; i < _viewModel.FlatList.Count; i++) | ||||||
|  |                     { | ||||||
|  |                         ImGui.TableNextRow(); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(i.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Level.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveFile); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveEntryName); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Entry.Magic1.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Entry.Magic2.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Entry.Description); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(_viewModel.FlatList[i].Entry.Type.ToString("G")); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     ImGui.EndTable(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 void DrawEntry(CpDatEntry entry, int index) | ||||||
|  |                 { | ||||||
|  |                     if (ImGui.TreeNodeEx($"Элемент: \"{entry.ArchiveFile}/{entry.ArchiveEntryName}\" - {entry.Description}##entry_{index}")) | ||||||
|  |                     { | ||||||
|  |                         ImGui.Text("Magic1: "); | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         ImGui.Text(entry.Magic1.ToString()); | ||||||
|  |  | ||||||
|  |                         ImGui.Text("Magic2: "); | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         ImGui.Text(entry.Magic2.ToString()); | ||||||
|  |  | ||||||
|  |                         ImGui.Text("Тип: "); | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         ImGui.Text(entry.Type.ToString()); | ||||||
|  |  | ||||||
|  |                         ImGui.Text("Кол-во дочерних элементов: "); | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         ImGui.Text(entry.ChildCount.ToString()); | ||||||
|  |  | ||||||
|  |                         foreach (var child in entry.Children) | ||||||
|  |                         { | ||||||
|  |                             DrawEntry(child, ++index); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         ImGui.TreePop(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if (_viewModel.Error is not null) | ||||||
|  |             { | ||||||
|  |                 ImGui.Text(_viewModel.Error); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 ImGui.Text("cp .dat не открыт"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         ImGui.End(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,29 +1,28 @@ | |||||||
| using System.Numerics; | using System.Numerics; | ||||||
|  | using CpDatLib; | ||||||
| using ImGuiNET; | using ImGuiNET; | ||||||
|  | using MissionTmaLib; | ||||||
| using MissionTmaLib.Parsing; | using MissionTmaLib.Parsing; | ||||||
| using NativeFileDialogSharp; | using NativeFileDialogSharp; | ||||||
| using NResLib; | using NResLib; | ||||||
| using NResUI.Abstractions; | using NResUI.Abstractions; | ||||||
| using NResUI.Models; | using NResUI.Models; | ||||||
|  | using ScrLib; | ||||||
| using TexmLib; | using TexmLib; | ||||||
|  | using VarsetLib; | ||||||
|  |  | ||||||
| namespace NResUI.ImGuiUI | namespace NResUI.ImGuiUI | ||||||
| { | { | ||||||
|     public class MainMenuBar : IImGuiPanel |     public class MainMenuBar( | ||||||
|  |         NResExplorerViewModel nResExplorerViewModel, | ||||||
|  |         TexmExplorerViewModel texmExplorerViewModel, | ||||||
|  |         ScrViewModel scrViewModel, | ||||||
|  |         MissionTmaViewModel missionTmaViewModel, | ||||||
|  |         VarsetViewModel varsetViewModel, | ||||||
|  |         CpDatSchemeViewModel cpDatSchemeViewModel, | ||||||
|  |         MessageBoxModalPanel messageBox) | ||||||
|  |         : IImGuiPanel | ||||||
|     { |     { | ||||||
|         private readonly NResExplorerViewModel _nResExplorerViewModel; |  | ||||||
|         private readonly TexmExplorerViewModel _texmExplorerViewModel; |  | ||||||
|         private readonly MissionTmaViewModel _missionTmaViewModel; |  | ||||||
|  |  | ||||||
|         private readonly MessageBoxModalPanel _messageBox; |  | ||||||
|         public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox, MissionTmaViewModel missionTmaViewModel) |  | ||||||
|         { |  | ||||||
|             _nResExplorerViewModel = nResExplorerViewModel; |  | ||||||
|             _texmExplorerViewModel = texmExplorerViewModel; |  | ||||||
|             _messageBox = messageBox; |  | ||||||
|             _missionTmaViewModel = missionTmaViewModel; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public void OnImGuiRender() |         public void OnImGuiRender() | ||||||
|         { |         { | ||||||
|             if (ImGui.BeginMenuBar()) |             if (ImGui.BeginMenuBar()) | ||||||
| @@ -40,7 +39,7 @@ namespace NResUI.ImGuiUI | |||||||
|  |  | ||||||
|                             var parseResult = NResParser.ReadFile(path); |                             var parseResult = NResParser.ReadFile(path); | ||||||
|  |  | ||||||
|                             _nResExplorerViewModel.SetParseResult(parseResult, path); |                             nResExplorerViewModel.SetParseResult(parseResult, path); | ||||||
|                             Console.WriteLine("Read NRES"); |                             Console.WriteLine("Read NRES"); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -57,7 +56,7 @@ namespace NResUI.ImGuiUI | |||||||
|  |  | ||||||
|                             var parseResult = TexmParser.ReadFromStream(fs, path); |                             var parseResult = TexmParser.ReadFromStream(fs, path); | ||||||
|  |  | ||||||
|                             _texmExplorerViewModel.SetParseResult(parseResult, path); |                             texmExplorerViewModel.SetParseResult(parseResult, path); | ||||||
|                             Console.WriteLine("Read TEXM"); |                             Console.WriteLine("Read TEXM"); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -76,8 +75,8 @@ namespace NResUI.ImGuiUI | |||||||
|  |  | ||||||
|                             var parseResult = TexmParser.ReadFromStream(fs, path); |                             var parseResult = TexmParser.ReadFromStream(fs, path); | ||||||
|  |  | ||||||
|                             _texmExplorerViewModel.SetParseResult(parseResult, path); |                             texmExplorerViewModel.SetParseResult(parseResult, path); | ||||||
|                             Console.WriteLine("Read TEXM"); |                             Console.WriteLine("Read TFNT TEXM"); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
| @@ -90,11 +89,56 @@ namespace NResUI.ImGuiUI | |||||||
|                             var path = result.Path; |                             var path = result.Path; | ||||||
|                             var parseResult = MissionTmaParser.ReadFile(path); |                             var parseResult = MissionTmaParser.ReadFile(path); | ||||||
|  |  | ||||||
|                             _missionTmaViewModel.SetParseResult(parseResult, path); |                             missionTmaViewModel.SetParseResult(parseResult, path); | ||||||
|  |                             Console.WriteLine("Read TMA"); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     if (_nResExplorerViewModel.HasFile) |                     if (ImGui.MenuItem("Open SCR Scripts File")) | ||||||
|  |                     { | ||||||
|  |                         var result = Dialog.FileOpen("scr"); | ||||||
|  |  | ||||||
|  |                         if (result.IsOk) | ||||||
|  |                         { | ||||||
|  |                             var path = result.Path; | ||||||
|  |                             var parseResult = ScrParser.ReadFile(path); | ||||||
|  |  | ||||||
|  |                             scrViewModel.SetParseResult(parseResult, path); | ||||||
|  |                             Console.WriteLine("Read SCR"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (ImGui.MenuItem("Open Varset File")) | ||||||
|  |                     { | ||||||
|  |                         var result = Dialog.FileOpen("var"); | ||||||
|  |  | ||||||
|  |                         if (result.IsOk) | ||||||
|  |                         { | ||||||
|  |                             var path = result.Path; | ||||||
|  |                             var parseResult = VarsetParser.Parse(path); | ||||||
|  |  | ||||||
|  |                             varsetViewModel.Items = parseResult; | ||||||
|  |  | ||||||
|  |                             Console.WriteLine("Read VARSET"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (ImGui.MenuItem("Open cp .dat Scheme File")) | ||||||
|  |                     { | ||||||
|  |                         var result = Dialog.FileOpen("dat"); | ||||||
|  |  | ||||||
|  |                         if (result.IsOk) | ||||||
|  |                         { | ||||||
|  |                             var path = result.Path; | ||||||
|  |                             var parseResult = CpDatParser.Parse(path); | ||||||
|  |  | ||||||
|  |                             cpDatSchemeViewModel.SetParseResult(parseResult, path); | ||||||
|  |  | ||||||
|  |                             Console.WriteLine("Read cp .dat"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (nResExplorerViewModel.HasFile) | ||||||
|                     { |                     { | ||||||
|                         if (ImGui.MenuItem("Экспортировать NRes")) |                         if (ImGui.MenuItem("Экспортировать NRes")) | ||||||
|                         { |                         { | ||||||
| @@ -104,9 +148,9 @@ namespace NResUI.ImGuiUI | |||||||
|                             { |                             { | ||||||
|                                 var path = result.Path; |                                 var path = result.Path; | ||||||
|  |  | ||||||
|                                 NResExporter.Export(_nResExplorerViewModel.Archive!, path, _nResExplorerViewModel.Path!); |                                 NResExporter.Export(nResExplorerViewModel.Archive!, path, nResExplorerViewModel.Path!); | ||||||
|  |  | ||||||
|                                 _messageBox.Show("Успешно экспортировано"); |                                 messageBox.Show("Успешно экспортировано"); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -117,6 +161,5 @@ namespace NResUI.ImGuiUI | |||||||
|                 ImGui.EndMenuBar(); |                 ImGui.EndMenuBar(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -18,9 +18,14 @@ public class MissionTmaExplorer : IImGuiPanel | |||||||
|     { |     { | ||||||
|         if (ImGui.Begin("Mission TMA Explorer")) |         if (ImGui.Begin("Mission TMA Explorer")) | ||||||
|         { |         { | ||||||
|  |             ImGui.Text("data.tma - это файл миссии. Его можно найти в папке MISSIONS"); | ||||||
|  |             ImGui.Separator(); | ||||||
|  |              | ||||||
|             var mission = _viewModel.Mission; |             var mission = _viewModel.Mission; | ||||||
|             if (_viewModel.HasFile && mission is not null) |             if (_viewModel.HasFile && mission is not null) | ||||||
|             { |             { | ||||||
|  |                 ImGui.Columns(2); | ||||||
|  |  | ||||||
|                 ImGui.Text("Путь к файлу: "); |                 ImGui.Text("Путь к файлу: "); | ||||||
|                 ImGui.SameLine(); |                 ImGui.SameLine(); | ||||||
|                 ImGui.Text(_viewModel.Path); |                 ImGui.Text(_viewModel.Path); | ||||||
| @@ -65,20 +70,32 @@ public class MissionTmaExplorer : IImGuiPanel | |||||||
|                                     { |                                     { | ||||||
|                                         ImGui.TableNextRow(); |                                         ImGui.TableNextRow(); | ||||||
|                                         ImGui.TableNextColumn(); |                                         ImGui.TableNextColumn(); | ||||||
|                                         ImGui.Text(arealInfo.Coords[k].X.ToString("F2")); |                                         ImGui.Text( | ||||||
|  |                                             arealInfo.Coords[k] | ||||||
|  |                                                 .X.ToString("F2") | ||||||
|  |                                         ); | ||||||
|                                         ImGui.TableNextColumn(); |                                         ImGui.TableNextColumn(); | ||||||
|                                         ImGui.Text(arealInfo.Coords[k].Y.ToString("F2")); |                                         ImGui.Text( | ||||||
|  |                                             arealInfo.Coords[k] | ||||||
|  |                                                 .Y.ToString("F2") | ||||||
|  |                                         ); | ||||||
|                                         ImGui.TableNextColumn(); |                                         ImGui.TableNextColumn(); | ||||||
|                                         ImGui.Text(arealInfo.Coords[k].Z.ToString("F2")); |                                         ImGui.Text( | ||||||
|  |                                             arealInfo.Coords[k] | ||||||
|  |                                                 .Z.ToString("F2") | ||||||
|  |                                         ); | ||||||
|                                     } |                                     } | ||||||
|  |  | ||||||
|                                     ImGui.EndTable(); |                                     ImGui.EndTable(); | ||||||
|                                 } |                                 } | ||||||
|  |  | ||||||
|                                 ImGui.TreePop(); |                                 ImGui.TreePop(); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         ImGui.TreePop(); |                         ImGui.TreePop(); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     ImGui.TreePop(); |                     ImGui.TreePop(); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -118,10 +135,10 @@ public class MissionTmaExplorer : IImGuiPanel | |||||||
|                                 ImGui.SameLine(); |                                 ImGui.SameLine(); | ||||||
|                                 ImGui.Text(clanInfo.ClanType.ToReadableString()); |                                 ImGui.Text(clanInfo.ClanType.ToReadableString()); | ||||||
|  |  | ||||||
|                                 ImGui.Text("Неизвестная строка 1: "); |                                 ImGui.Text("Скрипты поведения (Mission Scripts): "); | ||||||
|                                 Utils.ShowHint("Кажется это путь к файлу поведения (Behavior), но пока не понятно. Обычно пути соответствуют 2 файла."); |                                 Utils.ShowHint("Пути к файлам .scr и .fml описывающих настройку объектов и поведение AI"); | ||||||
|                                 ImGui.SameLine(); |                                 ImGui.SameLine(); | ||||||
|                                 ImGui.Text(clanInfo.UnkString2); |                                 ImGui.Text(clanInfo.ScriptsString); | ||||||
|  |  | ||||||
|                                 if (clanInfo.UnknownParts.Count > 0) |                                 if (clanInfo.UnknownParts.Count > 0) | ||||||
|                                 { |                                 { | ||||||
| @@ -162,14 +179,14 @@ public class MissionTmaExplorer : IImGuiPanel | |||||||
|                                     ImGui.Text("Отсутствует неизвестная часть"); |                                     ImGui.Text("Отсутствует неизвестная часть"); | ||||||
|                                 } |                                 } | ||||||
|  |  | ||||||
|                                 ImGui.Text("Путь к файлу .trf: "); |                                 ImGui.Text("Дерево исследований: "); | ||||||
|                                 Utils.ShowHint("Не до конца понятно, что означает, вероятно это NRes с деревом исследований"); |                                 Utils.ShowHint("NRes с деревом исследований"); | ||||||
|                                 ImGui.SameLine(); |                                 ImGui.SameLine(); | ||||||
|                                 ImGui.Text(clanInfo.ResearchNResPath); |                                 ImGui.Text(clanInfo.ResearchNResPath); | ||||||
|  |  | ||||||
|                                 ImGui.Text("Неизвестное число 3: "); |                                 ImGui.Text("Количество мозгов (Brains))): "); | ||||||
|                                 ImGui.SameLine(); |                                 ImGui.SameLine(); | ||||||
|                                 ImGui.Text(clanInfo.UnkInt3.ToString()); |                                 ImGui.Text(clanInfo.Brains.ToString()); | ||||||
|  |  | ||||||
|                                 ImGui.Text("Матрица союзников"); |                                 ImGui.Text("Матрица союзников"); | ||||||
|                                 Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга"); |                                 Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга"); | ||||||
| @@ -185,7 +202,10 @@ public class MissionTmaExplorer : IImGuiPanel | |||||||
|                                         ImGui.TableNextColumn(); |                                         ImGui.TableNextColumn(); | ||||||
|                                         ImGui.Text(alliesMapKey); |                                         ImGui.Text(alliesMapKey); | ||||||
|                                         ImGui.TableNextColumn(); |                                         ImGui.TableNextColumn(); | ||||||
|                                         ImGui.Text(clanInfo.AlliesMap[alliesMapKey].ToString()); |                                         ImGui.Text( | ||||||
|  |                                             clanInfo.AlliesMap[alliesMapKey] | ||||||
|  |                                                 .ToString() | ||||||
|  |                                         ); | ||||||
|                                     } |                                     } | ||||||
|  |  | ||||||
|                                     ImGui.EndTable(); |                                     ImGui.EndTable(); | ||||||
| @@ -204,8 +224,189 @@ public class MissionTmaExplorer : IImGuiPanel | |||||||
|  |  | ||||||
|                 if (ImGui.TreeNodeEx("Объекты")) |                 if (ImGui.TreeNodeEx("Объекты")) | ||||||
|                 { |                 { | ||||||
|  |                     var gameObjectsData = mission.GameObjectsData; | ||||||
|  |  | ||||||
|  |                     ImGui.Text("Фиче-сет: "); | ||||||
|  |                     Utils.ShowHint("Магическое число из файла, на основе которого игра читает разные секции об объекте"); | ||||||
|  |                     ImGui.SameLine(); | ||||||
|  |                     ImGui.Text(gameObjectsData.GameObjectsFeatureSet.ToString()); | ||||||
|  |  | ||||||
|  |                     ImGui.Text("Кол-во объектов: "); | ||||||
|  |                     ImGui.SameLine(); | ||||||
|  |                     ImGui.Text(gameObjectsData.GameObjectsCount.ToString()); | ||||||
|  |  | ||||||
|  |                     for (var i = 0; i < gameObjectsData.GameObjectInfos.Count; i++) | ||||||
|  |                     { | ||||||
|  |                         var gameObjectInfo = gameObjectsData.GameObjectInfos[i]; | ||||||
|  |                         if (ImGui.TreeNodeEx($"Объект {i} - {gameObjectInfo.DatString}")) | ||||||
|  |                         { | ||||||
|  |                             ImGui.Text("Тип объекта: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.Type.ToReadableString()); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Неизвестные флаги: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.UnknownFlags.ToString("X8")); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Путь к файлу .dat: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.DatString); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Индекс владеющего клана: "); | ||||||
|  |                             Utils.ShowHint("-1 если объект никому не принадлежит"); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.OwningClanIndex.ToString()); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Порядковый номер: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.Order.ToString()); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Вектор позиции: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text($"{gameObjectInfo.Position.X} : {gameObjectInfo.Position.Y} : {gameObjectInfo.Position.Z}"); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Вектор поворота: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text($"{gameObjectInfo.Rotation.X} : {gameObjectInfo.Rotation.Y} : {gameObjectInfo.Rotation.Z}"); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Вектор масштаба: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text($"{gameObjectInfo.Scale.X} : {gameObjectInfo.Scale.Y} : {gameObjectInfo.Scale.Z}"); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Неизвестная строка 2: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.UnknownString2); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Неизвестное число 4: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.UnknownInt4.ToString()); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Неизвестное число 5: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.UnknownInt5.ToString()); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Неизвестное число 6: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(gameObjectInfo.UnknownInt6.ToString()); | ||||||
|  |  | ||||||
|  |                             if (ImGui.TreeNodeEx("Настройки")) | ||||||
|  |                             { | ||||||
|  |                                 ImGui.Text("Неиспользуемый заголовок: "); | ||||||
|  |                                 ImGui.SameLine(); | ||||||
|  |                                 ImGui.Text(gameObjectInfo.Settings.Unused.ToString()); | ||||||
|  |                                 ImGui.Text("Кол-во настроек: "); | ||||||
|  |                                 ImGui.SameLine(); | ||||||
|  |                                 ImGui.Text(gameObjectInfo.Settings.SettingsCount.ToString()); | ||||||
|  |                                 ImGui.Text("0 - дробное число, 1 - целое число"); | ||||||
|  |                                 if (ImGui.BeginTable("Настройки", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|  |                                 { | ||||||
|  |                                     ImGui.TableSetupColumn("Тип"); | ||||||
|  |                                     ImGui.TableSetupColumn("Число 1"); | ||||||
|  |                                     ImGui.TableSetupColumn("Число 2"); | ||||||
|  |                                     ImGui.TableSetupColumn("Число 3"); | ||||||
|  |                                     ImGui.TableSetupColumn("Название"); | ||||||
|  |                                     ImGui.TableHeadersRow(); | ||||||
|  |  | ||||||
|  |                                     for (var i1 = 0; i1 < gameObjectInfo.Settings.Settings.Count; i1++) | ||||||
|  |                                     { | ||||||
|  |                                         var setting = gameObjectInfo.Settings.Settings[i1]; | ||||||
|  |                                          | ||||||
|  |                                         ImGui.TableNextRow(); | ||||||
|  |                                         ImGui.TableNextColumn(); | ||||||
|  |                                         ImGui.Text(setting.SettingType.ToString()); | ||||||
|  |                                         ImGui.TableNextColumn(); | ||||||
|  |                                         ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString()); | ||||||
|  |                                         ImGui.TableNextColumn(); | ||||||
|  |                                         ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString()); | ||||||
|  |                                         ImGui.TableNextColumn(); | ||||||
|  |                                         ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString()); | ||||||
|  |                                         ImGui.TableNextColumn(); | ||||||
|  |                                         ImGui.Text(setting.Name); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     ImGui.EndTable(); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|                                 ImGui.TreePop(); |                                 ImGui.TreePop(); | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|  |                             ImGui.TreePop(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     ImGui.Text("LAND строка: "); | ||||||
|  |                     Utils.ShowHint("Видимо это путь к настройкам поверхности"); | ||||||
|  |                     ImGui.SameLine(); | ||||||
|  |                     ImGui.Text(gameObjectsData.LandString); | ||||||
|  |  | ||||||
|  |                     ImGui.Text("Неизвестное число: "); | ||||||
|  |                     ImGui.SameLine(); | ||||||
|  |                     ImGui.Text(gameObjectsData.UnknownInt.ToString()); | ||||||
|  |  | ||||||
|  |                     ImGui.Text("Техническое описание: "); | ||||||
|  |                     ImGui.SameLine(); | ||||||
|  |                     ImGui.Text(gameObjectsData.MissionTechDescription?.Replace((char)0xcd, '.') ?? "Отсутствует"); | ||||||
|  |  | ||||||
|  |                     var lodeData = gameObjectsData.LodeData; | ||||||
|  |                     if (lodeData is not null) | ||||||
|  |                     { | ||||||
|  |                         ImGui.Text("Информация о LOD-ах"); | ||||||
|  |  | ||||||
|  |                         ImGui.Text("Неиспользуемый заголовок: "); | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         ImGui.Text(lodeData.Unused.ToString()); | ||||||
|  |  | ||||||
|  |                         ImGui.Text("Кол-во LOD-ов: "); | ||||||
|  |                         ImGui.SameLine(); | ||||||
|  |                         ImGui.Text(lodeData.LodeCount.ToString()); | ||||||
|  |                          | ||||||
|  |                         if (ImGui.BeginTable("Информация о лодах", 7, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|  |                         { | ||||||
|  |                             ImGui.TableSetupColumn("X"); | ||||||
|  |                             ImGui.TableSetupColumn("Y"); | ||||||
|  |                             ImGui.TableSetupColumn("Z"); | ||||||
|  |                             ImGui.TableSetupColumn("Число 1"); | ||||||
|  |                             ImGui.TableSetupColumn("Флаги 2"); | ||||||
|  |                             ImGui.TableSetupColumn("Число 3"); | ||||||
|  |                             ImGui.TableSetupColumn("Число 4"); | ||||||
|  |                             ImGui.TableHeadersRow(); | ||||||
|  |  | ||||||
|  |                             for (var i1 = 0; i1 < lodeData.Lodes.Count; i1++) | ||||||
|  |                             { | ||||||
|  |                                 var lode = lodeData.Lodes[i1]; | ||||||
|  |                                          | ||||||
|  |                                 ImGui.TableNextRow(); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownVector.X.ToString()); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownVector.Y.ToString()); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownVector.Z.ToString()); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownInt1.ToString()); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownFlags2.ToString()); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownFloat.ToString()); | ||||||
|  |                                 ImGui.TableNextColumn(); | ||||||
|  |                                 ImGui.Text(lode.UnknownInt3.ToString()); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             ImGui.EndTable(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         ImGui.Text("Информаия о LOD-ах отсутствует"); | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |  | ||||||
|  |                     ImGui.TreePop(); | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 ImGui.NextColumn(); | ||||||
|  |                  | ||||||
|  |                 ImGui.Text("Тут хочу сделать карту"); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -1,22 +1,45 @@ | |||||||
| using ImGuiNET; | using CpDatLib; | ||||||
|  | using ImGuiNET; | ||||||
|  | using MissionTmaLib.Parsing; | ||||||
|  | using NResLib; | ||||||
| using NResUI.Abstractions; | using NResUI.Abstractions; | ||||||
| using NResUI.Models; | using NResUI.Models; | ||||||
|  | using ScrLib; | ||||||
|  | using TexmLib; | ||||||
|  | using VarsetLib; | ||||||
|  |  | ||||||
| namespace NResUI.ImGuiUI; | namespace NResUI.ImGuiUI; | ||||||
|  |  | ||||||
| public class NResExplorerPanel : IImGuiPanel | public class NResExplorerPanel : IImGuiPanel | ||||||
| { | { | ||||||
|     private readonly NResExplorerViewModel _viewModel; |     private readonly NResExplorerViewModel _viewModel; | ||||||
|  |     private readonly TexmExplorerViewModel _texmExplorerViewModel; | ||||||
|  |     private readonly VarsetViewModel _varsetViewModel; | ||||||
|  |     private readonly CpDatSchemeViewModel _cpDatSchemeViewModel; | ||||||
|  |     private readonly MissionTmaViewModel _missionTmaViewModel; | ||||||
|  |     private readonly ScrViewModel _scrViewModel; | ||||||
|  |  | ||||||
|     public NResExplorerPanel(NResExplorerViewModel viewModel) |     public NResExplorerPanel(NResExplorerViewModel viewModel, TexmExplorerViewModel texmExplorerViewModel, | ||||||
|  |         VarsetViewModel varsetViewModel, CpDatSchemeViewModel cpDatSchemeViewModel, MissionTmaViewModel missionTmaViewModel, ScrViewModel scrViewModel) | ||||||
|     { |     { | ||||||
|         _viewModel = viewModel; |         _viewModel = viewModel; | ||||||
|  |         _texmExplorerViewModel = texmExplorerViewModel; | ||||||
|  |         _varsetViewModel = varsetViewModel; | ||||||
|  |         _cpDatSchemeViewModel = cpDatSchemeViewModel; | ||||||
|  |         _missionTmaViewModel = missionTmaViewModel; | ||||||
|  |         _scrViewModel = scrViewModel; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     int contextMenuRow = -1; | ||||||
|  |  | ||||||
|     public void OnImGuiRender() |     public void OnImGuiRender() | ||||||
|     { |     { | ||||||
|         if (ImGui.Begin("NRes Explorer")) |         if (ImGui.Begin("NRes Explorer")) | ||||||
|         { |         { | ||||||
|  |             ImGui.Text( | ||||||
|  |                 "NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res"); | ||||||
|  |             ImGui.Separator(); | ||||||
|  |  | ||||||
|             if (!_viewModel.HasFile) |             if (!_viewModel.HasFile) | ||||||
|             { |             { | ||||||
|                 ImGui.Text("No NRes is opened"); |                 ImGui.Text("No NRes is opened"); | ||||||
| @@ -45,13 +68,14 @@ public class NResExplorerPanel : IImGuiPanel | |||||||
|                     ImGui.SameLine(); |                     ImGui.SameLine(); | ||||||
|                     ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString()); |                     ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString()); | ||||||
|  |  | ||||||
|  |                     if (ImGui.BeginTable("content", 12, | ||||||
|                     if (ImGui.BeginTable("content", 11)) |                             ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|                     { |                     { | ||||||
|                         ImGui.TableSetupColumn("Тип файла"); |                         ImGui.TableSetupColumn("Тип файла"); | ||||||
|  |                         ImGui.TableSetupColumn("Кол-во элементов"); | ||||||
|                         ImGui.TableSetupColumn("Magic1"); |                         ImGui.TableSetupColumn("Magic1"); | ||||||
|                         ImGui.TableSetupColumn("Длина файла в байтах"); |                         ImGui.TableSetupColumn("Длина файла в байтах"); | ||||||
|                         ImGui.TableSetupColumn("Magic2"); |                         ImGui.TableSetupColumn("Размер элемента"); | ||||||
|                         ImGui.TableSetupColumn("Имя файла"); |                         ImGui.TableSetupColumn("Имя файла"); | ||||||
|                         ImGui.TableSetupColumn("Magic3"); |                         ImGui.TableSetupColumn("Magic3"); | ||||||
|                         ImGui.TableSetupColumn("Magic4"); |                         ImGui.TableSetupColumn("Magic4"); | ||||||
| @@ -66,8 +90,21 @@ public class NResExplorerPanel : IImGuiPanel | |||||||
|                         { |                         { | ||||||
|                             ImGui.TableNextRow(); |                             ImGui.TableNextRow(); | ||||||
|                             ImGui.TableNextColumn(); |                             ImGui.TableNextColumn(); | ||||||
|  |  | ||||||
|  |                             ImGui.Selectable("##row_select" + i, false, ImGuiSelectableFlags.SpanAllColumns); | ||||||
|  |                             if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Right)) | ||||||
|  |                             { | ||||||
|  |                                 Console.WriteLine("Context menu for row " + i); | ||||||
|  |                                 contextMenuRow = i; | ||||||
|  |                                 ImGui.OpenPopup("row_context_menu"); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |  | ||||||
|                             ImGui.Text(_viewModel.Archive.Files[i].FileType); |                             ImGui.Text(_viewModel.Archive.Files[i].FileType); | ||||||
|                             ImGui.TableNextColumn(); |                             ImGui.TableNextColumn(); | ||||||
|  |                             ImGui.Text(_viewModel.Archive.Files[i].ElementCount.ToString()); | ||||||
|  |                             ImGui.TableNextColumn(); | ||||||
|                             ImGui.Text( |                             ImGui.Text( | ||||||
|                                 _viewModel.Archive.Files[i] |                                 _viewModel.Archive.Files[i] | ||||||
|                                     .Magic1.ToString() |                                     .Magic1.ToString() | ||||||
| @@ -80,7 +117,7 @@ public class NResExplorerPanel : IImGuiPanel | |||||||
|                             ImGui.TableNextColumn(); |                             ImGui.TableNextColumn(); | ||||||
|                             ImGui.Text( |                             ImGui.Text( | ||||||
|                                 _viewModel.Archive.Files[i] |                                 _viewModel.Archive.Files[i] | ||||||
|                                     .Magic2.ToString() |                                     .ElementSize.ToString() | ||||||
|                             ); |                             ); | ||||||
|                             ImGui.TableNextColumn(); |                             ImGui.TableNextColumn(); | ||||||
|                             ImGui.Text(_viewModel.Archive.Files[i].FileName); |                             ImGui.Text(_viewModel.Archive.Files[i].FileName); | ||||||
| @@ -116,6 +153,130 @@ public class NResExplorerPanel : IImGuiPanel | |||||||
|                             ); |                             ); | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|  |                         if (ImGui.BeginPopup("row_context_menu")) | ||||||
|  |                         { | ||||||
|  |                             if (contextMenuRow == -1 || contextMenuRow > _viewModel.Archive.Files.Count) | ||||||
|  |                             { | ||||||
|  |                                 ImGui.Text("Broken context menu :(. Reopen"); | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 var file = _viewModel.Archive.Files[contextMenuRow]; | ||||||
|  |                                 ImGui.Text("Actions for file " + file.FileName); | ||||||
|  |                                 ImGui.TextDisabled("Program has no understading of file format("); | ||||||
|  |                                 ImGui.Separator(); | ||||||
|  |                                 if (ImGui.MenuItem("Open as Texture TEXM")) | ||||||
|  |                                 { | ||||||
|  |                                     using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, | ||||||
|  |                                         FileShare.Read); | ||||||
|  |                                     fs.Seek(file.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |  | ||||||
|  |                                     var buffer = new byte[file.FileLength]; | ||||||
|  |  | ||||||
|  |                                     fs.ReadExactly(buffer, 0, file.FileLength); | ||||||
|  |  | ||||||
|  |                                     using var ms = new MemoryStream(buffer); | ||||||
|  |  | ||||||
|  |                                     var parseResult = TexmParser.ReadFromStream(ms, file.FileName); | ||||||
|  |  | ||||||
|  |                                     _texmExplorerViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName)); | ||||||
|  |                                     Console.WriteLine("Read TEXM from context menu"); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 if (ImGui.MenuItem("Open as Archive NRes")) | ||||||
|  |                                 { | ||||||
|  |                                     using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, | ||||||
|  |                                         FileShare.Read); | ||||||
|  |                                     fs.Seek(file.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |  | ||||||
|  |                                     var buffer = new byte[file.FileLength]; | ||||||
|  |  | ||||||
|  |                                     fs.ReadExactly(buffer, 0, file.FileLength); | ||||||
|  |  | ||||||
|  |                                     using var ms = new MemoryStream(buffer); | ||||||
|  |  | ||||||
|  |                                     var parseResult = NResParser.ReadFile(ms); | ||||||
|  |  | ||||||
|  |                                     _viewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName)); | ||||||
|  |                                     Console.WriteLine("Read NRes from context menu"); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 if (ImGui.MenuItem("Open as Varset .var")) | ||||||
|  |                                 { | ||||||
|  |                                     using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, | ||||||
|  |                                         FileShare.Read); | ||||||
|  |                                     fs.Seek(file.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |  | ||||||
|  |                                     var buffer = new byte[file.FileLength]; | ||||||
|  |  | ||||||
|  |                                     fs.ReadExactly(buffer, 0, file.FileLength); | ||||||
|  |  | ||||||
|  |                                     using var ms = new MemoryStream(buffer); | ||||||
|  |  | ||||||
|  |                                     var parseResult = VarsetParser.Parse(ms); | ||||||
|  |  | ||||||
|  |                                     _varsetViewModel.Items = parseResult; | ||||||
|  |                                     Console.WriteLine("Read Varset from context menu"); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 if (ImGui.MenuItem("Open as Scheme cp.dat")) | ||||||
|  |                                 { | ||||||
|  |                                     using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, | ||||||
|  |                                         FileShare.Read); | ||||||
|  |                                     fs.Seek(file.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |  | ||||||
|  |                                     var buffer = new byte[file.FileLength]; | ||||||
|  |  | ||||||
|  |                                     fs.ReadExactly(buffer, 0, file.FileLength); | ||||||
|  |  | ||||||
|  |                                     using var ms = new MemoryStream(buffer); | ||||||
|  |  | ||||||
|  |                                     var parseResult = CpDatParser.Parse(ms); | ||||||
|  |  | ||||||
|  |                                     _cpDatSchemeViewModel.SetParseResult(parseResult, file.FileName); | ||||||
|  |                                     Console.WriteLine("Read cp.dat from context menu"); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 if (ImGui.MenuItem("Open as Mission .tma")) | ||||||
|  |                                 { | ||||||
|  |                                     using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, | ||||||
|  |                                         FileShare.Read); | ||||||
|  |                                     fs.Seek(file.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |  | ||||||
|  |                                     var buffer = new byte[file.FileLength]; | ||||||
|  |  | ||||||
|  |                                     fs.ReadExactly(buffer, 0, file.FileLength); | ||||||
|  |  | ||||||
|  |                                     using var ms = new MemoryStream(buffer); | ||||||
|  |  | ||||||
|  |                                     var parseResult = MissionTmaParser.ReadFile(ms); | ||||||
|  |  | ||||||
|  |                                     _missionTmaViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName)); | ||||||
|  |                                     Console.WriteLine("Read .tma from context menu"); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 if (ImGui.MenuItem("Open as Scripts .scr")) | ||||||
|  |                                 { | ||||||
|  |                                     using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, | ||||||
|  |                                         FileShare.Read); | ||||||
|  |                                     fs.Seek(file.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |  | ||||||
|  |                                     var buffer = new byte[file.FileLength]; | ||||||
|  |  | ||||||
|  |                                     fs.ReadExactly(buffer, 0, file.FileLength); | ||||||
|  |  | ||||||
|  |                                     using var ms = new MemoryStream(buffer); | ||||||
|  |  | ||||||
|  |                                     var parseResult = ScrParser.ReadFile(ms); | ||||||
|  |  | ||||||
|  |                                     _scrViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName)); | ||||||
|  |                                     Console.WriteLine("Read .scr from context menu"); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             ImGui.EndPopup(); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|                         ImGui.EndTable(); |                         ImGui.EndTable(); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|   | |||||||
							
								
								
									
										147
									
								
								NResUI/ImGuiUI/ScrExplorer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								NResUI/ImGuiUI/ScrExplorer.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | |||||||
|  | using ImGuiNET; | ||||||
|  | using NResUI.Abstractions; | ||||||
|  | using NResUI.Models; | ||||||
|  | using ScrLib; | ||||||
|  |  | ||||||
|  | namespace NResUI.ImGuiUI; | ||||||
|  |  | ||||||
|  | public class ScrExplorer : IImGuiPanel | ||||||
|  | { | ||||||
|  |     private readonly ScrViewModel _viewModel; | ||||||
|  |  | ||||||
|  |     public ScrExplorer(ScrViewModel viewModel) | ||||||
|  |     { | ||||||
|  |         _viewModel = viewModel; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void OnImGuiRender() | ||||||
|  |     { | ||||||
|  |         if (ImGui.Begin("SCR Explorer")) | ||||||
|  |         { | ||||||
|  |             ImGui.Text("scr - это файл AI скриптов. Их можно найти в папке MISSIONS/SCRIPTS"); | ||||||
|  |             ImGui.Separator(); | ||||||
|  |              | ||||||
|  |             var scr = _viewModel.Scr; | ||||||
|  |             if (_viewModel.HasFile && scr is not null) | ||||||
|  |             { | ||||||
|  |                 ImGui.Text("Магия: "); | ||||||
|  |                 Utils.ShowHint("тут всегда число 59 (0x3b) - это число известных игре скриптов"); | ||||||
|  |                 ImGui.SameLine(); | ||||||
|  |                 ImGui.Text(scr.Magic.ToString()); | ||||||
|  |  | ||||||
|  |                 ImGui.Text("Кол-во секций: "); | ||||||
|  |                 ImGui.SameLine(); | ||||||
|  |                 ImGui.Text(scr.EntryCount.ToString()); | ||||||
|  |  | ||||||
|  |                 if (ImGui.TreeNodeEx("Секции")) | ||||||
|  |                 { | ||||||
|  |                     for (var i = 0; i < scr.Entries.Count; i++) | ||||||
|  |                     { | ||||||
|  |                         var entry = scr.Entries[i]; | ||||||
|  |                         if (ImGui.TreeNodeEx($"Секция {i} - \"{entry.Title}\"")) | ||||||
|  |                         { | ||||||
|  |                             ImGui.Text("Индекс: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(entry.Index.ToString()); | ||||||
|  |  | ||||||
|  |                             ImGui.Text("Кол-во элементов: "); | ||||||
|  |                             ImGui.SameLine(); | ||||||
|  |                             ImGui.Text(entry.InnerCount.ToString()); | ||||||
|  |  | ||||||
|  |                             if (ImGui.BeginTable($"Элементы##{i:0000}", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|  |                             { | ||||||
|  |                                 ImGui.TableSetupColumn("Индекс встроенного скрипта"); | ||||||
|  |                                 ImGui.TableSetupColumn("UnkInner2"); | ||||||
|  |                                 ImGui.TableSetupColumn("UnkInner3"); | ||||||
|  |                                 ImGui.TableSetupColumn("Тип действия"); | ||||||
|  |                                 ImGui.TableSetupColumn("UnkInner5"); | ||||||
|  |                                 ImGui.TableSetupColumn("Кол-во аргументов"); | ||||||
|  |                                 ImGui.TableSetupColumn("Аргументы"); | ||||||
|  |                                 ImGui.TableSetupColumn("UnkInner7"); | ||||||
|  |                                 ImGui.TableHeadersRow(); | ||||||
|  |  | ||||||
|  |                                 for (int j = 0; j < entry.Inners.Count; j++) | ||||||
|  |                                 { | ||||||
|  |                                     var inner = entry.Inners[j]; | ||||||
|  |                                     ImGui.TableNextRow(); | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text(inner.ScriptIndex.ToString()); | ||||||
|  |  | ||||||
|  |                                     if (inner.ScriptIndex == 2) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("Первый аргумент - номер проблемы"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     if (inner.ScriptIndex == 4) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("Первый аргумент - номер проблемы"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     if (inner.ScriptIndex == 8) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("Установить dCurrentProblem стейт (VARSET:arg0)"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     if (inner.ScriptIndex == 20) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("Первый аргумент - номер проблемы"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text(inner.UnkInner2.ToString()); | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text(inner.UnkInner3.ToString()); | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text($"{(int) inner.Type}: {inner.Type:G}"); | ||||||
|  |                                     if (inner.Type == ScrEntryInnerType._0) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("0 обязан иметь аргументы"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     if (inner.Type == ScrEntryInnerType.CheckInternalState) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("Для 5 вообще не нужны данные, игра проверяет внутренний стейт"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     if (inner.Type == ScrEntryInnerType.SetVarsetValue) | ||||||
|  |                                     { | ||||||
|  |                                         Utils.ShowHint("В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3"); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text(inner.UnkInner5.ToString()); | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text(inner.ArgumentsCount.ToString()); | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     foreach (var argument in inner.Arguments) | ||||||
|  |                                     { | ||||||
|  |                                         if (ImGui.Button(argument.ToString())) | ||||||
|  |                                         { | ||||||
|  |                                         } | ||||||
|  |  | ||||||
|  |                                         ImGui.SameLine(); | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     ImGui.TableNextColumn(); | ||||||
|  |                                     ImGui.Text(inner.UnkInner7.ToString()); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 ImGui.EndTable(); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             ImGui.TreePop(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     ImGui.TreePop(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 ImGui.Text("SCR не открыт"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ImGui.End(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -21,6 +21,9 @@ public class TexmExplorer : IImGuiPanel | |||||||
|     { |     { | ||||||
|         if (ImGui.Begin("TEXM Explorer")) |         if (ImGui.Begin("TEXM Explorer")) | ||||||
|         { |         { | ||||||
|  |             ImGui.Text("TEXM - это файл текстуры. Их можно найти внутри NRes архивов, например Textures.lib"); | ||||||
|  |             ImGui.Separator(); | ||||||
|  |              | ||||||
|             if (!_viewModel.HasFile) |             if (!_viewModel.HasFile) | ||||||
|             { |             { | ||||||
|                 ImGui.Text("No TEXM opened"); |                 ImGui.Text("No TEXM opened"); | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								NResUI/ImGuiUI/VarsetExplorerPanel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								NResUI/ImGuiUI/VarsetExplorerPanel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | using ImGuiNET; | ||||||
|  | using NResUI.Abstractions; | ||||||
|  | using NResUI.Models; | ||||||
|  |  | ||||||
|  | namespace NResUI.ImGuiUI; | ||||||
|  |  | ||||||
|  | public class VarsetExplorerPanel : IImGuiPanel | ||||||
|  | { | ||||||
|  |     private readonly VarsetViewModel _viewModel; | ||||||
|  |  | ||||||
|  |     public VarsetExplorerPanel(VarsetViewModel viewModel) | ||||||
|  |     { | ||||||
|  |         _viewModel = viewModel; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void OnImGuiRender() | ||||||
|  |     { | ||||||
|  |         if (ImGui.Begin("VARSET Explorer")) | ||||||
|  |         { | ||||||
|  |             ImGui.Text(".var - это файл динамических настроек. Можно найти в MISSIONS/SCRIPTS/varset.var, а также внутри behpsp.res"); | ||||||
|  |             ImGui.Separator(); | ||||||
|  |  | ||||||
|  |             if (_viewModel.Items.Count == 0) | ||||||
|  |             { | ||||||
|  |                 ImGui.Text("VARSET не загружен"); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 if (ImGui.BeginTable($"varset", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) | ||||||
|  |                 { | ||||||
|  |                     ImGui.TableSetupColumn("Индекс"); | ||||||
|  |                     ImGui.TableSetupColumn("Тип"); | ||||||
|  |                     ImGui.TableSetupColumn("Имя"); | ||||||
|  |                     ImGui.TableSetupColumn("Значение"); | ||||||
|  |                     ImGui.TableHeadersRow(); | ||||||
|  |  | ||||||
|  |                     for (int j = 0; j < _viewModel.Items.Count; j++) | ||||||
|  |                     { | ||||||
|  |                         var item = _viewModel.Items[j]; | ||||||
|  |                         ImGui.TableNextRow(); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(j.ToString()); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(item.Type); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(item.Name); | ||||||
|  |                         ImGui.TableNextColumn(); | ||||||
|  |                         ImGui.Text(item.Value); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     ImGui.EndTable(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 ImGui.TreePop(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ImGui.End(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								NResUI/Models/BinaryExplorerViewModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								NResUI/Models/BinaryExplorerViewModel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | using System.Numerics; | ||||||
|  |  | ||||||
|  | namespace NResUI.Models; | ||||||
|  |  | ||||||
|  | public class BinaryExplorerViewModel | ||||||
|  | { | ||||||
|  |     public bool HasFile { get; set; } | ||||||
|  |     public string? Error { get; set; } | ||||||
|  |  | ||||||
|  |     public string Path { get; set; } = ""; | ||||||
|  |     public byte[] Data { get; set; } = []; | ||||||
|  |  | ||||||
|  |     public List<Region> Regions { get; set; } = []; | ||||||
|  |  | ||||||
|  |     public Vector4 NextColor; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class Region | ||||||
|  | { | ||||||
|  |     public int Begin { get; set; } | ||||||
|  |  | ||||||
|  |     public int Length { get; set; } | ||||||
|  |  | ||||||
|  |     public uint Color { get; set; } | ||||||
|  |  | ||||||
|  |     public string? Value; | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								NResUI/Models/CpDatSchemeViewModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								NResUI/Models/CpDatSchemeViewModel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | using CpDatLib; | ||||||
|  | using ScrLib; | ||||||
|  |  | ||||||
|  | namespace NResUI.Models; | ||||||
|  |  | ||||||
|  | public class CpDatSchemeViewModel | ||||||
|  | { | ||||||
|  |     public bool HasFile { get; set; } | ||||||
|  |     public string? Error { get; set; } | ||||||
|  |  | ||||||
|  |     public CpDatScheme? CpDatScheme { get; set; } | ||||||
|  |  | ||||||
|  |     public List<(int Level, CpDatEntry Entry)> FlatList { get; set; } = []; | ||||||
|  |  | ||||||
|  |     public string? Path { get; set; } | ||||||
|  |  | ||||||
|  |     public void SetParseResult(CpDatParseResult parseResult, string path) | ||||||
|  |     { | ||||||
|  |         CpDatScheme = parseResult.Scheme; | ||||||
|  |         Error = parseResult.Error; | ||||||
|  |         HasFile = true; | ||||||
|  |         Path = path; | ||||||
|  |  | ||||||
|  |         if (CpDatScheme is not null) | ||||||
|  |         { | ||||||
|  |             RebuildFlatList(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void RebuildFlatList() | ||||||
|  |     { | ||||||
|  |         FlatList = []; | ||||||
|  |  | ||||||
|  |         if (CpDatScheme is null) | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |              | ||||||
|  |         CollectEntries(CpDatScheme.Root, 0); | ||||||
|  |              | ||||||
|  |         void CollectEntries(CpDatEntry entry, int level) | ||||||
|  |         { | ||||||
|  |             FlatList.Add((level, entry)); | ||||||
|  |             foreach (var child in entry.Children) | ||||||
|  |             { | ||||||
|  |                 CollectEntries(child, level + 1); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								NResUI/Models/ScrViewModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								NResUI/Models/ScrViewModel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | using ScrLib; | ||||||
|  |  | ||||||
|  | namespace NResUI.Models; | ||||||
|  |  | ||||||
|  | public class ScrViewModel | ||||||
|  | { | ||||||
|  |     public bool HasFile { get; set; } | ||||||
|  |     public string? Error { get; set; } | ||||||
|  |  | ||||||
|  |     public ScrFile? Scr { get; set; } | ||||||
|  |  | ||||||
|  |     public string? Path { get; set; } | ||||||
|  |  | ||||||
|  |     public void SetParseResult(ScrFile scrFile, string path) | ||||||
|  |     { | ||||||
|  |         Scr = scrFile; | ||||||
|  |         HasFile = true; | ||||||
|  |         Path = path; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								NResUI/Models/VarsetViewModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								NResUI/Models/VarsetViewModel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | using VarsetLib; | ||||||
|  |  | ||||||
|  | namespace NResUI.Models; | ||||||
|  |  | ||||||
|  | public class VarsetViewModel | ||||||
|  | { | ||||||
|  |     public List<VarsetItem> Items { get; set; } = []; | ||||||
|  | } | ||||||
| @@ -1,10 +1,8 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <OutputType>Exe</OutputType> |         <OutputType Condition="'$(OS)' == 'Windows_NT'">WinExe</OutputType> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |         <OutputType Condition="'$(OS)' != 'Windows_NT'">Exe</OutputType> | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
| @@ -12,16 +10,19 @@ | |||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|       <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" /> |       <PackageReference Include="Microsoft.Extensions.DependencyInjection" /> | ||||||
|       <PackageReference Include="NativeFileDialogSharp" Version="0.5.0" /> |       <PackageReference Include="NativeFileDialogSharp" /> | ||||||
|       <PackageReference Include="Silk.NET" Version="2.22.0" /> |       <PackageReference Include="Silk.NET" /> | ||||||
|       <PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" /> |       <PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|  |       <ProjectReference Include="..\CpDatLib\CpDatLib.csproj" /> | ||||||
|       <ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" /> |       <ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" /> | ||||||
|       <ProjectReference Include="..\NResLib\NResLib.csproj" /> |       <ProjectReference Include="..\NResLib\NResLib.csproj" /> | ||||||
|  |       <ProjectReference Include="..\ScrLib\ScrLib.csproj" /> | ||||||
|       <ProjectReference Include="..\TexmLib\TexmLib.csproj" /> |       <ProjectReference Include="..\TexmLib\TexmLib.csproj" /> | ||||||
|  |       <ProjectReference Include="..\VarsetLib\VarsetLib.csproj" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -137,9 +137,9 @@ namespace NResUI | |||||||
|  |  | ||||||
|         public void SetLod(int @base, int min, int max) |         public void SetLod(int @base, int min, int max) | ||||||
|         { |         { | ||||||
|             _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base); |             _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, in @base); | ||||||
|             _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min); |             _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, in min); | ||||||
|             _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max); |             _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, in max); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void SetWrap(TextureCoordinate coord, TextureWrapMode mode) |         public void SetWrap(TextureCoordinate coord, TextureWrapMode mode) | ||||||
|   | |||||||
| @@ -1,69 +0,0 @@ | |||||||
|  |  | ||||||
| Microsoft Visual Studio Solution File, Format Version 12.00 |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextureDecoder", "TextureDecoder\TextureDecoder.csproj", "{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLUnpacker", "NLUnpacker\NLUnpacker.csproj", "{50C83E6C-23ED-4A8E-B948-89686A742CF0}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResUI", "NResUI\NResUI.csproj", "{7456A089-0701-416C-8668-1F740BF4B72C}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResLib", "NResLib\NResLib.csproj", "{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshUnpacker", "MeshUnpacker\MeshUnpacker.csproj", "{F1465FFE-0D66-4A3C-90D7-153A14E226E6}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TexmLib", "TexmLib\TexmLib.csproj", "{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}" |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionDataUnpacker", "MissionDataUnpacker\MissionDataUnpacker.csproj", "{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}" |  | ||||||
| EndProject |  | ||||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{BAF212FE-A0FD-41A2-A1A9-B406FDDFBAF3}" |  | ||||||
| 	ProjectSection(SolutionItems) = preProject |  | ||||||
| 		README.md = README.md |  | ||||||
| 	EndProjectSection |  | ||||||
| EndProject |  | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}" |  | ||||||
| EndProject |  | ||||||
| Global |  | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution |  | ||||||
| 		Debug|Any CPU = Debug|Any CPU |  | ||||||
| 		Release|Any CPU = Release|Any CPU |  | ||||||
| 	EndGlobalSection |  | ||||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution |  | ||||||
| 		{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 	EndGlobalSection |  | ||||||
| EndGlobal |  | ||||||
							
								
								
									
										21
									
								
								ParkanPlayground.slnx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								ParkanPlayground.slnx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | <Solution> | ||||||
|  |   <Folder Name="/meta/"> | ||||||
|  |     <File Path="Directory.Build.props" /> | ||||||
|  |     <File Path="Directory.Packages.props" /> | ||||||
|  |     <File Path="README.md" /> | ||||||
|  |   </Folder> | ||||||
|  |   <Project Path="Common\Common.csproj" Type="Classic C#" /> | ||||||
|  |   <Project Path="CpDatLib\CpDatLib.csproj" Type="Classic C#" /> | ||||||
|  |   <Project Path="MeshUnpacker/MeshUnpacker.csproj" /> | ||||||
|  |   <Project Path="MissionDataUnpacker/MissionDataUnpacker.csproj" /> | ||||||
|  |   <Project Path="MissionTmaLib/MissionTmaLib.csproj" /> | ||||||
|  |   <Project Path="NLUnpacker/NLUnpacker.csproj" /> | ||||||
|  |   <Project Path="NResLib/NResLib.csproj" /> | ||||||
|  |   <Project Path="NResUI/NResUI.csproj" /> | ||||||
|  |   <Project Path="ParkanPlayground/ParkanPlayground.csproj" /> | ||||||
|  |   <Project Path="ScrLib/ScrLib.csproj" /> | ||||||
|  |   <Project Path="TexmLib/TexmLib.csproj" /> | ||||||
|  |   <Project Path="TextureDecoder/TextureDecoder.csproj" /> | ||||||
|  |   <Project Path="VarsetLib/VarsetLib.csproj" /> | ||||||
|  |   <Project Path="Visualisator/Visualisator.csproj" /> | ||||||
|  | </Solution> | ||||||
							
								
								
									
										77
									
								
								ParkanPlayground/Msh01.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								ParkanPlayground/Msh01.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | 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)) | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             element.Lod[0] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 8)); | ||||||
|  |             element.Lod[1] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 10)); | ||||||
|  |             element.Lod[2] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 12)); | ||||||
|  |             element.Lod[3] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 14)); | ||||||
|  |             element.Lod[4] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 16)); | ||||||
|  |             element.Lod[5] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 18)); | ||||||
|  |             element.Lod[6] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 20)); | ||||||
|  |             element.Lod[7] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 22)); | ||||||
|  |             element.Lod[8] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 24)); | ||||||
|  |             element.Lod[9] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 26)); | ||||||
|  |             element.Lod[10] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 28)); | ||||||
|  |             element.Lod[11] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 30)); | ||||||
|  |             element.Lod[12] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 32)); | ||||||
|  |             element.Lod[13] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 34)); | ||||||
|  |             element.Lod[14] = BinaryPrimitives.ReadUInt16LittleEndian(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[] Lod { get; set; } = new ushort[15]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										193
									
								
								ParkanPlayground/Msh02.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								ParkanPlayground/Msh02.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | |||||||
|  | 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.BottomFrontLeft = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8)) | ||||||
|  |         ); | ||||||
|  |         bb.BottomFrontRight = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20)) | ||||||
|  |         ); | ||||||
|  |         bb.BottomBackRight = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32)) | ||||||
|  |         ); | ||||||
|  |         bb.BottomBackLeft = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44)) | ||||||
|  |         ); | ||||||
|  |         bb.TopFrontLeft = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56)) | ||||||
|  |         ); | ||||||
|  |         bb.TopFrontRight = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68)) | ||||||
|  |         ); | ||||||
|  |         bb.TopBackRight = new Vector3( | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)), | ||||||
|  |             BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80)) | ||||||
|  |         ); | ||||||
|  |         bb.TopBackLeft = 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; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 140 байт в начале файла | ||||||
|  |     /// </summary> | ||||||
|  |     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 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; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 96 bytes - bounding box (8 points each 3 float = 96 bytes) | ||||||
|  |     /// 0x60 bytes or 0x18 by 4 bytes | ||||||
|  |     /// </summary> | ||||||
|  |     public class BoundingBox | ||||||
|  |     { | ||||||
|  |         public Vector3 BottomFrontLeft { get; set; } | ||||||
|  |         public Vector3 BottomFrontRight { get; set; } | ||||||
|  |         public Vector3 BottomBackRight { get; set; } | ||||||
|  |         public Vector3 BottomBackLeft { get; set; } | ||||||
|  |         public Vector3 TopFrontLeft { get; set; } | ||||||
|  |         public Vector3 TopFrontRight { get; set; } | ||||||
|  |         public Vector3 TopBackRight { get; set; } | ||||||
|  |         public Vector3 TopBackLeft { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								ParkanPlayground/Msh03.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								ParkanPlayground/Msh03.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | using System.Buffers.Binary; | ||||||
|  | using Common; | ||||||
|  | using NResLib; | ||||||
|  |  | ||||||
|  | namespace ParkanPlayground; | ||||||
|  |  | ||||||
|  | public class Msh03 | ||||||
|  | { | ||||||
|  |     public static List<Vector3> ReadComponent(FileStream mshFs, NResArchive mshNres) | ||||||
|  |     { | ||||||
|  |         var verticesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "03 00 00 00"); | ||||||
|  |  | ||||||
|  |         if (verticesFileEntry is null) | ||||||
|  |         { | ||||||
|  |             throw new Exception("Archive doesn't contain vertices file (03)"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (verticesFileEntry.ElementSize != 12) | ||||||
|  |         { | ||||||
|  |             throw new Exception("Vertices file (03) element size is not 12"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var verticesFile = new byte[verticesFileEntry.ElementCount * verticesFileEntry.ElementSize]; | ||||||
|  |         mshFs.Seek(verticesFileEntry.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |         mshFs.ReadExactly(verticesFile, 0, verticesFile.Length); | ||||||
|  |  | ||||||
|  |         var vertices = verticesFile.Chunk(12).Select(x => new Vector3( | ||||||
|  |                 BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(0)), | ||||||
|  |                 BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(4)), | ||||||
|  |                 BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(8)) | ||||||
|  |             ) | ||||||
|  |         ).ToList(); | ||||||
|  |         return vertices; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								ParkanPlayground/Msh07.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								ParkanPlayground/Msh07.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | 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.ReadUInt16LittleEndian(x.AsSpan(4)), | ||||||
|  |             Magic06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)), | ||||||
|  |             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 ushort Magic04 { get; set; } | ||||||
|  |         public ushort Magic06 { 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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								ParkanPlayground/Msh0D.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								ParkanPlayground/Msh0D.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | 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 = x.AsSpan(4)[0], | ||||||
|  |             Magic05 = x.AsSpan(5)[0], | ||||||
|  |             Magic06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)), | ||||||
|  |             CountOf06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)), | ||||||
|  |             IndexInto06 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0xA)), | ||||||
|  |             CountOf03 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xE)), | ||||||
|  |             IndexInto03 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x10)), | ||||||
|  |         }).ToList(); | ||||||
|  |  | ||||||
|  |         return elements; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class Msh0DElement | ||||||
|  |     { | ||||||
|  |         public uint Flags { get; set; } | ||||||
|  |          | ||||||
|  |         // Magic04 и Magic06 обрабатываются вместе | ||||||
|  |  | ||||||
|  |         public byte Magic04 { get; set; } | ||||||
|  |         public byte Magic05 { get; set; } | ||||||
|  |         public ushort Magic06 { get; set; } | ||||||
|  |         public ushort CountOf06 { get; set; } | ||||||
|  |         public int IndexInto06 { get; set; } | ||||||
|  |         public ushort CountOf03 { get; set; } | ||||||
|  |         public int IndexInto03 { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								ParkanPlayground/Msh15.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								ParkanPlayground/Msh15.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | using System.Buffers.Binary; | ||||||
|  | using NResLib; | ||||||
|  |  | ||||||
|  | namespace ParkanPlayground; | ||||||
|  |  | ||||||
|  | public static class Msh15 | ||||||
|  | { | ||||||
|  |     public static List<Msh15Element> ReadComponent( | ||||||
|  |         FileStream mshFs, NResArchive archive) | ||||||
|  |     { | ||||||
|  |         var entry = archive.Files.FirstOrDefault(x => x.FileType == "15 00 00 00"); | ||||||
|  |  | ||||||
|  |         if (entry is null) | ||||||
|  |         { | ||||||
|  |             throw new Exception("Archive doesn't contain file (15)"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var data = new byte[entry.ElementCount * entry.ElementSize]; | ||||||
|  |         mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin); | ||||||
|  |         mshFs.ReadExactly(data, 0, data.Length); | ||||||
|  |  | ||||||
|  |         var elementBytes = data.Chunk(28); | ||||||
|  |  | ||||||
|  |         var elements = elementBytes.Select(x => new Msh15Element() | ||||||
|  |         { | ||||||
|  |             Flags = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0)), | ||||||
|  |             Magic04 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)), | ||||||
|  |             Vertex1Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)), | ||||||
|  |             Vertex2Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(10)), | ||||||
|  |             Vertex3Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(12)), | ||||||
|  |             Magic0E = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(14)), | ||||||
|  |             Magic12 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(18)), | ||||||
|  |             Magic16 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(22)), | ||||||
|  |             Magic1A = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(26)), | ||||||
|  |              | ||||||
|  |         }).ToList(); | ||||||
|  |  | ||||||
|  |         return elements; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class Msh15Element | ||||||
|  |     { | ||||||
|  |         public uint Flags { get; set; } | ||||||
|  |  | ||||||
|  |         public uint Magic04 { get; set; } | ||||||
|  |         public ushort Vertex1Index { get; set; } | ||||||
|  |         public ushort Vertex2Index { get; set; } | ||||||
|  |         public ushort Vertex3Index { get; set; } | ||||||
|  |          | ||||||
|  |         public uint Magic0E { get; set; } | ||||||
|  |         public uint Magic12 { get; set; } | ||||||
|  |         public uint Magic16 { get; set; } | ||||||
|  |         public ushort Magic1A { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										231
									
								
								ParkanPlayground/MshConverter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								ParkanPlayground/MshConverter.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | |||||||
|  | using System.Text; | ||||||
|  | using Common; | ||||||
|  | using NResLib; | ||||||
|  |  | ||||||
|  | namespace ParkanPlayground; | ||||||
|  |  | ||||||
|  | public class MshConverter | ||||||
|  | { | ||||||
|  |     public void Convert(string mshPath) | ||||||
|  |     { | ||||||
|  |         var mshNresResult = NResParser.ReadFile(mshPath); | ||||||
|  |         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 component15 = Msh15.ReadComponent(mshFs, mshNres); | ||||||
|  |          | ||||||
|  |         var component0A = Msh0A.ReadComponent(mshFs, mshNres); | ||||||
|  |         var component07 = Msh07.ReadComponent(mshFs, mshNres); | ||||||
|  |         var component0D = Msh0D.ReadComponent(mshFs, mshNres); | ||||||
|  |  | ||||||
|  |         // Triangle Vertex Indices | ||||||
|  |         var component06 = Msh06.ReadComponent(mshFs, mshNres); | ||||||
|  |  | ||||||
|  |         // vertices | ||||||
|  |         var component03 = Msh03.ReadComponent(mshFs, mshNres); | ||||||
|  |  | ||||||
|  |         _ = 5; | ||||||
|  |  | ||||||
|  |         // --- Write OBJ --- | ||||||
|  |         using var sw = new StreamWriter("test.obj", false, new UTF8Encoding(false)); | ||||||
|  |  | ||||||
|  |         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 | ||||||
|  |  | ||||||
|  |         // 01 - это части меша (Piece) | ||||||
|  |         for (var pieceIndex = 0; pieceIndex < component01.Elements.Count; pieceIndex++) | ||||||
|  |         { | ||||||
|  |             Console.WriteLine($"Piece {pieceIndex}"); | ||||||
|  |             var piece01 = component01.Elements[pieceIndex]; | ||||||
|  |             // var state = (piece.State00 == 0xffff) ? 0 : piece.State00; | ||||||
|  |  | ||||||
|  |             for (var lodIndex = 0; lodIndex < piece01.Lod.Length; lodIndex++) | ||||||
|  |             { | ||||||
|  |                 var lod = piece01.Lod[lodIndex]; | ||||||
|  |                 if (lod == 0xffff) | ||||||
|  |                 { | ||||||
|  |                     // Console.WriteLine($"Piece {pieceIndex} has lod -1 at {lodIndex}. Skipping"); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 sw.WriteLine($"o piece_{pieceIndex}_lod_{lodIndex}"); | ||||||
|  |                 // 02 - Submesh | ||||||
|  |                 var part02 = component02.Elements[lod]; | ||||||
|  |  | ||||||
|  |                 int indexInto07 = part02.StartIndexIn07; | ||||||
|  |                 var comp07 = component07[indexInto07]; | ||||||
|  |                 Console.WriteLine($"Lod {lodIndex}"); | ||||||
|  |                 Console.WriteLine($"Comp07: {comp07.OffsetX}, {comp07.OffsetY}, {comp07.OffsetZ}"); | ||||||
|  |  | ||||||
|  |                 var element0Dstart = part02.StartOffsetIn0d; | ||||||
|  |                 var element0Dcount = part02.ByteLengthIn0D; | ||||||
|  |  | ||||||
|  |                 // Console.WriteLine($"Started piece {pieceIndex}. LOD={lod}. 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; // indices | ||||||
|  |  | ||||||
|  |                     uint maxIndex = element0D.CountOf03; | ||||||
|  |                     uint indicesCount = element0D.CountOf06; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                     // Convert IndexInto06 to ushort array index (3 ushorts per triangle) | ||||||
|  |                     // Console.WriteLine($"Processing 0D element[{element0Dstart + comp0Dindex}]. IndexInto03={indexInto03}, IndexInto06={indexInto06}. Number of triangles={indicesCount}"); | ||||||
|  |  | ||||||
|  |                     if (indicesCount != 0) | ||||||
|  |                     { | ||||||
|  |                         // sw.WriteLine($"o piece_{pieceIndex}_of_mesh_{comp0Dindex}"); | ||||||
|  |  | ||||||
|  |                         for (int ind = 0; ind < indicesCount; ind += 3) | ||||||
|  |                         { | ||||||
|  |                             // Each triangle uses 3 consecutive ushorts in component06 | ||||||
|  |  | ||||||
|  |                             // sw.WriteLine($"o piece_{pieceIndex}_of_mesh_{comp0Dindex}_tri_{ind}"); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                             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; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void Export(string filePath, IEnumerable<Vector3> vertices, List<IndexedEdge> edges) | ||||||
|  |     { | ||||||
|  |         using (var writer = new StreamWriter(filePath)) | ||||||
|  |         { | ||||||
|  |             writer.WriteLine("# Exported OBJ file"); | ||||||
|  |  | ||||||
|  |             // Write vertices | ||||||
|  |             foreach (var v in vertices) | ||||||
|  |             { | ||||||
|  |                 writer.WriteLine($"v {v.X:F2} {v.Y:F2} {v.Z:F2}"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Write edges as lines ("l" elements in .obj format) | ||||||
|  |             foreach (var e in edges) | ||||||
|  |             { | ||||||
|  |                 // OBJ uses 1-based indexing | ||||||
|  |                 writer.WriteLine($"l {e.Index1 + 1} {e.Index2 + 1}"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,13 +2,13 @@ | |||||||
|  |  | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <OutputType>Exe</OutputType> |         <OutputType>Exe</OutputType> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|  |       <ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" /> | ||||||
|       <ProjectReference Include="..\NResLib\NResLib.csproj" /> |       <ProjectReference Include="..\NResLib\NResLib.csproj" /> | ||||||
|  |       <ProjectReference Include="..\ScrLib\ScrLib.csproj" /> | ||||||
|  |       <ProjectReference Include="..\VarsetLib\VarsetLib.csproj" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -1,41 +1,17 @@ | |||||||
| using System.Buffers.Binary; | using System.Buffers.Binary; | ||||||
| using System.Text; | using Common; | ||||||
|  | using MissionTmaLib.Parsing; | ||||||
| using NResLib; | using NResLib; | ||||||
|  | using ParkanPlayground; | ||||||
|  |  | ||||||
| var libFile = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\ui\\ui_back.lib"; | // var cpDatEntryConverter = new CpDatEntryConverter(); | ||||||
|  | // cpDatEntryConverter.Convert(); | ||||||
|  |  | ||||||
| var parseResult = NResParser.ReadFile(libFile); | var converter = new MshConverter(); | ||||||
|  |  | ||||||
| if (parseResult.Error != null) | // converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\133_fr_m_bunker.msh"); | ||||||
| { | converter.Convert("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\DATA\\MAPS\\SC_1\\Land.msh"); | ||||||
|     Console.WriteLine(parseResult.Error); | // converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\73_fr_m_brige.msh"); | ||||||
|     return; | // 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"); | ||||||
| // var libFileName = new FileInfo(libFile).Name; |  | ||||||
| // |  | ||||||
| // if (Directory.Exists(libFileName)) |  | ||||||
| // { |  | ||||||
| //     Directory.Delete(libFileName, true); |  | ||||||
| // } |  | ||||||
| // |  | ||||||
| // var dir = Directory.CreateDirectory(libFileName); |  | ||||||
| // |  | ||||||
| // byte[] copyBuffer = new byte[8192]; |  | ||||||
| // |  | ||||||
| // foreach (var element in elements) |  | ||||||
| // { |  | ||||||
| //     nResFs.Seek(element.OffsetInFile, SeekOrigin.Begin); |  | ||||||
| //     using var destFs = new FileStream(Path.Combine(libFileName, element.FileName), FileMode.CreateNew); |  | ||||||
| // |  | ||||||
| //     var totalCopiedBytes = 0; |  | ||||||
| //     while (totalCopiedBytes < element.ItemLength) |  | ||||||
| //     { |  | ||||||
| //         var needReadBytes = Math.Min(element.ItemLength - totalCopiedBytes, copyBuffer.Length); |  | ||||||
| //         var readBytes = nResFs.Read(copyBuffer, 0, needReadBytes); |  | ||||||
| //          |  | ||||||
| //         destFs.Write(copyBuffer, 0, readBytes); |  | ||||||
| // |  | ||||||
| //         totalCopiedBytes += readBytes; |  | ||||||
| //     } |  | ||||||
| // } |  | ||||||
							
								
								
									
										389
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										389
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,11 +2,13 @@ | |||||||
|  |  | ||||||
| <div align="center"> | <div align="center"> | ||||||
|   <img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" /> |   <img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" /> | ||||||
|  |   <img width="817" height="376" alt="Image" src="https://github.com/user-attachments/assets/c4959106-9da4-4c78-a2b7-6c94e360a89e" /> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Сборка проекта | ## Сборка проекта | ||||||
|  |  | ||||||
| Проект написан на C# под `.NET 8` | Проект написан на C# под `.NET 9` | ||||||
|  |  | ||||||
| Вам должно хватить `dotnet build` для сборки всех проектов отдельно. | Вам должно хватить `dotnet build` для сборки всех проектов отдельно. | ||||||
|  |  | ||||||
| @@ -14,13 +16,13 @@ | |||||||
|  |  | ||||||
| ### Состояние проекта | ### Состояние проекта | ||||||
|  |  | ||||||
| - Распаковка всех `NRes` файлов | - Поддержка всех `NRes` файлов - звуки, музыка, текстуры, карты и другие файлы. Есть документация. | ||||||
| - Распаковка всех `TEXM` текстур | - Поддержка всех `TEXM` текстур. Есть документация. | ||||||
|   + формат 565 работает некорректно | - Поддержка файлов миссий `.tma`. | ||||||
|   + не понятно назначение двух магических чисел в заголовке | - Поддержка шрифтов TFNT. | ||||||
| - Распаковка данных миссии `.tma`. Пока работает чтение ареалов и кланов. | - Поддержка файлов скриптов `.scr`. | ||||||
| - Распаковка файла NL. Есть только декодирование заголовка. Формат кажется не используется игрой, а реверс бинарника игры то ещё занятие. | - Поддержка файлов параметров `.var`. | ||||||
| - Распаковка текстуры шрифта формата TFNT. Встроен прямо в UI. По сути шрифт это 4116 байт заголовка и текстура TEXM сразу после. | - Поддержка файлов схем объектов `.dat`. | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Структура проекта | ### Структура проекта | ||||||
| @@ -34,47 +36,6 @@ | |||||||
|  |  | ||||||
| Я конечно стараюсь, но ничего не обещаю. | Я конечно стараюсь, но ничего не обещаю. | ||||||
|  |  | ||||||
| #### NResUI |  | ||||||
|  |  | ||||||
| UI приложение на OpenGL + ImGui. |  | ||||||
|  |  | ||||||
| Туда постепенно добавляю логику. |  | ||||||
|  |  | ||||||
| #### NResLib |  | ||||||
|  |  | ||||||
| Библиотека распаковки формата NRes и всех файлов, которые им запакованы.  |  | ||||||
|  |  | ||||||
| Есть логика импорта и экспорта. Работа не завершена, но уже сейчас можно читать любые архивы такого формата. |  | ||||||
|  |  | ||||||
| #### TexmLib |  | ||||||
|  |  | ||||||
| Библиотека распаковки текстур TEXM. |  | ||||||
|  |  | ||||||
| Есть логика импорта и экспорта, хотя к UI последняя не подключена. |  | ||||||
|  |  | ||||||
| #### NLUnpacker |  | ||||||
|  |  | ||||||
| Приложение распаковки NL. |  | ||||||
|  |  | ||||||
| Работа приостановлена, т.к. кажется игра не использует эти файлы. |  | ||||||
|  |  | ||||||
| #### MissionDataUnpacker |  | ||||||
|  |  | ||||||
| Приложение распаковки миссий `.tma`. |  | ||||||
|  |  | ||||||
| Готово чтение ареалов и кланов. Пока в процессе. |  | ||||||
|  |  | ||||||
| #### ParkanPlayground |  | ||||||
|  |  | ||||||
| Пустой проект, использую для локальных тестов. |  | ||||||
|  |  | ||||||
| #### TextureDecoder |  | ||||||
|  |  | ||||||
| Приложение для экспорта текстур TEXM. |  | ||||||
|  |  | ||||||
| Изначально тут игрался с текстурами. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Для Reverse Engineering-а использую Ghidra | ## Для Reverse Engineering-а использую Ghidra | ||||||
|  |  | ||||||
| ### Наблюдения  | ### Наблюдения  | ||||||
| @@ -86,6 +47,336 @@ UI приложение на OpenGL + ImGui. | |||||||
| - Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки. | - Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки. | ||||||
| - Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`. | - Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`. | ||||||
|  |  | ||||||
|  | ## Как быстро найти текст среди всех файлов игры | ||||||
|  |  | ||||||
|  | ```shell | ||||||
|  | grep -rl --include="*" "s_tree_05" . | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Как быстро найти байты среди всех файлов игры | ||||||
|  |  | ||||||
|  | ```shell | ||||||
|  | grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' . | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Как работает игра | ||||||
|  |  | ||||||
|  | Главное меню: | ||||||
|  |  | ||||||
|  | Игра сканирует хардкод папку `missions` на наличие файлов миссий. (буквально 01, 02, 03 и т.д.) | ||||||
|  |  | ||||||
|  | Сначала игра читает название миссии из файла `descr` - тут название для меню. | ||||||
|  |  | ||||||
|  | - Одиночные игры - `missions/single.{index}/descr` | ||||||
|  | - Тренировочные миссии - `missions/tutorial.{index}/descr` | ||||||
|  | - Кампания - `missions/campaign/campaign.{index1}/descr` | ||||||
|  |   * Далее используются подпапки - `missions/campaign/campaign.{index1}/mission.{index2}/descr` | ||||||
|  |  | ||||||
|  | Как только игра не находит файл `descr`, заканчивается итерация по папкам (понял, т.к. пробуется файл 05 - он не существует). | ||||||
|  |  | ||||||
|  | Загрузка миссии: | ||||||
|  |  | ||||||
|  | Читается файл `ui/game_resources.cfg` | ||||||
|  | Из этого файла загружаются ресурсы | ||||||
|  | - `library = "ui\\ui.lib"` - загружается файл `ui.lib` | ||||||
|  | - `library = "ui\\font.lib"` - загружается файл `font.lib` | ||||||
|  | - `library = "sounds.lib"` - загружается файл `sounds.lib` | ||||||
|  | - `library = "voices.lib"` - загружается файл `voices.lib` | ||||||
|  |  | ||||||
|  | Затем игра читает `save/saveslots.cfg` - тут слоты сохранения | ||||||
|  |  | ||||||
|  | Затем `Comp.ini` - тут системные функции, которые используются для загрузки объектов. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | - `Host.url` - этого файла нет | ||||||
|  | - `palettes.lib` - тут палитры, но этот NRes пустой | ||||||
|  | - `system.rlb` - не понятно что | ||||||
|  | - `Textures.lib` - тут текстуры | ||||||
|  | - `Material.lib` - тут какие-то материалы - не понятно | ||||||
|  | - `LightMap.lib` - видимо это карты освещения - не понятно | ||||||
|  | - `sys.lib` - не понятно | ||||||
|  |  | ||||||
|  |  | ||||||
|  | - `ScanCode.dsc` - текстовый файл с мапом клавиш | ||||||
|  | - `command.dsc` - текстовый файл с мапом клавиш | ||||||
|  |  | ||||||
|  | Тут видимо идёт конфигурация ввода | ||||||
|  |  | ||||||
|  | - `table_1.man` - текстовый файл | ||||||
|  | - `table_2.man` - текстовый файл | ||||||
|  | - `hero.man` - текстовый файл | ||||||
|  | - `addition.man` - текстовый файл | ||||||
|  | - Снова `table_1.man` | ||||||
|  | - Снова `table_1.man` | ||||||
|  | - `M1.tbl` - текстовый файл | ||||||
|  | - Снова `table_2.man` | ||||||
|  | - Снова `table_2.man` | ||||||
|  | - `M2.tbl` - текстовый файл | ||||||
|  | - Снова `hero.man` | ||||||
|  | - Снова `hero.man` | ||||||
|  | - `HERO.TBL` | ||||||
|  | - Снова `addition.man` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | - `ui/hq.cfg` | ||||||
|  | - Снова `ui/hq.cfg` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Дальше непосредственно читается миссия | ||||||
|  |  | ||||||
|  | - `mission.cfg` - метадата миссии | ||||||
|  | - `units\\units\\prebld\\scr_pre1.dat` из метаданных `object prebuild` - `cp` файл (грузятся подряд все) | ||||||
|  | - Опять `ui/hq.cfg` | ||||||
|  | - `mistips.mis` - описание для игрока (экран F1) | ||||||
|  | - `scancode.dsc` - хз | ||||||
|  | - `command.dsc` - хз | ||||||
|  | - `ui_hero.man` - хз | ||||||
|  | - `ui_bots.man` - хз | ||||||
|  | - `ui_hq.man` - хз | ||||||
|  | - `ui_other.man` - хз | ||||||
|  | - Цикл чтения курсоров | ||||||
|  |   * `ui/cursor.cfg` - тут настройки курсора. | ||||||
|  |   * `ui/{name}` - курсор | ||||||
|  | - Снова `mission.cfg` - метадата миссии | ||||||
|  | - `descr` - название | ||||||
|  | - `data/textres.cfg` - конфиг текстов | ||||||
|  | - Снова `mission.cfg` - метадата миссии | ||||||
|  | - Ещё раз `mission.cfg` - метадата миссии | ||||||
|  | - `ui/minimap.lib` - NRes с текстурами миникарты.  | ||||||
|  | - `messages.cfg` - Tutorial messages | ||||||
|  |  | ||||||
|  | УРА НАКОНЕЦ-ТО `data.tma` | ||||||
|  |  | ||||||
|  | - Из `.tma` берётся LAND строка (я её так назвал) | ||||||
|  | - `DATA\\MAPS\\SC_3\\land1.wea` | ||||||
|  | - `DATA\\MAPS\\SC_3\\land2.wea` | ||||||
|  | - `BuildDat.lst` - Behaviour will use these schemes to Build Fortification | ||||||
|  | - `DATA\\MAPS\\SC_3\\land.map` | ||||||
|  | - `DATA\\MAPS\\SC_3\\land.msh` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | - `effects.rlb` | ||||||
|  |  | ||||||
|  | Цикл по кланам из `.tma` | ||||||
|  | - `MISSIONS\\SCRIPTS\\screampl.scr` | ||||||
|  | - `varset.var` | ||||||
|  | - `MISSIONS\\SCRIPTS\\varset.var` | ||||||
|  | - `MISSIONS\\SCRIPTS\\screampl.fml` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | - `missions/single.01/sky.ske` | ||||||
|  | - `missions/single.01/sky.wea` | ||||||
|  |  | ||||||
|  | Дальше начинаются объекты игры | ||||||
|  | - `"UNITS\\BUILDS\\BUNKER\\mbunk01.dat"` - cp файл | ||||||
|  |  | ||||||
|  | ## Загрузка `cp` файлов | ||||||
|  |  | ||||||
|  | `cp` файл - схема. Он содержит дерево частей объекта. | ||||||
|  |  | ||||||
|  | `cp` файл читается в `ArealMap.dll/CreateObjectFromScheme` | ||||||
|  |  | ||||||
|  | В зависимости от типа объекта внутри схемы (байты 4..8) выбирается функция, с помощью которой загружается схема. | ||||||
|  |  | ||||||
|  | Функция выбирается на основе файла `Comp.ini`. | ||||||
|  | - Для ClassBuilding (0x80000000) - вызывается функция c классом 3 (по таблице ниже Building). | ||||||
|  | - Для всех остальных - функция с классом 4 (по таблице ниже Agent). | ||||||
|  |  | ||||||
|  | На основе файла `Comp.ini` и первом вызове внутри функции `World3D.dll/CreateObject` ремаппинг id: | ||||||
|  |  | ||||||
|  | |  Class ID  |   ClassName   | Function                       | | ||||||
|  | |:----------:|:-------------:|--------------------------------| | ||||||
|  | |     1      |   Landscape   | `terrain.dll LoadLandscape`    | | ||||||
|  | |     2      |     Agent     | `animesh.dll LoadAgent`        | | ||||||
|  | |     3      |   Building    | `terrain.dll LoadBuilding`     | | ||||||
|  | |     4      |     Agent     | `animesh.dll LoadAgent`        | | ||||||
|  | |     5      |    Camera     | `terrain.dll LoadCamera`       | | ||||||
|  | |     7      |  Atmospehere  | `terrain.dll CreateAtmosphere` | | ||||||
|  | |     9      |     Agent     | `animesh.dll LoadAgent`        | | ||||||
|  | |     10     |     Agent     | `animesh.dll LoadAgent`        | | ||||||
|  | |     11     |   Research    | `misload.dll LoadResearch`     | | ||||||
|  | |     12     |     Agent     | `animesh.dll LoadAgent`        | | ||||||
|  |  | ||||||
|  | Будет дополняться по мере реверса. | ||||||
|  |  | ||||||
|  | Всем этим функциям передаётся `nres_file_name, nres_entry_name, 0, player_id` | ||||||
|  |  | ||||||
|  | ## `fr FORT` файл | ||||||
|  |  | ||||||
|  | Всегда 0x80 байт | ||||||
|  | Содержит 2 ссылки на файлы: | ||||||
|  | - `.bas` | ||||||
|  | - `.ctl` - вызывается `LoadAgent` | ||||||
|  |  | ||||||
|  | ## `.msh` | ||||||
|  |  | ||||||
|  | ### Описание ниже валидно только для моделей роботов и зданий. | ||||||
|  | ##### Land.msh использует другой формат, хотя 03 файл это всё ещё точки. | ||||||
|  |  | ||||||
|  | Загружается в `AniMesh.dll/LoadAniMesh` | ||||||
|  |  | ||||||
|  | - Тип 01 - заголовок. Он хранит список деталей (submesh) в разных LOD | ||||||
|  |   ``` | ||||||
|  |   нулевому элементу добавляется флаг 0x1000000 | ||||||
|  |   Содержит 2 ссылки на файлы анимаций (короткие - файл 13, длинные - файл 08) | ||||||
|  |   Если интерполируется анимация -0.5s короче чем magic1 у файла 13 | ||||||
|  |   И у файла есть OffsetIntoFile13 | ||||||
|  |   И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда) | ||||||
|  |   Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт) | ||||||
|  |   ``` | ||||||
|  | - Тип 02 - описание одного LOD Submesh | ||||||
|  |   ``` | ||||||
|  |   Вначале идёт заголовок 0x8C (140) байт | ||||||
|  |   В заголовке: | ||||||
|  |   8 Vector3 (x,y,z) - bounding box | ||||||
|  |   1 Vector4 - center | ||||||
|  |   1 Vector3 - bottom | ||||||
|  |   1 Vector3 - top | ||||||
|  |   1 float - xy_radius | ||||||
|  |   Далее инфа про куски меша | ||||||
|  |   ``` | ||||||
|  | - Тип 03 - это вершины (vertex) | ||||||
|  | - Тип 06 - индексы треугольников в файле 03 | ||||||
|  | - Тип 04 - скорее всего какие-то цвета RGBA или типа того | ||||||
|  | - Тип 08 - меш-анимации (см файл 01) | ||||||
|  |   ``` | ||||||
|  |   Индексируется по IndexInFile08 из файла 01 либо по файлу 13 через OffsetIntoFile13 | ||||||
|  |   Структура: | ||||||
|  |   Vector3 position; | ||||||
|  |   float time; // содержит только целые секунды | ||||||
|  |   short rotation_x; // делится на 32767 | ||||||
|  |   short rotation_y; // делится на 32767 | ||||||
|  |   short rotation_z; // делится на 32767 | ||||||
|  |   short rotation_w; // делится на 32767 | ||||||
|  |   --- | ||||||
|  |   Игра интерполирует анимацию между текущим стейтом и следующим по time. | ||||||
|  |   Если время интерполяции совпадает с исходным time, жёстко берётся первый стейт из 0x13. | ||||||
|  |   Если время интерполяции совпадает с конечным time, жёстко берётся второй стейт из 0x13. | ||||||
|  |   Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time) | ||||||
|  |   ``` | ||||||
|  | - Тип 12 - microtexture mapping | ||||||
|  | - Тип 13 - короткие меш-анимации (почему я это не дописал?) | ||||||
|  |   ``` | ||||||
|  |   Буквально (hex) | ||||||
|  |   00 01 01 02 ... | ||||||
|  |   ``` | ||||||
|  | - Тип 0A - ссылка на части меша, не упакованные в текущий меш (например у бункера 4 и 5 части хранятся в parts.rlb) | ||||||
|  |   ``` | ||||||
|  |   Не имеет фиксированной длины. Хранит строки в следующем формате. | ||||||
|  |   Игра обращается по индексу, пропуская суммарную длину и пропуская 4 байта на каждую строку (длина). | ||||||
|  |   т.е. буквально файл выглядит так | ||||||
|  |   00 00 00 00 - пустая строка | ||||||
|  |   03 00 00 00 - длина строки 1 | ||||||
|  |   73 74 72 00 - строка "str" + null terminator | ||||||
|  |   .. и повторяется до конца файла | ||||||
|  |   Кол-во элементов из файла 01 должно быть равно кол-ву строк в этом файле, хотя игра это не проверяет. | ||||||
|  |   Если у элемента эта строка равна "central", ему выставляется флаг (flag |= 1) | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | ## `.wea` | ||||||
|  |  | ||||||
|  | Загружается в `World3D.dll/LoadMatManager` | ||||||
|  |  | ||||||
|  | По сути это текстовый файл состоящий из 2 частей: | ||||||
|  | - Материалы | ||||||
|  |   ``` | ||||||
|  |   {count} | ||||||
|  |   {id} {name} | ||||||
|  |   ``` | ||||||
|  | - Карты освещения | ||||||
|  |   ``` | ||||||
|  |   LIGHTMAPS | ||||||
|  |   {count} | ||||||
|  |   {id} {name} | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | Может как-то анимироваться. Как - пока не понятно. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Внутренняя система ID | ||||||
|  |  | ||||||
|  | - `1` -  | ||||||
|  | - `4` - IShader | ||||||
|  | - `5` - ITerrain | ||||||
|  | - `6` - IGameObject (0x138) | ||||||
|  | - `7` - IAtmosphereObject | ||||||
|  | - `8` - ICamera | ||||||
|  | - `9` - IQueue | ||||||
|  | - `10` - IControl | ||||||
|  | - `0xb` - IAnimation | ||||||
|  | - `0xd` - IMatManager | ||||||
|  | - `0xe` - ILightManager | ||||||
|  | - `0x10` - IBehavior | ||||||
|  | - `0x11` - IBasement | ||||||
|  | - `0x12` - ICamera2 или IBufferingCamera | ||||||
|  | - `0x13` - IEffectManager | ||||||
|  | - `0x14` - IPosition | ||||||
|  | - `0x15` - IAgent | ||||||
|  | - `0x16` - ILifeSystem | ||||||
|  | - `0x17` - IBuilding - точно он, т.к. ArealMap.CreateObject на него проверяет | ||||||
|  | - `0x18` - IMesh2 | ||||||
|  | - `0x19` - IManManager | ||||||
|  | - `0x20` - IJointMesh | ||||||
|  | - `0x21` - IShade | ||||||
|  | - `0x23` - IGameSettings | ||||||
|  | - `0x24` - IGameObject2 | ||||||
|  | - `0x25` - unknown (implemented by AniMesh) | ||||||
|  | - `0x26` - unknown (implemented by AniMesh) | ||||||
|  | - `0x28` - ICollObject | ||||||
|  | - `0x101` - 3DRender | ||||||
|  | - `0x105` - NResFile | ||||||
|  | - `0x106` - NResFileMetadata | ||||||
|  | - `0x107` - 3DSound | ||||||
|  | - `0x201` - IWizard | ||||||
|  | - `0x202` - IItemManager | ||||||
|  | - `0x203` - ICollManager | ||||||
|  | - `0x301` - IArealMap | ||||||
|  | - `0x302` - ISystemArealMap | ||||||
|  | - `0x303` - IHallway | ||||||
|  | - `0x304` - Distributor | ||||||
|  | - `0x401` - ISuperAI | ||||||
|  | - `0x501` - MissionData | ||||||
|  | - `0x502` - ResTree | ||||||
|  | - `0x700` - NetWatcher | ||||||
|  | - `0x701` - INetworkInterface | ||||||
|  | - `0x10d` - CreateVertexBufferData | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Опции | ||||||
|  |  | ||||||
|  | World3D.dll содержит функцию CreateGameSettings. | ||||||
|  | Она создаёт объект настроек и далее вызывает методы в соседних библиотеках. | ||||||
|  | - Terrain.dll - InitializeSettings | ||||||
|  | - Effect.dll - InitializeSettings | ||||||
|  | - Control.dll - InitializeSettings | ||||||
|  |  | ||||||
|  | Остальные наверное не трогают настройки. | ||||||
|  |  | ||||||
|  | | Resource ID |    wOptionID    |            Name            | Default | Description        | | ||||||
|  | |:-----------:|:---------------:|:--------------------------:|:-------:|--------------------| | ||||||
|  | |      1      |   100 (0x64)    |      "Texture detail"      |         |                    | | ||||||
|  | |      2      |   101 (0x65)    |         "3D Sound"         |         |                    | | ||||||
|  | |      3      |   102 (0x66)    |    "Mouse sensitivity"     |         |                    | | ||||||
|  | |      4      |   103 (0x67)    |   "Joystick sensitivity"   |         |                    | | ||||||
|  | |      5      | !not a setting! |    "Illegal wOptionID"     |         |                    | | ||||||
|  | |      6      |   104 (0x68)    |     "Wait for retrace"     |         |                    | | ||||||
|  | |      7      |   105 (0x69)    |     "Inverse mouse X"      |         |                    | | ||||||
|  | |      8      |   106 (0x6a)    |     "Inverse mouse Y"      |         |                    | | ||||||
|  | |      9      |   107 (0x6b)    |    "Inverse joystick X"    |         |                    | | ||||||
|  | |     10      |   108 (0x6c)    |    "Inverse joystick Y"    |         |                    | | ||||||
|  | |     11      |   109 (0x6d)    |     "Use BumpMapping"      |         |                    | | ||||||
|  | |     12      |   110 (0x6e)    |     "3D Sound quality"     |         |                    | | ||||||
|  | |     13      |    90 (0x5a)    |      "Reverse sound"       |         |                    | | ||||||
|  | |     14      |    91 (0x5b)    |  "Sound buffer frequency"  |         |                    | | ||||||
|  | |     15      |    92 (0x5c)    | "Play sound buffer always" |         |                    | | ||||||
|  | |     16      |    93 (0x5d)    | "Select best sound device" |         |                    | | ||||||
|  | |    ----     |    30 (0x1e)    |        ShadeConfig         |         | из файла shade.cfg | | ||||||
|  | |    ----     |    (0x8001e)    |                            |         | добавляет AniMesh  | | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Контакты | ## Контакты | ||||||
|  |  | ||||||
| Вы можете связаться со мной в [Telegram](https://t.me/bird_egop). | Вы можете связаться со мной в [Telegram](https://t.me/bird_egop). | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								ScrLib/MissionTmaParseResult.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								ScrLib/MissionTmaParseResult.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace ScrLib; | ||||||
|  |  | ||||||
|  | public record ScrParseResult(ScrFile? Scr, string? Error); | ||||||
							
								
								
									
										60
									
								
								ScrLib/ScrFile.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ScrLib/ScrFile.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | namespace ScrLib; | ||||||
|  |  | ||||||
|  | public class ScrFile | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// тут всегда число 59 (0x3b) - это число известных игре скриптов | ||||||
|  |     /// </summary> | ||||||
|  |     public int Magic { get; set; } | ||||||
|  |  | ||||||
|  |     public int EntryCount { get; set; } | ||||||
|  |  | ||||||
|  |     public List<ScrEntry> Entries { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class ScrEntry | ||||||
|  | { | ||||||
|  |     public string Title { get; set; } | ||||||
|  |  | ||||||
|  |     public int Index { get; set; } | ||||||
|  |  | ||||||
|  |     public int InnerCount { get; set; } | ||||||
|  |  | ||||||
|  |     public List<ScrEntryInner> Inners { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class ScrEntryInner | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Номер скрипта в игре (это тех, которых 0x3b) | ||||||
|  |     /// </summary> | ||||||
|  |     public int ScriptIndex { get; set; } | ||||||
|  |  | ||||||
|  |     public int UnkInner2 { get; set; } | ||||||
|  |     public int UnkInner3 { get; set; } | ||||||
|  |  | ||||||
|  |     public ScrEntryInnerType Type { get; set; } | ||||||
|  |  | ||||||
|  |     public int UnkInner5 { get; set; } | ||||||
|  |  | ||||||
|  |     public int ArgumentsCount { get; set; } | ||||||
|  |  | ||||||
|  |     public List<int> Arguments { get; set; } | ||||||
|  |  | ||||||
|  |     public int UnkInner7 { get; set; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public enum ScrEntryInnerType | ||||||
|  | { | ||||||
|  |     Unspecified = -1, | ||||||
|  |     _0 = 0, | ||||||
|  |     _1 = 1, | ||||||
|  |     _2 = 2, | ||||||
|  |     _3 = 3, | ||||||
|  |     _4 = 4, | ||||||
|  |     CheckInternalState = 5, | ||||||
|  |     /// <summary> | ||||||
|  |     /// В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3 | ||||||
|  |     /// </summary> | ||||||
|  |     SetVarsetValue = 6, | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								ScrLib/ScrLib.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								ScrLib/ScrLib.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\Common\Common.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
							
								
								
									
										62
									
								
								ScrLib/ScrParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								ScrLib/ScrParser.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | using Common; | ||||||
|  |  | ||||||
|  | namespace ScrLib; | ||||||
|  |  | ||||||
|  | public class ScrParser | ||||||
|  | { | ||||||
|  |     public static ScrFile ReadFile(string filePath) | ||||||
|  |     { | ||||||
|  |         using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); | ||||||
|  |  | ||||||
|  |         return ReadFile(fs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static ScrFile ReadFile(Stream fs) | ||||||
|  |     { | ||||||
|  |         var scrFile = new ScrFile(); | ||||||
|  |  | ||||||
|  |         scrFile.Magic = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |         scrFile.EntryCount = fs.ReadInt32LittleEndian(); | ||||||
|  |         scrFile.Entries = []; | ||||||
|  |  | ||||||
|  |         for (var i = 0; i < scrFile.EntryCount; i++) | ||||||
|  |         { | ||||||
|  |             var entry = new ScrEntry(); | ||||||
|  |             entry.Title = fs.ReadLengthPrefixedString(); | ||||||
|  |  | ||||||
|  |             // тут игра дополнительно вычитывает ещё 1 байт, видимо как \0 для char* | ||||||
|  |             fs.ReadByte(); | ||||||
|  |  | ||||||
|  |             entry.Index = fs.ReadInt32LittleEndian(); | ||||||
|  |             entry.InnerCount = fs.ReadInt32LittleEndian(); | ||||||
|  |             entry.Inners = []; | ||||||
|  |             for (var i1 = 0; i1 < entry.InnerCount; i1++) | ||||||
|  |             { | ||||||
|  |                 var entryInner = new ScrEntryInner(); | ||||||
|  |                 entryInner.ScriptIndex = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |                 entryInner.UnkInner2 = fs.ReadInt32LittleEndian(); | ||||||
|  |                 entryInner.UnkInner3 = fs.ReadInt32LittleEndian(); | ||||||
|  |                 entryInner.Type = (ScrEntryInnerType)fs.ReadInt32LittleEndian(); | ||||||
|  |                 entryInner.UnkInner5 = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |                 entryInner.ArgumentsCount = fs.ReadInt32LittleEndian(); | ||||||
|  |  | ||||||
|  |                 entryInner.Arguments = []; | ||||||
|  |  | ||||||
|  |                 for (var i2 = 0; i2 < entryInner.ArgumentsCount; i2++) | ||||||
|  |                 { | ||||||
|  |                     entryInner.Arguments.Add(fs.ReadInt32LittleEndian()); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 entryInner.UnkInner7 = fs.ReadInt32LittleEndian(); | ||||||
|  |                 entry.Inners.Add(entryInner); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             scrFile.Entries.Add(entry); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return scrFile; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -180,14 +180,20 @@ public class TexmFile | |||||||
|         { |         { | ||||||
|             var rawPixel = span.Slice(i, 2); |             var rawPixel = span.Slice(i, 2); | ||||||
|  |  | ||||||
|             var g = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31); |             // swap endianess | ||||||
|             var b = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63); |             (rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]); | ||||||
|             var r = (byte)((rawPixel[1] & 0b11111) * 255 / 31); |  | ||||||
|  |  | ||||||
|             result[i / 2 * 4 + 0] = r; |             var r = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31); | ||||||
|             result[i / 2 * 4 + 1] = g; |             var g = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63); | ||||||
|             result[i / 2 * 4 + 2] = b; |             var b = (byte)((rawPixel[1] & 0b11111) * 255 / 31); | ||||||
|             result[i / 2 * 4 + 3] = r; |  | ||||||
|  |             result[i / 2 * 4 + 0] = (byte)(0xff - r); | ||||||
|  |             result[i / 2 * 4 + 1] = (byte)(0xff - g); | ||||||
|  |             result[i / 2 * 4 + 2] = (byte)(0xff - b); | ||||||
|  |             result[i / 2 * 4 + 3] = 0xff; | ||||||
|  |  | ||||||
|  |             // swap endianess back | ||||||
|  |             (rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return result; |         return result; | ||||||
| @@ -202,6 +208,9 @@ public class TexmFile | |||||||
|         { |         { | ||||||
|             var rawPixel = span.Slice(i, 2); |             var rawPixel = span.Slice(i, 2); | ||||||
|  |  | ||||||
|  |             // swap endianess | ||||||
|  |             (rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]); | ||||||
|  |  | ||||||
|             var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17); |             var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17); | ||||||
|             var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17); |             var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17); | ||||||
|             var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17); |             var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17); | ||||||
| @@ -211,6 +220,9 @@ public class TexmFile | |||||||
|             result[i / 2 * 4 + 1] = g; |             result[i / 2 * 4 + 1] = g; | ||||||
|             result[i / 2 * 4 + 2] = b; |             result[i / 2 * 4 + 2] = b; | ||||||
|             result[i / 2 * 4 + 3] = a; |             result[i / 2 * 4 + 3] = a; | ||||||
|  |              | ||||||
|  |             // swap endianess back | ||||||
|  |             (rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return result; |         return result; | ||||||
| @@ -247,16 +259,21 @@ public class TexmFile | |||||||
|         for (var i = 0; i < span.Length; i += 4) |         for (var i = 0; i < span.Length; i += 4) | ||||||
|         { |         { | ||||||
|             var rawPixel = span.Slice(i, 4); |             var rawPixel = span.Slice(i, 4); | ||||||
|  |             // swap endianess back | ||||||
|  |             // (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]); | ||||||
|  |  | ||||||
|             var b = rawPixel[0]; |             var r = rawPixel[0]; | ||||||
|             var g = rawPixel[1]; |             var g = rawPixel[1]; | ||||||
|             var r = rawPixel[2]; |             var b = rawPixel[2]; | ||||||
|             var a = rawPixel[3]; |             var a = rawPixel[3]; | ||||||
|  |  | ||||||
|             result[i + 0] = r; |             result[i + 0] = r; | ||||||
|             result[i + 1] = g; |             result[i + 1] = g; | ||||||
|             result[i + 2] = b; |             result[i + 2] = b; | ||||||
|             result[i + 3] = a; |             result[i + 3] = a; | ||||||
|  |              | ||||||
|  |             // swap endianess back | ||||||
|  |             // (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return result; |         return result; | ||||||
|   | |||||||
| @@ -1,13 +1,7 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|     <PropertyGroup> |  | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |  | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|         <PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" /> |         <PackageReference Include="SixLabors.ImageSharp" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -2,17 +2,10 @@ | |||||||
|  |  | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <OutputType>Exe</OutputType> |         <OutputType>Exe</OutputType> | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Nullable>enable</Nullable> |  | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|       <PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" /> |       <PackageReference Include="SixLabors.ImageSharp" /> | ||||||
|     </ItemGroup> |  | ||||||
|  |  | ||||||
|     <ItemGroup> |  | ||||||
|       <ProjectReference Include="..\TexmLib\TexmLib.csproj" /> |  | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								VarsetLib/VarsetItem.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								VarsetLib/VarsetItem.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | namespace VarsetLib; | ||||||
|  |  | ||||||
|  | public record VarsetItem(string Type, string Name, string Value); | ||||||
							
								
								
									
										3
									
								
								VarsetLib/VarsetLib.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								VarsetLib/VarsetLib.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										82
									
								
								VarsetLib/VarsetParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								VarsetLib/VarsetParser.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | namespace VarsetLib; | ||||||
|  |  | ||||||
|  | public class VarsetParser | ||||||
|  | { | ||||||
|  |     public static List<VarsetItem> Parse(string path) | ||||||
|  |     { | ||||||
|  |         using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); | ||||||
|  |  | ||||||
|  |         return Parse(fs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static List<VarsetItem> Parse(Stream fs) | ||||||
|  |     { | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             var reader = new StreamReader(fs); | ||||||
|  |  | ||||||
|  |             List<VarsetItem> varsetItems = []; | ||||||
|  |  | ||||||
|  |             var lineIndex = 1; | ||||||
|  |             while (!reader.EndOfStream) | ||||||
|  |             { | ||||||
|  |                 var line = reader.ReadLine()!; | ||||||
|  |                 if (line.Length == 0) | ||||||
|  |                 { | ||||||
|  |                     lineIndex++; | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (line.StartsWith("//") || line.Trim().StartsWith("//")) | ||||||
|  |                 { | ||||||
|  |                     lineIndex++; | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!line.StartsWith("VAR")) | ||||||
|  |                 { | ||||||
|  |                     Console.WriteLine($"Error on line: {lineIndex}! Not starting with VAR"); | ||||||
|  |                     lineIndex++; | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var openParenthesisIndex = line.IndexOf("("); | ||||||
|  |                 var closeParenthesisIndex = line.IndexOf(")"); | ||||||
|  |  | ||||||
|  |                 if (openParenthesisIndex == -1 || closeParenthesisIndex == -1 || | ||||||
|  |                     closeParenthesisIndex <= openParenthesisIndex) | ||||||
|  |                 { | ||||||
|  |                     Console.WriteLine($"Error on line: {lineIndex}! VAR() format invalid"); | ||||||
|  |                     lineIndex++; | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var arguments = line.Substring(openParenthesisIndex + 1, | ||||||
|  |                     closeParenthesisIndex - openParenthesisIndex - 1); | ||||||
|  |  | ||||||
|  |                 var parts = arguments.Trim() | ||||||
|  |                     .Split(','); | ||||||
|  |  | ||||||
|  |                 var type = parts[0] | ||||||
|  |                     .Trim(); | ||||||
|  |  | ||||||
|  |                 var name = parts[1] | ||||||
|  |                     .Trim(); | ||||||
|  |  | ||||||
|  |                 var value = parts[2] | ||||||
|  |                     .Trim(); | ||||||
|  |  | ||||||
|  |                 var item = new VarsetItem(type, name, value); | ||||||
|  |                 varsetItems.Add(item); | ||||||
|  |  | ||||||
|  |                 lineIndex++; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return varsetItems; | ||||||
|  |         } | ||||||
|  |         catch | ||||||
|  |         { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										180
									
								
								Visualisator/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								Visualisator/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | |||||||
|  | // Configure window options | ||||||
|  |  | ||||||
|  | using System.Buffers.Binary; | ||||||
|  | using System.Numerics; | ||||||
|  | using Silk.NET.OpenGL; | ||||||
|  | using Silk.NET.Windowing; | ||||||
|  |  | ||||||
|  | public static class Program | ||||||
|  | { | ||||||
|  |     private static string vertexShaderSource = @" | ||||||
|  |             #version 330 core | ||||||
|  |             layout (location = 0) in vec3 aPos; | ||||||
|  |             uniform mat4 uMVP; | ||||||
|  |  | ||||||
|  |             void main() | ||||||
|  |             { | ||||||
|  |                 gl_Position = uMVP * vec4(aPos, 1.0); | ||||||
|  |                 gl_PointSize = 8.0; | ||||||
|  |             } | ||||||
|  |         "; | ||||||
|  |  | ||||||
|  |     private static string fragmentShaderSource = @" | ||||||
|  |             #version 330 core | ||||||
|  |             out vec4 FragColor; | ||||||
|  |  | ||||||
|  |             void main() | ||||||
|  |             { | ||||||
|  |                 FragColor = vec4(1.0, 1.0, 1.0, 1.0); // White points | ||||||
|  |             } | ||||||
|  |         "; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private static IWindow? window; | ||||||
|  |     private static GL? gl = null; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private static uint shaderProgram = uint.MaxValue; | ||||||
|  |     private static uint vao = uint.MaxValue; | ||||||
|  |     private static uint vbo = uint.MaxValue; | ||||||
|  |     private static Matrix4x4 mvp = new Matrix4x4(); | ||||||
|  |     private static float[] points = [];  | ||||||
|  |  | ||||||
|  |     public static void Main(string[] args) | ||||||
|  |     { | ||||||
|  |         var path = "C:\\ParkanUnpacked\\Land.msh\\2_03 00 00 00_Land.bin"; | ||||||
|  |         var bytes = File.ReadAllBytes(path); | ||||||
|  |  | ||||||
|  |         points = new float[bytes.Length / 4]; | ||||||
|  |         for (int i = 0; i < bytes.Length / 4; i++) | ||||||
|  |         { | ||||||
|  |             points[i] = BinaryPrimitives.ReadSingleBigEndian(bytes.AsSpan()[(i * 4)..]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var options = WindowOptions.Default; | ||||||
|  |         options.API = new GraphicsAPI(ContextAPI.OpenGL, new APIVersion(3, 3)); | ||||||
|  |         options.Title = "3D Points with Silk.NET"; | ||||||
|  |         window = Window.Create(options); | ||||||
|  |  | ||||||
|  |         window.Load += OnLoad; | ||||||
|  |         window.Render += OnRender; | ||||||
|  |         window.Run(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     unsafe static void OnLoad() | ||||||
|  |     { | ||||||
|  |         gl = window.CreateOpenGL(); | ||||||
|  |         // Compile shaders | ||||||
|  |         uint vertexShader = gl.CreateShader(ShaderType.VertexShader); | ||||||
|  |         gl.ShaderSource(vertexShader, vertexShaderSource); | ||||||
|  |         gl.CompileShader(vertexShader); | ||||||
|  |         CheckShaderCompile(vertexShader); | ||||||
|  |  | ||||||
|  |         uint fragmentShader = gl.CreateShader(ShaderType.FragmentShader); | ||||||
|  |         gl.ShaderSource(fragmentShader, fragmentShaderSource); | ||||||
|  |         gl.CompileShader(fragmentShader); | ||||||
|  |         CheckShaderCompile(fragmentShader); | ||||||
|  |  | ||||||
|  |         // Create shader program | ||||||
|  |         shaderProgram = gl.CreateProgram(); | ||||||
|  |         gl.AttachShader(shaderProgram, vertexShader); | ||||||
|  |         gl.AttachShader(shaderProgram, fragmentShader); | ||||||
|  |         gl.LinkProgram(shaderProgram); | ||||||
|  |         CheckProgramLink(shaderProgram); | ||||||
|  |  | ||||||
|  |         gl.DeleteShader(vertexShader); | ||||||
|  |         gl.DeleteShader(fragmentShader); | ||||||
|  |  | ||||||
|  |         // Create VAO and VBO | ||||||
|  |         vao = gl.GenVertexArray(); | ||||||
|  |         gl.BindVertexArray(vao); | ||||||
|  |  | ||||||
|  |         vbo = gl.GenBuffer(); | ||||||
|  |         gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo); | ||||||
|  |         unsafe | ||||||
|  |         { | ||||||
|  |             fixed (float* ptr = points) | ||||||
|  |             { | ||||||
|  |                 gl.BufferData( | ||||||
|  |                     BufferTargetARB.ArrayBuffer, | ||||||
|  |                     (nuint) (points.Length * sizeof(float)), | ||||||
|  |                     ptr, | ||||||
|  |                     BufferUsageARB.StaticDraw | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         gl.VertexAttribPointer( | ||||||
|  |             0, | ||||||
|  |             3, | ||||||
|  |             VertexAttribPointerType.Float, | ||||||
|  |             false, | ||||||
|  |             3 * sizeof(float), | ||||||
|  |             (void*) 0 | ||||||
|  |         ); | ||||||
|  |         gl.EnableVertexAttribArray(0); | ||||||
|  |  | ||||||
|  |         gl.BindVertexArray(0); // Unbind VAO | ||||||
|  |  | ||||||
|  |         gl.Enable(EnableCap.DepthTest); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     unsafe static void OnRender(double dt) | ||||||
|  |     { | ||||||
|  |         gl.ClearColor( | ||||||
|  |             0.1f, | ||||||
|  |             0.1f, | ||||||
|  |             0.1f, | ||||||
|  |             1.0f | ||||||
|  |         ); | ||||||
|  |         gl.Clear((uint) (ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit)); | ||||||
|  |  | ||||||
|  |         // Set up MVP matrix | ||||||
|  |         Matrix4x4 view = Matrix4x4.CreateLookAt( | ||||||
|  |             new Vector3(100, 100, 40), // Camera position | ||||||
|  |             Vector3.Zero, // Look at origin | ||||||
|  |             Vector3.UnitY | ||||||
|  |         ); // Up direction | ||||||
|  |         Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView( | ||||||
|  |             (float) Math.PI / 4f, // 45 degrees | ||||||
|  |             (float) window.Size.X / window.Size.Y, | ||||||
|  |             0.1f, | ||||||
|  |             100f | ||||||
|  |         ); | ||||||
|  |         mvp = view * projection; | ||||||
|  |          | ||||||
|  |         gl.UseProgram(shaderProgram); | ||||||
|  |  | ||||||
|  |         // Set MVP matrix (transpose=true for column-major format) | ||||||
|  |         int mvpLocation = gl.GetUniformLocation(shaderProgram, "uMVP"); | ||||||
|  |  | ||||||
|  |         fixed (Matrix4x4* ptr = &mvp) | ||||||
|  |         { | ||||||
|  |             gl.UniformMatrix4( | ||||||
|  |                 mvpLocation, | ||||||
|  |                 1, | ||||||
|  |                 true, | ||||||
|  |                 (float*) ptr | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         gl.BindVertexArray(vao); | ||||||
|  |         gl.DrawArrays(PrimitiveType.Points, 0, (uint) (points.Length / 3)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Error checking methods | ||||||
|  |     static void CheckShaderCompile(uint shader) | ||||||
|  |     { | ||||||
|  |         gl.GetShader(shader, ShaderParameterName.CompileStatus, out int success); | ||||||
|  |         if (success == 0) | ||||||
|  |             Console.WriteLine(gl.GetShaderInfoLog(shader)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static void CheckProgramLink(uint program) | ||||||
|  |     { | ||||||
|  |         gl.GetProgram(program, ProgramPropertyARB.LinkStatus, out int success); | ||||||
|  |         if (success == 0) | ||||||
|  |             Console.WriteLine(gl.GetProgramInfoLog(program)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								Visualisator/Visualisator.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Visualisator/Visualisator.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |     <PropertyGroup> | ||||||
|  |         <OutputType>Exe</OutputType> | ||||||
|  |         <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||||
|  |     </PropertyGroup> | ||||||
|  |  | ||||||
|  |     <ItemGroup> | ||||||
|  |         <PackageReference Include="Microsoft.Extensions.DependencyInjection" /> | ||||||
|  |         <PackageReference Include="NativeFileDialogSharp"  /> | ||||||
|  |         <PackageReference Include="Silk.NET"  /> | ||||||
|  |         <PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" /> | ||||||
|  |     </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
		Reference in New Issue
	
	Block a user