mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-09-13 18:20:30 +03:00
Compare commits
25 Commits
backup2611
...
windows-re
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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>
|
@@ -1,7 +1,7 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace MissionTmaLib.Parsing;
|
||||
namespace Common;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
@@ -13,6 +13,14 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32LittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
@@ -21,6 +29,24 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedString(this FileStream 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 ReadLengthPrefixedString(this FileStream fs)
|
||||
{
|
||||
var len = fs.ReadInt32LittleEndian();
|
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.Runtime.InteropServices;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
namespace Common;
|
||||
|
||||
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
||||
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);
|
12
CpDatLib/CpDatEntry.cs
Normal file
12
CpDatLib/CpDatEntry.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace CpDatLib;
|
||||
|
||||
public record CpDatEntry(
|
||||
string ArchiveFile,
|
||||
string ArchiveEntryName,
|
||||
int Magic1,
|
||||
int Magic2,
|
||||
string Description,
|
||||
int Magic3,
|
||||
int ChildCount, // игра не хранит это число в объекте, но оно есть в файле
|
||||
List<CpDatEntry> Children
|
||||
);
|
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);
|
68
CpDatLib/CpDatParser.cs
Normal file
68
CpDatLib/CpDatParser.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Common;
|
||||
|
||||
namespace CpDatLib;
|
||||
|
||||
public class CpDatParser
|
||||
{
|
||||
public static CpDatParseResult Parse(string filePath)
|
||||
{
|
||||
Span<byte> f0f1 = stackalloc byte[4];
|
||||
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
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(FileStream 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.ReadNullTerminatedString();
|
||||
|
||||
fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||
var magic3 = 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, magic3, 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>
|
@@ -2,9 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -2,9 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
@@ -12,7 +12,7 @@ public class ClanInfo
|
||||
/// </summary>
|
||||
public ClanType ClanType { get; set; }
|
||||
|
||||
public string UnkString2 { get; set; }
|
||||
public string ScriptsString { get; set; }
|
||||
public int UnknownClanPartCount { get; set; }
|
||||
public List<UnknownClanTreeInfoPart> UnknownParts { get; set; }
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ClanInfo
|
||||
/// Игра называет этот путь TreeName
|
||||
/// </summary>
|
||||
public string ResearchNResPath { get; set; }
|
||||
public int UnkInt3 { get; set; }
|
||||
public int Brains { get; set; }
|
||||
public int AlliesMapCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@@ -7,18 +7,3 @@ public enum ClanType
|
||||
AI = 2,
|
||||
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
|
||||
{
|
||||
@@ -22,7 +24,7 @@ public class GameObjectInfo
|
||||
/// </remarks>
|
||||
public int OwningClanIndex { get; set; }
|
||||
|
||||
public int UnknownInt3 { get; set; }
|
||||
public int Order { get; set; }
|
||||
|
||||
public Vector3 Position { 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);
|
@@ -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">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
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,10 +1,12 @@
|
||||
namespace MissionTmaLib.Parsing;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib.Parsing;
|
||||
|
||||
public class MissionTmaParser
|
||||
{
|
||||
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);
|
||||
|
||||
var arealData = LoadAreals(fs);
|
||||
|
||||
@@ -78,7 +80,7 @@ public class MissionTmaParser
|
||||
// MISSIONS\SCRIPTS\default
|
||||
// MISSIONS\SCRIPTS\tut1_pl
|
||||
// MISSIONS\SCRIPTS\tut1_en
|
||||
clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString();
|
||||
clanTreeInfo.ScriptsString = fileStream.ReadLengthPrefixedString();
|
||||
}
|
||||
|
||||
if (2 < clanFeatureSet)
|
||||
@@ -118,7 +120,7 @@ public class MissionTmaParser
|
||||
|
||||
if (4 < clanFeatureSet)
|
||||
{
|
||||
clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian();
|
||||
clanTreeInfo.Brains = fileStream.ReadInt32LittleEndian();
|
||||
}
|
||||
|
||||
if (5 < clanFeatureSet)
|
||||
@@ -185,7 +187,11 @@ public class MissionTmaParser
|
||||
|
||||
if (3 < gameObjectsFeatureSet)
|
||||
{
|
||||
gameObjectInfo.UnknownInt3 = fileStream.ReadInt32LittleEndian();
|
||||
gameObjectInfo.Order = fileStream.ReadInt32LittleEndian();
|
||||
if (gameObjectInfo.Type == GameObjectType.Building)
|
||||
{
|
||||
gameObjectInfo.Order += int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
// читает 12 байт
|
||||
@@ -379,5 +385,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);
|
@@ -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>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
</ItemGroup>
|
||||
|
||||
</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 decodedHeader = new byte[fileCount * 32];
|
||||
|
@@ -19,10 +19,11 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
|
||||
/// каждый элемент это 64 байта,
|
||||
/// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64)
|
||||
/// </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="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="Magic3">[40..44] Неизвестное число</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>
|
||||
public record ListMetadataItem(
|
||||
string FileType,
|
||||
uint ElementCount,
|
||||
int Magic1,
|
||||
int FileLength,
|
||||
int Magic2,
|
||||
int ElementSize,
|
||||
string FileName,
|
||||
int Magic3,
|
||||
int Magic4,
|
||||
|
@@ -30,7 +30,7 @@ public class NResExporter
|
||||
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);
|
||||
}
|
||||
|
@@ -1,9 +1,3 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -7,7 +7,7 @@ public static class NResParser
|
||||
{
|
||||
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);
|
||||
|
||||
if (nResFs.Length < 16)
|
||||
{
|
||||
@@ -48,13 +48,32 @@ public static class NResParser
|
||||
for (int i = 0; i < header.FileCount; i++)
|
||||
{
|
||||
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(
|
||||
new ListMetadataItem(
|
||||
FileType: Encoding.ASCII.GetString(metaDataBuffer[..8]).TrimEnd('\0'),
|
||||
FileType: type,
|
||||
ElementCount: type2,
|
||||
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
|
||||
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'),
|
||||
Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]),
|
||||
Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]),
|
||||
|
@@ -32,10 +32,11 @@
|
||||
3. В конце файла есть метаданные.
|
||||
Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта.
|
||||
|
||||
+ [0..8] ASCII описание типа файла, например TEXM или MAT0
|
||||
+ [0..4] ASCII описание типа файла, например TEXM или MAT0
|
||||
+ [4..8] Количество элементов в файле (если файл составной, например .trf)
|
||||
+ [8..12] Неизвестное число
|
||||
+ [12..16] Длина файла в байтах
|
||||
+ [16..20] Неизвестное число
|
||||
+ [16..20] Размер элемента в файле (если файл составной, например .trf)
|
||||
+ [20..40] ASCII имя файла
|
||||
+ [40..44] Неизвестное число
|
||||
+ [44..48] Неизвестное число
|
||||
|
@@ -53,6 +53,10 @@ public class App
|
||||
serviceCollection.AddSingleton(new NResExplorerViewModel());
|
||||
serviceCollection.AddSingleton(new TexmExplorerViewModel());
|
||||
serviceCollection.AddSingleton(new MissionTmaViewModel());
|
||||
serviceCollection.AddSingleton(new BinaryExplorerViewModel());
|
||||
serviceCollection.AddSingleton(new ScrViewModel());
|
||||
serviceCollection.AddSingleton(new VarsetViewModel());
|
||||
serviceCollection.AddSingleton(new CpDatSchemeViewModel());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
112
NResUI/ImGuiUI/CpDatSchemeExplorer.cs
Normal file
112
NResUI/ImGuiUI/CpDatSchemeExplorer.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
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", 7, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
{
|
||||
ImGui.TableSetupColumn("Уровень вложенности");
|
||||
ImGui.TableSetupColumn("Архив");
|
||||
ImGui.TableSetupColumn("Элемент");
|
||||
ImGui.TableSetupColumn("Magic1");
|
||||
ImGui.TableSetupColumn("Magic2");
|
||||
ImGui.TableSetupColumn("Описание");
|
||||
ImGui.TableSetupColumn("Magic3");
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
for (int i = 0; i < _viewModel.FlatList.Count; i++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
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.Magic3.ToString());
|
||||
}
|
||||
|
||||
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("Magic3: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.Magic3.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 MissionTmaLib;
|
||||
using MissionTmaLib.Parsing;
|
||||
using NativeFileDialogSharp;
|
||||
using NResLib;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
using ScrLib;
|
||||
using TexmLib;
|
||||
using VarsetLib;
|
||||
|
||||
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()
|
||||
{
|
||||
if (ImGui.BeginMenuBar())
|
||||
@@ -40,7 +39,7 @@ namespace NResUI.ImGuiUI
|
||||
|
||||
var parseResult = NResParser.ReadFile(path);
|
||||
|
||||
_nResExplorerViewModel.SetParseResult(parseResult, path);
|
||||
nResExplorerViewModel.SetParseResult(parseResult, path);
|
||||
Console.WriteLine("Read NRES");
|
||||
}
|
||||
}
|
||||
@@ -57,7 +56,7 @@ namespace NResUI.ImGuiUI
|
||||
|
||||
var parseResult = TexmParser.ReadFromStream(fs, path);
|
||||
|
||||
_texmExplorerViewModel.SetParseResult(parseResult, path);
|
||||
texmExplorerViewModel.SetParseResult(parseResult, path);
|
||||
Console.WriteLine("Read TEXM");
|
||||
}
|
||||
}
|
||||
@@ -76,8 +75,8 @@ namespace NResUI.ImGuiUI
|
||||
|
||||
var parseResult = TexmParser.ReadFromStream(fs, path);
|
||||
|
||||
_texmExplorerViewModel.SetParseResult(parseResult, path);
|
||||
Console.WriteLine("Read TEXM");
|
||||
texmExplorerViewModel.SetParseResult(parseResult, path);
|
||||
Console.WriteLine("Read TFNT TEXM");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +89,56 @@ namespace NResUI.ImGuiUI
|
||||
var path = result.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"))
|
||||
{
|
||||
@@ -104,9 +148,9 @@ namespace NResUI.ImGuiUI
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -18,9 +18,14 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
{
|
||||
if (ImGui.Begin("Mission TMA Explorer"))
|
||||
{
|
||||
ImGui.Text("data.tma - это файл миссии. Его можно найти в папке MISSIONS");
|
||||
ImGui.Separator();
|
||||
|
||||
var mission = _viewModel.Mission;
|
||||
if (_viewModel.HasFile && mission is not null)
|
||||
{
|
||||
ImGui.Columns(2);
|
||||
|
||||
ImGui.Text("Путь к файлу: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(_viewModel.Path);
|
||||
@@ -65,20 +70,32 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(arealInfo.Coords[k].X.ToString("F2"));
|
||||
ImGui.Text(
|
||||
arealInfo.Coords[k]
|
||||
.X.ToString("F2")
|
||||
);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(arealInfo.Coords[k].Y.ToString("F2"));
|
||||
ImGui.Text(
|
||||
arealInfo.Coords[k]
|
||||
.Y.ToString("F2")
|
||||
);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(arealInfo.Coords[k].Z.ToString("F2"));
|
||||
ImGui.Text(
|
||||
arealInfo.Coords[k]
|
||||
.Z.ToString("F2")
|
||||
);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
@@ -118,10 +135,10 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.ClanType.ToReadableString());
|
||||
|
||||
ImGui.Text("Неизвестная строка 1: ");
|
||||
Utils.ShowHint("Кажется это путь к файлу поведения (Behavior), но пока не понятно. Обычно пути соответствуют 2 файла.");
|
||||
ImGui.Text("Скрипты поведения (Mission Scripts): ");
|
||||
Utils.ShowHint("Пути к файлам .scr и .fml описывающих настройку объектов и поведение AI");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.UnkString2);
|
||||
ImGui.Text(clanInfo.ScriptsString);
|
||||
|
||||
if (clanInfo.UnknownParts.Count > 0)
|
||||
{
|
||||
@@ -162,14 +179,14 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
ImGui.Text("Отсутствует неизвестная часть");
|
||||
}
|
||||
|
||||
ImGui.Text("Путь к файлу .trf: ");
|
||||
Utils.ShowHint("Не до конца понятно, что означает, вероятно это NRes с деревом исследований");
|
||||
ImGui.Text("Дерево исследований: ");
|
||||
Utils.ShowHint("NRes с деревом исследований");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.ResearchNResPath);
|
||||
|
||||
ImGui.Text("Неизвестное число 3: ");
|
||||
ImGui.Text("Количество мозгов (Brains))): ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.UnkInt3.ToString());
|
||||
ImGui.Text(clanInfo.Brains.ToString());
|
||||
|
||||
ImGui.Text("Матрица союзников");
|
||||
Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга");
|
||||
@@ -185,7 +202,10 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(alliesMapKey);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(clanInfo.AlliesMap[alliesMapKey].ToString());
|
||||
ImGui.Text(
|
||||
clanInfo.AlliesMap[alliesMapKey]
|
||||
.ToString()
|
||||
);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
@@ -204,8 +224,189 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
|
||||
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.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
|
||||
{
|
||||
|
@@ -17,6 +17,9 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
{
|
||||
if (ImGui.Begin("NRes Explorer"))
|
||||
{
|
||||
ImGui.Text("NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res");
|
||||
ImGui.Separator();
|
||||
|
||||
if (!_viewModel.HasFile)
|
||||
{
|
||||
ImGui.Text("No NRes is opened");
|
||||
@@ -46,12 +49,13 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
|
||||
|
||||
|
||||
if (ImGui.BeginTable("content", 11))
|
||||
if (ImGui.BeginTable("content", 12, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
{
|
||||
ImGui.TableSetupColumn("Тип файла");
|
||||
ImGui.TableSetupColumn("Кол-во элементов");
|
||||
ImGui.TableSetupColumn("Magic1");
|
||||
ImGui.TableSetupColumn("Длина файла в байтах");
|
||||
ImGui.TableSetupColumn("Magic2");
|
||||
ImGui.TableSetupColumn("Размер элемента");
|
||||
ImGui.TableSetupColumn("Имя файла");
|
||||
ImGui.TableSetupColumn("Magic3");
|
||||
ImGui.TableSetupColumn("Magic4");
|
||||
@@ -68,6 +72,8 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.Archive.Files[i].FileType);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.Archive.Files[i].ElementCount.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(
|
||||
_viewModel.Archive.Files[i]
|
||||
.Magic1.ToString()
|
||||
@@ -80,7 +86,7 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(
|
||||
_viewModel.Archive.Files[i]
|
||||
.Magic2.ToString()
|
||||
.ElementSize.ToString()
|
||||
);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.Archive.Files[i].FileName);
|
||||
|
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"))
|
||||
{
|
||||
ImGui.Text("TEXM - это файл текстуры. Их можно найти внутри NRes архивов, например Textures.lib");
|
||||
ImGui.Separator();
|
||||
|
||||
if (!_viewModel.HasFile)
|
||||
{
|
||||
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;
|
||||
}
|
40
NResUI/Models/CpDatSchemeViewModel.cs
Normal file
40
NResUI/Models/CpDatSchemeViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
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)
|
||||
{
|
||||
FlatList = [];
|
||||
|
||||
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; } = [];
|
||||
}
|
@@ -2,9 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -12,16 +9,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
|
||||
<PackageReference Include="Silk.NET" Version="2.22.0" />
|
||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="NativeFileDialogSharp" />
|
||||
<PackageReference Include="Silk.NET" />
|
||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CpDatLib\CpDatLib.csproj" />
|
||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -137,9 +137,9 @@ namespace NResUI
|
||||
|
||||
public void SetLod(int @base, int min, int max)
|
||||
{
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, in @base);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, in min);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, in max);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
186
ParkanPlayground/Msh02.cs
Normal file
186
ParkanPlayground/Msh02.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Buffers.Binary;
|
||||
using Common;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh02
|
||||
{
|
||||
public static Msh02Component ReadComponent(FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var fileEntry = archive.Files.FirstOrDefault(x => x.FileType == "02 00 00 00");
|
||||
|
||||
if (fileEntry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain 02 component");
|
||||
}
|
||||
|
||||
var data = new byte[fileEntry.FileLength];
|
||||
mshFs.Seek(fileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var header = data.AsSpan(0, 0x8c); // 140 bytes header
|
||||
|
||||
var center = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x64)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x68))
|
||||
);
|
||||
var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c));
|
||||
|
||||
var bb = new BoundingBox();
|
||||
bb.Vec1 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8))
|
||||
);
|
||||
bb.Vec2 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20))
|
||||
);
|
||||
bb.Vec3 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32))
|
||||
);
|
||||
bb.Vec4 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44))
|
||||
);
|
||||
bb.Vec5 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56))
|
||||
);
|
||||
bb.Vec6 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68))
|
||||
);
|
||||
bb.Vec7 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80))
|
||||
);
|
||||
bb.Vec8 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92))
|
||||
);
|
||||
|
||||
var bottom = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(112)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(116)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(120))
|
||||
);
|
||||
|
||||
var top = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(124)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(128)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(132))
|
||||
);
|
||||
|
||||
var xyRadius = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(136));
|
||||
|
||||
|
||||
List<Msh02Element> elements = new List<Msh02Element>();
|
||||
var skippedHeader = data.AsSpan(0x8c); // skip header
|
||||
for (var i = 0; i < fileEntry.ElementCount; i++)
|
||||
{
|
||||
var element = new Msh02Element();
|
||||
element.StartIndexIn07 =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 0));
|
||||
element.CountIn07 =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 2));
|
||||
element.StartOffsetIn0d =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 4));
|
||||
element.ByteLengthIn0D =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 6));
|
||||
element.LocalMinimum = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 8)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 12)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 16))
|
||||
);
|
||||
element.LocalMaximum = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 20)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 24)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 28))
|
||||
);
|
||||
element.Center = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 32)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 36)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 40))
|
||||
);
|
||||
element.Vector4 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 44)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 48)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 52))
|
||||
);
|
||||
element.Vector5 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 56)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 64))
|
||||
);
|
||||
elements.Add(element);
|
||||
|
||||
_ = 5;
|
||||
}
|
||||
|
||||
return new Msh02Component()
|
||||
{
|
||||
Header = new Msh02Header()
|
||||
{
|
||||
BoundingBox = bb,
|
||||
Center = center,
|
||||
CenterW = centerW,
|
||||
Bottom = bottom,
|
||||
Top = top,
|
||||
XYRadius = xyRadius
|
||||
},
|
||||
Elements = elements
|
||||
};
|
||||
}
|
||||
|
||||
public class Msh02Component
|
||||
{
|
||||
public Msh02Header Header { get; set; }
|
||||
public List<Msh02Element> Elements { get; set; }
|
||||
}
|
||||
|
||||
public class Msh02Header
|
||||
{
|
||||
public BoundingBox BoundingBox { get; set; }
|
||||
public Vector3 Center { get; set; }
|
||||
public float CenterW { get; set; }
|
||||
public Vector3 Bottom { get; set; }
|
||||
public Vector3 Top { get; set; }
|
||||
public float XYRadius { get; set; }
|
||||
}
|
||||
|
||||
public class 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; }
|
||||
}
|
||||
|
||||
public class BoundingBox
|
||||
{
|
||||
public Vector3 Vec1 { get; set; }
|
||||
public Vector3 Vec2 { get; set; }
|
||||
public Vector3 Vec3 { get; set; }
|
||||
public Vector3 Vec4 { get; set; }
|
||||
public Vector3 Vec5 { get; set; }
|
||||
public Vector3 Vec6 { get; set; }
|
||||
public Vector3 Vec7 { get; set; }
|
||||
public Vector3 Vec8 { get; set; }
|
||||
}
|
||||
}
|
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; }
|
||||
}
|
||||
}
|
229
ParkanPlayground/MshConverter.cs
Normal file
229
ParkanPlayground/MshConverter.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
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 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>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,41 +1,17 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using System.Buffers.Binary;
|
||||
using Common;
|
||||
using MissionTmaLib.Parsing;
|
||||
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)
|
||||
{
|
||||
Console.WriteLine(parseResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
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");
|
||||
// converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\73_fr_m_brige.msh");
|
||||
// converter.Convert("E:\\ParkanUnpacked\\intsys.rlb\\277_MESH_o_pws_l_01.msh");
|
||||
// converter.Convert("E:\\ParkanUnpacked\\static.rlb\\2_MESH_s_stn_0_01.msh");
|
||||
// converter.Convert("E:\\ParkanUnpacked\\bases.rlb\\25_MESH_R_H_02.msh");
|
388
README.md
388
README.md
@@ -2,11 +2,13 @@
|
||||
|
||||
<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="817" height="376" alt="Image" src="https://github.com/user-attachments/assets/c4959106-9da4-4c78-a2b7-6c94e360a89e" />
|
||||
</div>
|
||||
|
||||
|
||||
## Сборка проекта
|
||||
|
||||
Проект написан на C# под `.NET 8`
|
||||
Проект написан на C# под `.NET 9`
|
||||
|
||||
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
|
||||
|
||||
@@ -14,13 +16,13 @@
|
||||
|
||||
### Состояние проекта
|
||||
|
||||
- Распаковка всех `NRes` файлов
|
||||
- Распаковка всех `TEXM` текстур
|
||||
+ формат 565 работает некорректно
|
||||
+ не понятно назначение двух магических чисел в заголовке
|
||||
- Распаковка данных миссии `.tma`. Пока работает чтение ареалов и кланов.
|
||||
- Распаковка файла NL. Есть только декодирование заголовка. Формат кажется не используется игрой, а реверс бинарника игры то ещё занятие.
|
||||
- Распаковка текстуры шрифта формата TFNT. Встроен прямо в UI. По сути шрифт это 4116 байт заголовка и текстура TEXM сразу после.
|
||||
- Поддержка всех `NRes` файлов - звуки, музыка, текстуры, карты и другие файлы. Есть документация.
|
||||
- Поддержка всех `TEXM` текстур. Есть документация.
|
||||
- Поддержка файлов миссий `.tma`.
|
||||
- Поддержка шрифтов TFNT.
|
||||
- Поддержка файлов скриптов `.scr`.
|
||||
- Поддержка файлов параметров `.var`.
|
||||
- Поддержка файлов схем объектов `.dat`.
|
||||
|
||||
|
||||
### Структура проекта
|
||||
@@ -34,47 +36,6 @@
|
||||
|
||||
Я конечно стараюсь, но ничего не обещаю.
|
||||
|
||||
#### NResUI
|
||||
|
||||
UI приложение на OpenGL + ImGui.
|
||||
|
||||
Туда постепенно добавляю логику.
|
||||
|
||||
#### NResLib
|
||||
|
||||
Библиотека распаковки формата NRes и всех файлов, которые им запакованы.
|
||||
|
||||
Есть логика импорта и экспорта. Работа не завершена, но уже сейчас можно читать любые архивы такого формата.
|
||||
|
||||
#### TexmLib
|
||||
|
||||
Библиотека распаковки текстур TEXM.
|
||||
|
||||
Есть логика импорта и экспорта, хотя к UI последняя не подключена.
|
||||
|
||||
#### NLUnpacker
|
||||
|
||||
Приложение распаковки NL.
|
||||
|
||||
Работа приостановлена, т.к. кажется игра не использует эти файлы.
|
||||
|
||||
#### MissionDataUnpacker
|
||||
|
||||
Приложение распаковки миссий `.tma`.
|
||||
|
||||
Готово чтение ареалов и кланов. Пока в процессе.
|
||||
|
||||
#### ParkanPlayground
|
||||
|
||||
Пустой проект, использую для локальных тестов.
|
||||
|
||||
#### TextureDecoder
|
||||
|
||||
Приложение для экспорта текстур TEXM.
|
||||
|
||||
Изначально тут игрался с текстурами.
|
||||
|
||||
|
||||
## Для Reverse Engineering-а использую Ghidra
|
||||
|
||||
### Наблюдения
|
||||
@@ -86,6 +47,335 @@ UI приложение на OpenGL + ImGui.
|
||||
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
|
||||
- Игра нормально не работает на 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` - IShadeConfig (у меня в папке с игрой его не оказалось)
|
||||
- `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
|
||||
- `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).
|
||||
|
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>
|
57
ScrLib/ScrParser.cs
Normal file
57
ScrLib/ScrParser.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Common;
|
||||
|
||||
namespace ScrLib;
|
||||
|
||||
public class ScrParser
|
||||
{
|
||||
public static ScrFile ReadFile(string filePath)
|
||||
{
|
||||
var fs = new FileStream(filePath, FileMode.Open);
|
||||
|
||||
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 g = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
||||
var b = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
||||
var r = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
||||
// swap endianess
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
|
||||
result[i / 2 * 4 + 0] = r;
|
||||
result[i / 2 * 4 + 1] = g;
|
||||
result[i / 2 * 4 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = r;
|
||||
var r = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
||||
var g = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
||||
var b = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
||||
|
||||
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;
|
||||
@@ -202,6 +208,9 @@ public class TexmFile
|
||||
{
|
||||
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 b = (byte)(((rawPixel[0] >> 0) & 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 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = a;
|
||||
|
||||
// swap endianess back
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -247,16 +259,21 @@ public class TexmFile
|
||||
for (var i = 0; i < span.Length; 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 r = rawPixel[2];
|
||||
var b = rawPixel[2];
|
||||
var a = rawPixel[3];
|
||||
|
||||
result[i + 0] = r;
|
||||
result[i + 1] = g;
|
||||
result[i + 2] = b;
|
||||
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;
|
||||
|
@@ -1,13 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -2,17 +2,10 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
</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>
|
68
VarsetLib/VarsetParser.cs
Normal file
68
VarsetLib/VarsetParser.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace VarsetLib;
|
||||
|
||||
public class VarsetParser
|
||||
{
|
||||
public static List<VarsetItem> Parse(string path)
|
||||
{
|
||||
FileStream fs = new FileStream(path, FileMode.Open);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
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