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.Buffers.Binary;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace MissionTmaLib.Parsing;
|
namespace Common;
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
@@ -13,6 +13,14 @@ public static class Extensions
|
|||||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
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)
|
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||||
{
|
{
|
||||||
Span<byte> buf = stackalloc byte[4];
|
Span<byte> buf = stackalloc byte[4];
|
||||||
@@ -21,6 +29,24 @@ public static class Extensions
|
|||||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
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)
|
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||||
{
|
{
|
||||||
var len = fs.ReadInt32LittleEndian();
|
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.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MissionTmaLib;
|
namespace Common;
|
||||||
|
|
||||||
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
||||||
public class IntFloatValue(Span<byte> span)
|
public class IntFloatValue(Span<byte> span)
|
3
Common/Vector3.cs
Normal file
3
Common/Vector3.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Common;
|
||||||
|
|
||||||
|
public record Vector3(float X, float Y, float Z);
|
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>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
@@ -12,7 +12,7 @@ public class ClanInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ClanType ClanType { get; set; }
|
public ClanType ClanType { get; set; }
|
||||||
|
|
||||||
public string UnkString2 { get; set; }
|
public string ScriptsString { get; set; }
|
||||||
public int UnknownClanPartCount { get; set; }
|
public int UnknownClanPartCount { get; set; }
|
||||||
public List<UnknownClanTreeInfoPart> UnknownParts { get; set; }
|
public List<UnknownClanTreeInfoPart> UnknownParts { get; set; }
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public class ClanInfo
|
|||||||
/// Игра называет этот путь TreeName
|
/// Игра называет этот путь TreeName
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ResearchNResPath { get; set; }
|
public string ResearchNResPath { get; set; }
|
||||||
public int UnkInt3 { get; set; }
|
public int Brains { get; set; }
|
||||||
public int AlliesMapCount { get; set; }
|
public int AlliesMapCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -6,19 +6,4 @@ public enum ClanType
|
|||||||
Player = 1,
|
Player = 1,
|
||||||
AI = 2,
|
AI = 2,
|
||||||
Neutral = 3
|
Neutral = 3
|
||||||
}
|
|
||||||
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
public static string ToReadableString(this ClanType clanType)
|
|
||||||
{
|
|
||||||
return clanType switch
|
|
||||||
{
|
|
||||||
ClanType.Environment => $"Окружение ({clanType:D})",
|
|
||||||
ClanType.Player => $"Игрок ({clanType:D})",
|
|
||||||
ClanType.AI => $"AI ({clanType:D})",
|
|
||||||
ClanType.Neutral => $"Нейтральный ({clanType:D})",
|
|
||||||
_ => $"Неизвестный ({clanType:D})"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
28
MissionTmaLib/Extensions.cs
Normal file
28
MissionTmaLib/Extensions.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static string ToReadableString(this ClanType clanType)
|
||||||
|
{
|
||||||
|
return clanType switch
|
||||||
|
{
|
||||||
|
ClanType.Environment => $"Окружение ({clanType:D})",
|
||||||
|
ClanType.Player => $"Игрок ({clanType:D})",
|
||||||
|
ClanType.AI => $"AI ({clanType:D})",
|
||||||
|
ClanType.Neutral => $"Нейтральный ({clanType:D})",
|
||||||
|
_ => $"Неизвестный ({clanType:D})"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToReadableString(this GameObjectType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
GameObjectType.Building => $"Строение {type:D}",
|
||||||
|
GameObjectType.Warbot => $"Варбот {type:D}",
|
||||||
|
GameObjectType.Tree => $"Дерево {type:D}",
|
||||||
|
GameObjectType.Stone => $"Камень {type:D}",
|
||||||
|
_ => $"Неизвестный ({type:D})"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public class GameObjectInfo
|
public class GameObjectInfo
|
||||||
{
|
{
|
||||||
@@ -22,7 +24,7 @@ public class GameObjectInfo
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public int OwningClanIndex { get; set; }
|
public int OwningClanIndex { get; set; }
|
||||||
|
|
||||||
public int UnknownInt3 { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
public Vector3 Position { get; set; }
|
public Vector3 Position { get; set; }
|
||||||
public Vector3 Rotation { get; set; }
|
public Vector3 Rotation { get; set; }
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownInt2, float UnknownFloat, int UnknownInt3);
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);
|
@@ -1,9 +1,5 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
<PropertyGroup>
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
</ItemGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
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 class MissionTmaParser
|
||||||
{
|
{
|
||||||
public static MissionTmaParseResult ReadFile(string filePath)
|
public static MissionTmaParseResult ReadFile(string filePath)
|
||||||
{
|
{
|
||||||
var fs = new FileStream(filePath, FileMode.Open);
|
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
|
||||||
var arealData = LoadAreals(fs);
|
var arealData = LoadAreals(fs);
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ public class MissionTmaParser
|
|||||||
// MISSIONS\SCRIPTS\default
|
// MISSIONS\SCRIPTS\default
|
||||||
// MISSIONS\SCRIPTS\tut1_pl
|
// MISSIONS\SCRIPTS\tut1_pl
|
||||||
// MISSIONS\SCRIPTS\tut1_en
|
// MISSIONS\SCRIPTS\tut1_en
|
||||||
clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString();
|
clanTreeInfo.ScriptsString = fileStream.ReadLengthPrefixedString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (2 < clanFeatureSet)
|
if (2 < clanFeatureSet)
|
||||||
@@ -118,7 +120,7 @@ public class MissionTmaParser
|
|||||||
|
|
||||||
if (4 < clanFeatureSet)
|
if (4 < clanFeatureSet)
|
||||||
{
|
{
|
||||||
clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian();
|
clanTreeInfo.Brains = fileStream.ReadInt32LittleEndian();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (5 < clanFeatureSet)
|
if (5 < clanFeatureSet)
|
||||||
@@ -185,7 +187,11 @@ public class MissionTmaParser
|
|||||||
|
|
||||||
if (3 < gameObjectsFeatureSet)
|
if (3 < gameObjectsFeatureSet)
|
||||||
{
|
{
|
||||||
gameObjectInfo.UnknownInt3 = fileStream.ReadInt32LittleEndian();
|
gameObjectInfo.Order = fileStream.ReadInt32LittleEndian();
|
||||||
|
if (gameObjectInfo.Type == GameObjectType.Building)
|
||||||
|
{
|
||||||
|
gameObjectInfo.Order += int.MaxValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// читает 12 байт
|
// читает 12 байт
|
||||||
@@ -378,6 +384,4 @@ public class MissionTmaParser
|
|||||||
lodeData
|
lodeData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record MissionTma(ArealsFileData ArealData, ClansFileData ClansData, GameObjectsFileData GameObjectsData);
|
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
@@ -1,3 +0,0 @@
|
|||||||
namespace MissionTmaLib;
|
|
||||||
|
|
||||||
public record Vector3(float X, float Y, float Z);
|
|
@@ -1,11 +1,11 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,7 +1,40 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont.rlb");
|
var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont-1.rlb");
|
||||||
|
|
||||||
|
var header = fileBytes.AsSpan().Slice(0, 32);
|
||||||
|
|
||||||
|
var nlHeaderBytes = header.Slice(0, 2);
|
||||||
|
var mustBeZero = header[2];
|
||||||
|
var mustBeOne = header[3];
|
||||||
|
var numberOfEntriesBytes = header.Slice(4, 2);
|
||||||
|
var sortingFlagBytes = header.Slice(14, 2);
|
||||||
|
var decryptionKeyBytes = header.Slice(20, 2);
|
||||||
|
|
||||||
|
var numberOfEntries = BinaryPrimitives.ReadInt16LittleEndian(numberOfEntriesBytes);
|
||||||
|
var sortingFlag = BinaryPrimitives.ReadInt16LittleEndian(sortingFlagBytes);
|
||||||
|
var decryptionKey = BinaryPrimitives.ReadInt16LittleEndian(decryptionKeyBytes);
|
||||||
|
|
||||||
|
var headerSize = numberOfEntries * 32;
|
||||||
|
|
||||||
|
var decryptedHeader = new byte[headerSize];
|
||||||
|
|
||||||
|
var keyLow = decryptionKeyBytes[0];
|
||||||
|
var keyHigh = decryptionKeyBytes[1];
|
||||||
|
for (var i = 0; i < headerSize; i++)
|
||||||
|
{
|
||||||
|
byte tmp = (byte)((keyLow << 1) ^ keyHigh);
|
||||||
|
keyLow = tmp;
|
||||||
|
keyHigh = (byte)((keyHigh >> 1) ^ tmp);
|
||||||
|
decryptedHeader[i] = (byte)(fileBytes[32 + i] ^ tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
var decryptedHeaderString = Encoding.ASCII.GetString(decryptedHeader, 0, headerSize);
|
||||||
|
var entries = decryptedHeader.Chunk(32).ToArray();
|
||||||
|
var entriesStrings = entries.Select(x => Encoding.ASCII.GetString(x, 0, x.Length)).ToArray();
|
||||||
|
|
||||||
|
File.WriteAllBytes("export.nl", decryptedHeader);
|
||||||
var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2));
|
var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2));
|
||||||
|
|
||||||
var decodedHeader = new byte[fileCount * 32];
|
var decodedHeader = new byte[fileCount * 32];
|
||||||
|
@@ -19,10 +19,11 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
|
|||||||
/// каждый элемент это 64 байта,
|
/// каждый элемент это 64 байта,
|
||||||
/// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64)
|
/// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="FileType">[0..8] ASCII описание типа файла, например TEXM или MAT0</param>
|
/// <param name="FileType">[0..4] ASCII описание типа файла, например TEXM или MAT0</param>
|
||||||
|
/// <param name="ElementCount">[4..8] Количество элементов в файле (если файл составной, например .trf) </param>
|
||||||
/// <param name="Magic1">[8..12] Неизвестное число</param>
|
/// <param name="Magic1">[8..12] Неизвестное число</param>
|
||||||
/// <param name="FileLength">[12..16] Длина файла в байтах</param>
|
/// <param name="FileLength">[12..16] Длина файла в байтах</param>
|
||||||
/// <param name="Magic2">[16..20] Неизвестное число</param>
|
/// <param name="ElementSize">[16..20] Размер элемента в файле (если файл составной, например .trf) </param>
|
||||||
/// <param name="FileName">[20..40] ASCII имя файла</param>
|
/// <param name="FileName">[20..40] ASCII имя файла</param>
|
||||||
/// <param name="Magic3">[40..44] Неизвестное число</param>
|
/// <param name="Magic3">[40..44] Неизвестное число</param>
|
||||||
/// <param name="Magic4">[44..48] Неизвестное число</param>
|
/// <param name="Magic4">[44..48] Неизвестное число</param>
|
||||||
@@ -32,9 +33,10 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
|
|||||||
/// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param>
|
/// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param>
|
||||||
public record ListMetadataItem(
|
public record ListMetadataItem(
|
||||||
string FileType,
|
string FileType,
|
||||||
|
uint ElementCount,
|
||||||
int Magic1,
|
int Magic1,
|
||||||
int FileLength,
|
int FileLength,
|
||||||
int Magic2,
|
int ElementSize,
|
||||||
string FileName,
|
string FileName,
|
||||||
int Magic3,
|
int Magic3,
|
||||||
int Magic4,
|
int Magic4,
|
||||||
|
@@ -30,7 +30,7 @@ public class NResExporter
|
|||||||
extension = ".bin";
|
extension = ".bin";
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{fileName}{extension}");
|
var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{archiveFile.FileType}_{fileName}{extension}");
|
||||||
|
|
||||||
File.WriteAllBytes(targetFilePath, buffer);
|
File.WriteAllBytes(targetFilePath, buffer);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,3 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -7,7 +7,7 @@ public static class NResParser
|
|||||||
{
|
{
|
||||||
public static NResParseResult ReadFile(string path)
|
public static NResParseResult ReadFile(string path)
|
||||||
{
|
{
|
||||||
using FileStream nResFs = new FileStream(path, FileMode.Open);
|
using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
|
||||||
if (nResFs.Length < 16)
|
if (nResFs.Length < 16)
|
||||||
{
|
{
|
||||||
@@ -48,13 +48,32 @@ public static class NResParser
|
|||||||
for (int i = 0; i < header.FileCount; i++)
|
for (int i = 0; i < header.FileCount; i++)
|
||||||
{
|
{
|
||||||
nResFs.ReadExactly(metaDataBuffer);
|
nResFs.ReadExactly(metaDataBuffer);
|
||||||
|
var type = "";
|
||||||
|
|
||||||
|
for (int j = 0; j < 4; j++)
|
||||||
|
{
|
||||||
|
if (!char.IsLetterOrDigit((char)metaDataBuffer[j]))
|
||||||
|
{
|
||||||
|
type += metaDataBuffer[j]
|
||||||
|
.ToString("X2") + " ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
type += (char)metaDataBuffer[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var type2 = BinaryPrimitives.ReadUInt32LittleEndian(metaDataBuffer.Slice(4));
|
||||||
|
|
||||||
|
type = type.Trim();
|
||||||
|
|
||||||
elements.Add(
|
elements.Add(
|
||||||
new ListMetadataItem(
|
new ListMetadataItem(
|
||||||
FileType: Encoding.ASCII.GetString(metaDataBuffer[..8]).TrimEnd('\0'),
|
FileType: type,
|
||||||
|
ElementCount: type2,
|
||||||
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
|
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
|
||||||
FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]),
|
FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]),
|
||||||
Magic2: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
|
ElementSize: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
|
||||||
FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'),
|
FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'),
|
||||||
Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]),
|
Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]),
|
||||||
Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]),
|
Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]),
|
||||||
|
@@ -32,10 +32,11 @@
|
|||||||
3. В конце файла есть метаданные.
|
3. В конце файла есть метаданные.
|
||||||
Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта.
|
Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта.
|
||||||
|
|
||||||
+ [0..8] ASCII описание типа файла, например TEXM или MAT0
|
+ [0..4] ASCII описание типа файла, например TEXM или MAT0
|
||||||
|
+ [4..8] Количество элементов в файле (если файл составной, например .trf)
|
||||||
+ [8..12] Неизвестное число
|
+ [8..12] Неизвестное число
|
||||||
+ [12..16] Длина файла в байтах
|
+ [12..16] Длина файла в байтах
|
||||||
+ [16..20] Неизвестное число
|
+ [16..20] Размер элемента в файле (если файл составной, например .trf)
|
||||||
+ [20..40] ASCII имя файла
|
+ [20..40] ASCII имя файла
|
||||||
+ [40..44] Неизвестное число
|
+ [40..44] Неизвестное число
|
||||||
+ [44..48] Неизвестное число
|
+ [44..48] Неизвестное число
|
||||||
|
@@ -53,6 +53,10 @@ public class App
|
|||||||
serviceCollection.AddSingleton(new NResExplorerViewModel());
|
serviceCollection.AddSingleton(new NResExplorerViewModel());
|
||||||
serviceCollection.AddSingleton(new TexmExplorerViewModel());
|
serviceCollection.AddSingleton(new TexmExplorerViewModel());
|
||||||
serviceCollection.AddSingleton(new MissionTmaViewModel());
|
serviceCollection.AddSingleton(new MissionTmaViewModel());
|
||||||
|
serviceCollection.AddSingleton(new BinaryExplorerViewModel());
|
||||||
|
serviceCollection.AddSingleton(new ScrViewModel());
|
||||||
|
serviceCollection.AddSingleton(new VarsetViewModel());
|
||||||
|
serviceCollection.AddSingleton(new CpDatSchemeViewModel());
|
||||||
|
|
||||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
262
NResUI/ImGuiUI/BinaryExplorerPanel.cs
Normal file
262
NResUI/ImGuiUI/BinaryExplorerPanel.cs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ImGuiNET;
|
||||||
|
using NativeFileDialogSharp;
|
||||||
|
using NResUI.Abstractions;
|
||||||
|
using NResUI.Models;
|
||||||
|
|
||||||
|
namespace NResUI.ImGuiUI;
|
||||||
|
|
||||||
|
public class BinaryExplorerPanel : IImGuiPanel
|
||||||
|
{
|
||||||
|
private readonly BinaryExplorerViewModel _viewModel;
|
||||||
|
|
||||||
|
public BinaryExplorerPanel(BinaryExplorerViewModel viewModel)
|
||||||
|
{
|
||||||
|
_viewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnImGuiRender()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
if (ImGui.Begin("Binary Explorer"))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Open File"))
|
||||||
|
{
|
||||||
|
OpenFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_viewModel.HasFile)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(_viewModel.Path);
|
||||||
|
|
||||||
|
if (ImGui.Button("Сохранить регионы"))
|
||||||
|
{
|
||||||
|
File.WriteAllText("preset.json", JsonSerializer.Serialize(_viewModel.Regions));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Загрузить регионы"))
|
||||||
|
{
|
||||||
|
_viewModel.Regions = JsonSerializer.Deserialize<List<Region>>(File.ReadAllText("preset.json"))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int bytesPerRow = 16;
|
||||||
|
if (ImGui.BeginTable("HexTable", bytesPerRow + 1, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.WidthFixed);
|
||||||
|
for (var i = 0; i < bytesPerRow; i++)
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn(i.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
for (int i = 0; i < _viewModel.Data.Length; i += bytesPerRow)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{i:x8} ");
|
||||||
|
|
||||||
|
for (int j = 0; j < 16; j++)
|
||||||
|
{
|
||||||
|
var index = i + j;
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (index < _viewModel.Data.Length)
|
||||||
|
{
|
||||||
|
uint? regionColor = GetRegionColor(i + j);
|
||||||
|
|
||||||
|
if (regionColor is not null)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, regionColor.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable($"{_viewModel.Data[i + j]}##sel{i + j}", regionColor.HasValue))
|
||||||
|
{
|
||||||
|
HandleRegionSelect(i + j);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regionColor is not null)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(200f);
|
||||||
|
if (ImGui.ColorPicker4("NextColor", ref _viewModel.NextColor, ImGuiColorEditFlags.Float))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTable("Регионы", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Номер");
|
||||||
|
ImGui.TableSetupColumn("Старт");
|
||||||
|
ImGui.TableSetupColumn("Длина");
|
||||||
|
ImGui.TableSetupColumn("Значение");
|
||||||
|
ImGui.TableSetupColumn("Действия");
|
||||||
|
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (int k = 0; k < _viewModel.Regions.Count; k++)
|
||||||
|
{
|
||||||
|
var region = _viewModel.Regions[k];
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(k.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(region.Begin.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(region.Length.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(region.Value ?? "unknown");
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.Button($"float##f{k}"))
|
||||||
|
{
|
||||||
|
region.Value = BinaryPrimitives.ReadSingleLittleEndian(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)])
|
||||||
|
.ToString("F2");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button($"int##i{k}"))
|
||||||
|
{
|
||||||
|
region.Value = BinaryPrimitives.ReadInt32LittleEndian(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)])
|
||||||
|
.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button($"ASCII##a{k}"))
|
||||||
|
{
|
||||||
|
region.Value = Encoding.ASCII.GetString(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button($"raw##r{k}"))
|
||||||
|
{
|
||||||
|
region.Value = string.Join(
|
||||||
|
"",
|
||||||
|
_viewModel.Data[region.Begin..(region.Begin + region.Length)]
|
||||||
|
.Select(x => x.ToString("x2"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint? GetRegionColor(int index)
|
||||||
|
{
|
||||||
|
Region? inRegion = _viewModel.Regions.Find(x => x.Begin <= index && x.Begin + x.Length > index);
|
||||||
|
|
||||||
|
return inRegion?.Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRegionSelect(int index)
|
||||||
|
{
|
||||||
|
Region? inRegion = _viewModel.Regions.FirstOrDefault(x => x.Begin <= index && index < x.Begin + x.Length);
|
||||||
|
|
||||||
|
if (inRegion is null)
|
||||||
|
{
|
||||||
|
// not in region
|
||||||
|
Region? prependRegion;
|
||||||
|
Region? appendRegion;
|
||||||
|
|
||||||
|
if ((prependRegion = _viewModel.Regions.Find(x => x.Begin + x.Length == index)) is not null)
|
||||||
|
{
|
||||||
|
if (prependRegion.Color == GetImGuiColor(_viewModel.NextColor))
|
||||||
|
{
|
||||||
|
prependRegion.Length += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((appendRegion = _viewModel.Regions.Find(x => x.Begin - 1 == index)) is not null)
|
||||||
|
{
|
||||||
|
if (appendRegion.Color == GetImGuiColor(_viewModel.NextColor))
|
||||||
|
{
|
||||||
|
appendRegion.Begin--;
|
||||||
|
appendRegion.Length += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = ImGui.ColorConvertFloat4ToU32(_viewModel.NextColor);
|
||||||
|
|
||||||
|
color = unchecked((uint) (0xFF << 24)) | color;
|
||||||
|
_viewModel.Regions.Add(
|
||||||
|
new Region()
|
||||||
|
{
|
||||||
|
Begin = index,
|
||||||
|
Length = 1,
|
||||||
|
Color = color
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inRegion.Length == 1)
|
||||||
|
{
|
||||||
|
_viewModel.Regions.Remove(inRegion);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inRegion.Begin == index)
|
||||||
|
{
|
||||||
|
inRegion.Begin++;
|
||||||
|
inRegion.Length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inRegion.Begin + inRegion.Length - 1 == index)
|
||||||
|
{
|
||||||
|
inRegion.Length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint GetImGuiColor(Vector4 vec)
|
||||||
|
{
|
||||||
|
var color = ImGui.ColorConvertFloat4ToU32(vec);
|
||||||
|
|
||||||
|
color = unchecked((uint) (0xFF << 24)) | color;
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OpenFile()
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("*");
|
||||||
|
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
var path = result.Path;
|
||||||
|
|
||||||
|
var bytes = File.ReadAllBytes(path);
|
||||||
|
_viewModel.HasFile = true;
|
||||||
|
_viewModel.Data = bytes;
|
||||||
|
_viewModel.Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
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 ImGuiNET;
|
||||||
|
using MissionTmaLib;
|
||||||
using MissionTmaLib.Parsing;
|
using MissionTmaLib.Parsing;
|
||||||
using NativeFileDialogSharp;
|
using NativeFileDialogSharp;
|
||||||
using NResLib;
|
using NResLib;
|
||||||
using NResUI.Abstractions;
|
using NResUI.Abstractions;
|
||||||
using NResUI.Models;
|
using NResUI.Models;
|
||||||
|
using ScrLib;
|
||||||
using TexmLib;
|
using TexmLib;
|
||||||
|
using VarsetLib;
|
||||||
|
|
||||||
namespace NResUI.ImGuiUI
|
namespace NResUI.ImGuiUI
|
||||||
{
|
{
|
||||||
public class MainMenuBar : IImGuiPanel
|
public class MainMenuBar(
|
||||||
|
NResExplorerViewModel nResExplorerViewModel,
|
||||||
|
TexmExplorerViewModel texmExplorerViewModel,
|
||||||
|
ScrViewModel scrViewModel,
|
||||||
|
MissionTmaViewModel missionTmaViewModel,
|
||||||
|
VarsetViewModel varsetViewModel,
|
||||||
|
CpDatSchemeViewModel cpDatSchemeViewModel,
|
||||||
|
MessageBoxModalPanel messageBox)
|
||||||
|
: IImGuiPanel
|
||||||
{
|
{
|
||||||
private readonly NResExplorerViewModel _nResExplorerViewModel;
|
|
||||||
private readonly TexmExplorerViewModel _texmExplorerViewModel;
|
|
||||||
private readonly MissionTmaViewModel _missionTmaViewModel;
|
|
||||||
|
|
||||||
private readonly MessageBoxModalPanel _messageBox;
|
|
||||||
public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox, MissionTmaViewModel missionTmaViewModel)
|
|
||||||
{
|
|
||||||
_nResExplorerViewModel = nResExplorerViewModel;
|
|
||||||
_texmExplorerViewModel = texmExplorerViewModel;
|
|
||||||
_messageBox = messageBox;
|
|
||||||
_missionTmaViewModel = missionTmaViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnImGuiRender()
|
public void OnImGuiRender()
|
||||||
{
|
{
|
||||||
if (ImGui.BeginMenuBar())
|
if (ImGui.BeginMenuBar())
|
||||||
@@ -40,7 +39,7 @@ namespace NResUI.ImGuiUI
|
|||||||
|
|
||||||
var parseResult = NResParser.ReadFile(path);
|
var parseResult = NResParser.ReadFile(path);
|
||||||
|
|
||||||
_nResExplorerViewModel.SetParseResult(parseResult, path);
|
nResExplorerViewModel.SetParseResult(parseResult, path);
|
||||||
Console.WriteLine("Read NRES");
|
Console.WriteLine("Read NRES");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,10 +53,10 @@ namespace NResUI.ImGuiUI
|
|||||||
var path = result.Path;
|
var path = result.Path;
|
||||||
|
|
||||||
using var fs = new FileStream(path, FileMode.Open);
|
using var fs = new FileStream(path, FileMode.Open);
|
||||||
|
|
||||||
var parseResult = TexmParser.ReadFromStream(fs, path);
|
var parseResult = TexmParser.ReadFromStream(fs, path);
|
||||||
|
|
||||||
_texmExplorerViewModel.SetParseResult(parseResult, path);
|
texmExplorerViewModel.SetParseResult(parseResult, path);
|
||||||
Console.WriteLine("Read TEXM");
|
Console.WriteLine("Read TEXM");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,11 +72,11 @@ namespace NResUI.ImGuiUI
|
|||||||
using var fs = new FileStream(path, FileMode.Open);
|
using var fs = new FileStream(path, FileMode.Open);
|
||||||
|
|
||||||
fs.Seek(4116, SeekOrigin.Begin);
|
fs.Seek(4116, SeekOrigin.Begin);
|
||||||
|
|
||||||
var parseResult = TexmParser.ReadFromStream(fs, path);
|
var parseResult = TexmParser.ReadFromStream(fs, path);
|
||||||
|
|
||||||
_texmExplorerViewModel.SetParseResult(parseResult, path);
|
texmExplorerViewModel.SetParseResult(parseResult, path);
|
||||||
Console.WriteLine("Read TEXM");
|
Console.WriteLine("Read TFNT TEXM");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,12 +88,57 @@ namespace NResUI.ImGuiUI
|
|||||||
{
|
{
|
||||||
var path = result.Path;
|
var path = result.Path;
|
||||||
var parseResult = MissionTmaParser.ReadFile(path);
|
var parseResult = MissionTmaParser.ReadFile(path);
|
||||||
|
|
||||||
_missionTmaViewModel.SetParseResult(parseResult, path);
|
missionTmaViewModel.SetParseResult(parseResult, path);
|
||||||
|
Console.WriteLine("Read TMA");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_nResExplorerViewModel.HasFile)
|
if (ImGui.MenuItem("Open SCR Scripts File"))
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("scr");
|
||||||
|
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
var path = result.Path;
|
||||||
|
var parseResult = ScrParser.ReadFile(path);
|
||||||
|
|
||||||
|
scrViewModel.SetParseResult(parseResult, path);
|
||||||
|
Console.WriteLine("Read SCR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Open Varset File"))
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("var");
|
||||||
|
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
var path = result.Path;
|
||||||
|
var parseResult = VarsetParser.Parse(path);
|
||||||
|
|
||||||
|
varsetViewModel.Items = parseResult;
|
||||||
|
|
||||||
|
Console.WriteLine("Read VARSET");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Open cp .dat Scheme File"))
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("dat");
|
||||||
|
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
var path = result.Path;
|
||||||
|
var parseResult = CpDatParser.Parse(path);
|
||||||
|
|
||||||
|
cpDatSchemeViewModel.SetParseResult(parseResult, path);
|
||||||
|
|
||||||
|
Console.WriteLine("Read cp .dat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nResExplorerViewModel.HasFile)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Экспортировать NRes"))
|
if (ImGui.MenuItem("Экспортировать NRes"))
|
||||||
{
|
{
|
||||||
@@ -103,10 +147,10 @@ namespace NResUI.ImGuiUI
|
|||||||
if (result.IsOk)
|
if (result.IsOk)
|
||||||
{
|
{
|
||||||
var path = result.Path;
|
var path = result.Path;
|
||||||
|
|
||||||
NResExporter.Export(_nResExplorerViewModel.Archive!, path, _nResExplorerViewModel.Path!);
|
NResExporter.Export(nResExplorerViewModel.Archive!, path, nResExplorerViewModel.Path!);
|
||||||
|
|
||||||
_messageBox.Show("Успешно экспортировано");
|
messageBox.Show("Успешно экспортировано");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,6 +161,5 @@ namespace NResUI.ImGuiUI
|
|||||||
ImGui.EndMenuBar();
|
ImGui.EndMenuBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -18,9 +18,14 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("Mission TMA Explorer"))
|
if (ImGui.Begin("Mission TMA Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("data.tma - это файл миссии. Его можно найти в папке MISSIONS");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
var mission = _viewModel.Mission;
|
var mission = _viewModel.Mission;
|
||||||
if (_viewModel.HasFile && mission is not null)
|
if (_viewModel.HasFile && mission is not null)
|
||||||
{
|
{
|
||||||
|
ImGui.Columns(2);
|
||||||
|
|
||||||
ImGui.Text("Путь к файлу: ");
|
ImGui.Text("Путь к файлу: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(_viewModel.Path);
|
ImGui.Text(_viewModel.Path);
|
||||||
@@ -65,32 +70,44 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
{
|
{
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(arealInfo.Coords[k].X.ToString("F2"));
|
ImGui.Text(
|
||||||
|
arealInfo.Coords[k]
|
||||||
|
.X.ToString("F2")
|
||||||
|
);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(arealInfo.Coords[k].Y.ToString("F2"));
|
ImGui.Text(
|
||||||
|
arealInfo.Coords[k]
|
||||||
|
.Y.ToString("F2")
|
||||||
|
);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(arealInfo.Coords[k].Z.ToString("F2"));
|
ImGui.Text(
|
||||||
|
arealInfo.Coords[k]
|
||||||
|
.Z.ToString("F2")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.TreeNodeEx("Кланы"))
|
if (ImGui.TreeNodeEx("Кланы"))
|
||||||
{
|
{
|
||||||
var (clanFeatureSet, clanCount, clanInfos) = mission.ClansData;
|
var (clanFeatureSet, clanCount, clanInfos) = mission.ClansData;
|
||||||
|
|
||||||
ImGui.Text("Фиче-сет: ");
|
ImGui.Text("Фиче-сет: ");
|
||||||
Utils.ShowHint("Магическое число из файла, на основе которого игра читает разные секции о клане");
|
Utils.ShowHint("Магическое число из файла, на основе которого игра читает разные секции о клане");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanFeatureSet.ToString());
|
ImGui.Text(clanFeatureSet.ToString());
|
||||||
|
|
||||||
ImGui.Text("Количество кланов: ");
|
ImGui.Text("Количество кланов: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanCount.ToString());
|
ImGui.Text(clanCount.ToString());
|
||||||
@@ -113,15 +130,15 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
ImGui.Text(" Y: ");
|
ImGui.Text(" Y: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanInfo.Y.ToString());
|
ImGui.Text(clanInfo.Y.ToString());
|
||||||
|
|
||||||
ImGui.Text("Тип клана: ");
|
ImGui.Text("Тип клана: ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanInfo.ClanType.ToReadableString());
|
ImGui.Text(clanInfo.ClanType.ToReadableString());
|
||||||
|
|
||||||
ImGui.Text("Неизвестная строка 1: ");
|
ImGui.Text("Скрипты поведения (Mission Scripts): ");
|
||||||
Utils.ShowHint("Кажется это путь к файлу поведения (Behavior), но пока не понятно. Обычно пути соответствуют 2 файла.");
|
Utils.ShowHint("Пути к файлам .scr и .fml описывающих настройку объектов и поведение AI");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanInfo.UnkString2);
|
ImGui.Text(clanInfo.ScriptsString);
|
||||||
|
|
||||||
if (clanInfo.UnknownParts.Count > 0)
|
if (clanInfo.UnknownParts.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -162,14 +179,14 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
ImGui.Text("Отсутствует неизвестная часть");
|
ImGui.Text("Отсутствует неизвестная часть");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Text("Путь к файлу .trf: ");
|
ImGui.Text("Дерево исследований: ");
|
||||||
Utils.ShowHint("Не до конца понятно, что означает, вероятно это NRes с деревом исследований");
|
Utils.ShowHint("NRes с деревом исследований");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanInfo.ResearchNResPath);
|
ImGui.Text(clanInfo.ResearchNResPath);
|
||||||
|
|
||||||
ImGui.Text("Неизвестное число 3: ");
|
ImGui.Text("Количество мозгов (Brains))): ");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(clanInfo.UnkInt3.ToString());
|
ImGui.Text(clanInfo.Brains.ToString());
|
||||||
|
|
||||||
ImGui.Text("Матрица союзников");
|
ImGui.Text("Матрица союзников");
|
||||||
Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга");
|
Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга");
|
||||||
@@ -178,20 +195,23 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
ImGui.TableSetupColumn("Клан");
|
ImGui.TableSetupColumn("Клан");
|
||||||
ImGui.TableSetupColumn("Союзник?");
|
ImGui.TableSetupColumn("Союзник?");
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var alliesMapKey in clanInfo.AlliesMap.Keys)
|
foreach (var alliesMapKey in clanInfo.AlliesMap.Keys)
|
||||||
{
|
{
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(alliesMapKey);
|
ImGui.Text(alliesMapKey);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(clanInfo.AlliesMap[alliesMapKey].ToString());
|
ImGui.Text(
|
||||||
|
clanInfo.AlliesMap[alliesMapKey]
|
||||||
|
.ToString()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,8 +224,189 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
|
|
||||||
if (ImGui.TreeNodeEx("Объекты"))
|
if (ImGui.TreeNodeEx("Объекты"))
|
||||||
{
|
{
|
||||||
|
var gameObjectsData = mission.GameObjectsData;
|
||||||
|
|
||||||
|
ImGui.Text("Фиче-сет: ");
|
||||||
|
Utils.ShowHint("Магическое число из файла, на основе которого игра читает разные секции об объекте");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectsData.GameObjectsFeatureSet.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Кол-во объектов: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectsData.GameObjectsCount.ToString());
|
||||||
|
|
||||||
|
for (var i = 0; i < gameObjectsData.GameObjectInfos.Count; i++)
|
||||||
|
{
|
||||||
|
var gameObjectInfo = gameObjectsData.GameObjectInfos[i];
|
||||||
|
if (ImGui.TreeNodeEx($"Объект {i} - {gameObjectInfo.DatString}"))
|
||||||
|
{
|
||||||
|
ImGui.Text("Тип объекта: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.Type.ToReadableString());
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестные флаги: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.UnknownFlags.ToString("X8"));
|
||||||
|
|
||||||
|
ImGui.Text("Путь к файлу .dat: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.DatString);
|
||||||
|
|
||||||
|
ImGui.Text("Индекс владеющего клана: ");
|
||||||
|
Utils.ShowHint("-1 если объект никому не принадлежит");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.OwningClanIndex.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Порядковый номер: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.Order.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Вектор позиции: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text($"{gameObjectInfo.Position.X} : {gameObjectInfo.Position.Y} : {gameObjectInfo.Position.Z}");
|
||||||
|
|
||||||
|
ImGui.Text("Вектор поворота: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text($"{gameObjectInfo.Rotation.X} : {gameObjectInfo.Rotation.Y} : {gameObjectInfo.Rotation.Z}");
|
||||||
|
|
||||||
|
ImGui.Text("Вектор масштаба: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text($"{gameObjectInfo.Scale.X} : {gameObjectInfo.Scale.Y} : {gameObjectInfo.Scale.Z}");
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестная строка 2: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.UnknownString2);
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестное число 4: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.UnknownInt4.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестное число 5: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.UnknownInt5.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестное число 6: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.UnknownInt6.ToString());
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Настройки"))
|
||||||
|
{
|
||||||
|
ImGui.Text("Неиспользуемый заголовок: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.Settings.Unused.ToString());
|
||||||
|
ImGui.Text("Кол-во настроек: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(gameObjectInfo.Settings.SettingsCount.ToString());
|
||||||
|
ImGui.Text("0 - дробное число, 1 - целое число");
|
||||||
|
if (ImGui.BeginTable("Настройки", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Тип");
|
||||||
|
ImGui.TableSetupColumn("Число 1");
|
||||||
|
ImGui.TableSetupColumn("Число 2");
|
||||||
|
ImGui.TableSetupColumn("Число 3");
|
||||||
|
ImGui.TableSetupColumn("Название");
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (var i1 = 0; i1 < gameObjectInfo.Settings.Settings.Count; i1++)
|
||||||
|
{
|
||||||
|
var setting = gameObjectInfo.Settings.Settings[i1];
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(setting.SettingType.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(setting.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.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.TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.Text("Тут хочу сделать карту");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -17,6 +17,9 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("NRes Explorer"))
|
if (ImGui.Begin("NRes Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
if (!_viewModel.HasFile)
|
if (!_viewModel.HasFile)
|
||||||
{
|
{
|
||||||
ImGui.Text("No NRes is opened");
|
ImGui.Text("No NRes is opened");
|
||||||
@@ -46,12 +49,13 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
|
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("Кол-во элементов");
|
||||||
ImGui.TableSetupColumn("Magic1");
|
ImGui.TableSetupColumn("Magic1");
|
||||||
ImGui.TableSetupColumn("Длина файла в байтах");
|
ImGui.TableSetupColumn("Длина файла в байтах");
|
||||||
ImGui.TableSetupColumn("Magic2");
|
ImGui.TableSetupColumn("Размер элемента");
|
||||||
ImGui.TableSetupColumn("Имя файла");
|
ImGui.TableSetupColumn("Имя файла");
|
||||||
ImGui.TableSetupColumn("Magic3");
|
ImGui.TableSetupColumn("Magic3");
|
||||||
ImGui.TableSetupColumn("Magic4");
|
ImGui.TableSetupColumn("Magic4");
|
||||||
@@ -68,6 +72,8 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(_viewModel.Archive.Files[i].FileType);
|
ImGui.Text(_viewModel.Archive.Files[i].FileType);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.Archive.Files[i].ElementCount.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(
|
ImGui.Text(
|
||||||
_viewModel.Archive.Files[i]
|
_viewModel.Archive.Files[i]
|
||||||
.Magic1.ToString()
|
.Magic1.ToString()
|
||||||
@@ -80,7 +86,7 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(
|
ImGui.Text(
|
||||||
_viewModel.Archive.Files[i]
|
_viewModel.Archive.Files[i]
|
||||||
.Magic2.ToString()
|
.ElementSize.ToString()
|
||||||
);
|
);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(_viewModel.Archive.Files[i].FileName);
|
ImGui.Text(_viewModel.Archive.Files[i].FileName);
|
||||||
|
147
NResUI/ImGuiUI/ScrExplorer.cs
Normal file
147
NResUI/ImGuiUI/ScrExplorer.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using NResUI.Abstractions;
|
||||||
|
using NResUI.Models;
|
||||||
|
using ScrLib;
|
||||||
|
|
||||||
|
namespace NResUI.ImGuiUI;
|
||||||
|
|
||||||
|
public class ScrExplorer : IImGuiPanel
|
||||||
|
{
|
||||||
|
private readonly ScrViewModel _viewModel;
|
||||||
|
|
||||||
|
public ScrExplorer(ScrViewModel viewModel)
|
||||||
|
{
|
||||||
|
_viewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnImGuiRender()
|
||||||
|
{
|
||||||
|
if (ImGui.Begin("SCR Explorer"))
|
||||||
|
{
|
||||||
|
ImGui.Text("scr - это файл AI скриптов. Их можно найти в папке MISSIONS/SCRIPTS");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
var scr = _viewModel.Scr;
|
||||||
|
if (_viewModel.HasFile && scr is not null)
|
||||||
|
{
|
||||||
|
ImGui.Text("Магия: ");
|
||||||
|
Utils.ShowHint("тут всегда число 59 (0x3b) - это число известных игре скриптов");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(scr.Magic.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Кол-во секций: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(scr.EntryCount.ToString());
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Секции"))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < scr.Entries.Count; i++)
|
||||||
|
{
|
||||||
|
var entry = scr.Entries[i];
|
||||||
|
if (ImGui.TreeNodeEx($"Секция {i} - \"{entry.Title}\""))
|
||||||
|
{
|
||||||
|
ImGui.Text("Индекс: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(entry.Index.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Кол-во элементов: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(entry.InnerCount.ToString());
|
||||||
|
|
||||||
|
if (ImGui.BeginTable($"Элементы##{i:0000}", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Индекс встроенного скрипта");
|
||||||
|
ImGui.TableSetupColumn("UnkInner2");
|
||||||
|
ImGui.TableSetupColumn("UnkInner3");
|
||||||
|
ImGui.TableSetupColumn("Тип действия");
|
||||||
|
ImGui.TableSetupColumn("UnkInner5");
|
||||||
|
ImGui.TableSetupColumn("Кол-во аргументов");
|
||||||
|
ImGui.TableSetupColumn("Аргументы");
|
||||||
|
ImGui.TableSetupColumn("UnkInner7");
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (int j = 0; j < entry.Inners.Count; j++)
|
||||||
|
{
|
||||||
|
var inner = entry.Inners[j];
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(inner.ScriptIndex.ToString());
|
||||||
|
|
||||||
|
if (inner.ScriptIndex == 2)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("Первый аргумент - номер проблемы");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inner.ScriptIndex == 4)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("Первый аргумент - номер проблемы");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inner.ScriptIndex == 8)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("Установить dCurrentProblem стейт (VARSET:arg0)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inner.ScriptIndex == 20)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("Первый аргумент - номер проблемы");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(inner.UnkInner2.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(inner.UnkInner3.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{(int) inner.Type}: {inner.Type:G}");
|
||||||
|
if (inner.Type == ScrEntryInnerType._0)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("0 обязан иметь аргументы");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inner.Type == ScrEntryInnerType.CheckInternalState)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("Для 5 вообще не нужны данные, игра проверяет внутренний стейт");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inner.Type == ScrEntryInnerType.SetVarsetValue)
|
||||||
|
{
|
||||||
|
Utils.ShowHint("В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(inner.UnkInner5.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(inner.ArgumentsCount.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
foreach (var argument in inner.Arguments)
|
||||||
|
{
|
||||||
|
if (ImGui.Button(argument.ToString()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(inner.UnkInner7.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("SCR не открыт");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,9 @@ public class TexmExplorer : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("TEXM Explorer"))
|
if (ImGui.Begin("TEXM Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("TEXM - это файл текстуры. Их можно найти внутри NRes архивов, например Textures.lib");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
if (!_viewModel.HasFile)
|
if (!_viewModel.HasFile)
|
||||||
{
|
{
|
||||||
ImGui.Text("No TEXM opened");
|
ImGui.Text("No TEXM opened");
|
||||||
|
60
NResUI/ImGuiUI/VarsetExplorerPanel.cs
Normal file
60
NResUI/ImGuiUI/VarsetExplorerPanel.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using NResUI.Abstractions;
|
||||||
|
using NResUI.Models;
|
||||||
|
|
||||||
|
namespace NResUI.ImGuiUI;
|
||||||
|
|
||||||
|
public class VarsetExplorerPanel : IImGuiPanel
|
||||||
|
{
|
||||||
|
private readonly VarsetViewModel _viewModel;
|
||||||
|
|
||||||
|
public VarsetExplorerPanel(VarsetViewModel viewModel)
|
||||||
|
{
|
||||||
|
_viewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnImGuiRender()
|
||||||
|
{
|
||||||
|
if (ImGui.Begin("VARSET Explorer"))
|
||||||
|
{
|
||||||
|
ImGui.Text(".var - это файл динамических настроек. Можно найти в MISSIONS/SCRIPTS/varset.var, а также внутри behpsp.res");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (_viewModel.Items.Count == 0)
|
||||||
|
{
|
||||||
|
ImGui.Text("VARSET не загружен");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGui.BeginTable($"varset", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Индекс");
|
||||||
|
ImGui.TableSetupColumn("Тип");
|
||||||
|
ImGui.TableSetupColumn("Имя");
|
||||||
|
ImGui.TableSetupColumn("Значение");
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (int j = 0; j < _viewModel.Items.Count; j++)
|
||||||
|
{
|
||||||
|
var item = _viewModel.Items[j];
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(j.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(item.Type);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(item.Name);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(item.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
NResUI/Models/BinaryExplorerViewModel.cs
Normal file
27
NResUI/Models/BinaryExplorerViewModel.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace NResUI.Models;
|
||||||
|
|
||||||
|
public class BinaryExplorerViewModel
|
||||||
|
{
|
||||||
|
public bool HasFile { get; set; }
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
public string Path { get; set; } = "";
|
||||||
|
public byte[] Data { get; set; } = [];
|
||||||
|
|
||||||
|
public List<Region> Regions { get; set; } = [];
|
||||||
|
|
||||||
|
public Vector4 NextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Region
|
||||||
|
{
|
||||||
|
public int Begin { get; set; }
|
||||||
|
|
||||||
|
public int Length { get; set; }
|
||||||
|
|
||||||
|
public uint Color { get; set; }
|
||||||
|
|
||||||
|
public string? Value;
|
||||||
|
}
|
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>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -12,16 +9,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
|
<PackageReference Include="NativeFileDialogSharp" />
|
||||||
<PackageReference Include="Silk.NET" Version="2.22.0" />
|
<PackageReference Include="Silk.NET" />
|
||||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CpDatLib\CpDatLib.csproj" />
|
||||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||||
|
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||||
|
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -137,9 +137,9 @@ namespace NResUI
|
|||||||
|
|
||||||
public void SetLod(int @base, int min, int max)
|
public void SetLod(int @base, int min, int max)
|
||||||
{
|
{
|
||||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base);
|
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, in @base);
|
||||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min);
|
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, in min);
|
||||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max);
|
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, in max);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetWrap(TextureCoordinate coord, TextureWrapMode mode)
|
public void SetWrap(TextureCoordinate coord, TextureWrapMode mode)
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextureDecoder", "TextureDecoder\TextureDecoder.csproj", "{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLUnpacker", "NLUnpacker\NLUnpacker.csproj", "{50C83E6C-23ED-4A8E-B948-89686A742CF0}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResUI", "NResUI\NResUI.csproj", "{7456A089-0701-416C-8668-1F740BF4B72C}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResLib", "NResLib\NResLib.csproj", "{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshUnpacker", "MeshUnpacker\MeshUnpacker.csproj", "{F1465FFE-0D66-4A3C-90D7-153A14E226E6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TexmLib", "TexmLib\TexmLib.csproj", "{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionDataUnpacker", "MissionDataUnpacker\MissionDataUnpacker.csproj", "{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{BAF212FE-A0FD-41A2-A1A9-B406FDDFBAF3}"
|
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
README.md = README.md
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
21
ParkanPlayground.slnx
Normal file
21
ParkanPlayground.slnx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/meta/">
|
||||||
|
<File Path="Directory.Build.props" />
|
||||||
|
<File Path="Directory.Packages.props" />
|
||||||
|
<File Path="README.md" />
|
||||||
|
</Folder>
|
||||||
|
<Project Path="Common\Common.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="CpDatLib\CpDatLib.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="MeshUnpacker/MeshUnpacker.csproj" />
|
||||||
|
<Project Path="MissionDataUnpacker/MissionDataUnpacker.csproj" />
|
||||||
|
<Project Path="MissionTmaLib/MissionTmaLib.csproj" />
|
||||||
|
<Project Path="NLUnpacker/NLUnpacker.csproj" />
|
||||||
|
<Project Path="NResLib/NResLib.csproj" />
|
||||||
|
<Project Path="NResUI/NResUI.csproj" />
|
||||||
|
<Project Path="ParkanPlayground/ParkanPlayground.csproj" />
|
||||||
|
<Project Path="ScrLib/ScrLib.csproj" />
|
||||||
|
<Project Path="TexmLib/TexmLib.csproj" />
|
||||||
|
<Project Path="TextureDecoder/TextureDecoder.csproj" />
|
||||||
|
<Project Path="VarsetLib/VarsetLib.csproj" />
|
||||||
|
<Project Path="Visualisator/Visualisator.csproj" />
|
||||||
|
</Solution>
|
77
ParkanPlayground/Msh01.cs
Normal file
77
ParkanPlayground/Msh01.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public static class Msh01
|
||||||
|
{
|
||||||
|
public static Msh01Component ReadComponent(FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var headerFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "01 00 00 00");
|
||||||
|
|
||||||
|
if (headerFileEntry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain header file (01)");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[headerFileEntry.ElementCount * headerFileEntry.ElementSize];
|
||||||
|
mshFs.Seek(headerFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
var dataSpan = data.AsSpan();
|
||||||
|
|
||||||
|
var elements = new List<SubMesh>((int)headerFileEntry.ElementCount);
|
||||||
|
for (var i = 0; i < headerFileEntry.ElementCount; i++)
|
||||||
|
{
|
||||||
|
var element = new SubMesh()
|
||||||
|
{
|
||||||
|
Type1 = dataSpan[i * headerFileEntry.ElementSize + 0],
|
||||||
|
Type2 = dataSpan[i * headerFileEntry.ElementSize + 1],
|
||||||
|
ParentIndex =
|
||||||
|
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 2)),
|
||||||
|
OffsetIntoFile13 =
|
||||||
|
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 4)),
|
||||||
|
IndexInFile08 =
|
||||||
|
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 6))
|
||||||
|
};
|
||||||
|
|
||||||
|
element.Lod[0] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 8));
|
||||||
|
element.Lod[1] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 10));
|
||||||
|
element.Lod[2] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 12));
|
||||||
|
element.Lod[3] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 14));
|
||||||
|
element.Lod[4] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 16));
|
||||||
|
element.Lod[5] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 18));
|
||||||
|
element.Lod[6] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 20));
|
||||||
|
element.Lod[7] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 22));
|
||||||
|
element.Lod[8] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 24));
|
||||||
|
element.Lod[9] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 26));
|
||||||
|
element.Lod[10] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 28));
|
||||||
|
element.Lod[11] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 30));
|
||||||
|
element.Lod[12] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 32));
|
||||||
|
element.Lod[13] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 34));
|
||||||
|
element.Lod[14] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 36));
|
||||||
|
elements.Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Msh01Component()
|
||||||
|
{
|
||||||
|
Elements = elements
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Msh01Component
|
||||||
|
{
|
||||||
|
public List<SubMesh> Elements { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubMesh
|
||||||
|
{
|
||||||
|
public byte Type1 { get; set; }
|
||||||
|
public byte Type2 { get; set; }
|
||||||
|
public short ParentIndex { get; set; }
|
||||||
|
public short OffsetIntoFile13 { get; set; }
|
||||||
|
public short IndexInFile08 { get; set; }
|
||||||
|
public ushort[] Lod { get; set; } = new ushort[15];
|
||||||
|
}
|
||||||
|
}
|
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>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||||
|
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||||
|
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,41 +1,17 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Text;
|
using Common;
|
||||||
|
using MissionTmaLib.Parsing;
|
||||||
using NResLib;
|
using NResLib;
|
||||||
|
using ParkanPlayground;
|
||||||
|
|
||||||
var libFile = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\ui\\ui_back.lib";
|
// var cpDatEntryConverter = new CpDatEntryConverter();
|
||||||
|
// cpDatEntryConverter.Convert();
|
||||||
|
|
||||||
var parseResult = NResParser.ReadFile(libFile);
|
var converter = new MshConverter();
|
||||||
|
|
||||||
if (parseResult.Error != null)
|
converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\133_fr_m_bunker.msh");
|
||||||
{
|
// converter.Convert("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\DATA\\MAPS\\SC_1\\Land.msh");
|
||||||
Console.WriteLine(parseResult.Error);
|
// converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\73_fr_m_brige.msh");
|
||||||
return;
|
// converter.Convert("E:\\ParkanUnpacked\\intsys.rlb\\277_MESH_o_pws_l_01.msh");
|
||||||
}
|
// converter.Convert("E:\\ParkanUnpacked\\static.rlb\\2_MESH_s_stn_0_01.msh");
|
||||||
|
// converter.Convert("E:\\ParkanUnpacked\\bases.rlb\\25_MESH_R_H_02.msh");
|
||||||
// var libFileName = new FileInfo(libFile).Name;
|
|
||||||
//
|
|
||||||
// if (Directory.Exists(libFileName))
|
|
||||||
// {
|
|
||||||
// Directory.Delete(libFileName, true);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var dir = Directory.CreateDirectory(libFileName);
|
|
||||||
//
|
|
||||||
// byte[] copyBuffer = new byte[8192];
|
|
||||||
//
|
|
||||||
// foreach (var element in elements)
|
|
||||||
// {
|
|
||||||
// nResFs.Seek(element.OffsetInFile, SeekOrigin.Begin);
|
|
||||||
// using var destFs = new FileStream(Path.Combine(libFileName, element.FileName), FileMode.CreateNew);
|
|
||||||
//
|
|
||||||
// var totalCopiedBytes = 0;
|
|
||||||
// while (totalCopiedBytes < element.ItemLength)
|
|
||||||
// {
|
|
||||||
// var needReadBytes = Math.Min(element.ItemLength - totalCopiedBytes, copyBuffer.Length);
|
|
||||||
// var readBytes = nResFs.Read(copyBuffer, 0, needReadBytes);
|
|
||||||
//
|
|
||||||
// destFs.Write(copyBuffer, 0, readBytes);
|
|
||||||
//
|
|
||||||
// totalCopiedBytes += readBytes;
|
|
||||||
// }
|
|
||||||
// }
|
|
388
README.md
388
README.md
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" />
|
<img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" />
|
||||||
|
<img width="817" height="376" alt="Image" src="https://github.com/user-attachments/assets/c4959106-9da4-4c78-a2b7-6c94e360a89e" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## Сборка проекта
|
## Сборка проекта
|
||||||
|
|
||||||
Проект написан на C# под `.NET 8`
|
Проект написан на C# под `.NET 9`
|
||||||
|
|
||||||
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
|
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
|
||||||
|
|
||||||
@@ -14,13 +16,13 @@
|
|||||||
|
|
||||||
### Состояние проекта
|
### Состояние проекта
|
||||||
|
|
||||||
- Распаковка всех `NRes` файлов
|
- Поддержка всех `NRes` файлов - звуки, музыка, текстуры, карты и другие файлы. Есть документация.
|
||||||
- Распаковка всех `TEXM` текстур
|
- Поддержка всех `TEXM` текстур. Есть документация.
|
||||||
+ формат 565 работает некорректно
|
- Поддержка файлов миссий `.tma`.
|
||||||
+ не понятно назначение двух магических чисел в заголовке
|
- Поддержка шрифтов TFNT.
|
||||||
- Распаковка данных миссии `.tma`. Пока работает чтение ареалов и кланов.
|
- Поддержка файлов скриптов `.scr`.
|
||||||
- Распаковка файла NL. Есть только декодирование заголовка. Формат кажется не используется игрой, а реверс бинарника игры то ещё занятие.
|
- Поддержка файлов параметров `.var`.
|
||||||
- Распаковка текстуры шрифта формата TFNT. Встроен прямо в UI. По сути шрифт это 4116 байт заголовка и текстура TEXM сразу после.
|
- Поддержка файлов схем объектов `.dat`.
|
||||||
|
|
||||||
|
|
||||||
### Структура проекта
|
### Структура проекта
|
||||||
@@ -34,47 +36,6 @@
|
|||||||
|
|
||||||
Я конечно стараюсь, но ничего не обещаю.
|
Я конечно стараюсь, но ничего не обещаю.
|
||||||
|
|
||||||
#### NResUI
|
|
||||||
|
|
||||||
UI приложение на OpenGL + ImGui.
|
|
||||||
|
|
||||||
Туда постепенно добавляю логику.
|
|
||||||
|
|
||||||
#### NResLib
|
|
||||||
|
|
||||||
Библиотека распаковки формата NRes и всех файлов, которые им запакованы.
|
|
||||||
|
|
||||||
Есть логика импорта и экспорта. Работа не завершена, но уже сейчас можно читать любые архивы такого формата.
|
|
||||||
|
|
||||||
#### TexmLib
|
|
||||||
|
|
||||||
Библиотека распаковки текстур TEXM.
|
|
||||||
|
|
||||||
Есть логика импорта и экспорта, хотя к UI последняя не подключена.
|
|
||||||
|
|
||||||
#### NLUnpacker
|
|
||||||
|
|
||||||
Приложение распаковки NL.
|
|
||||||
|
|
||||||
Работа приостановлена, т.к. кажется игра не использует эти файлы.
|
|
||||||
|
|
||||||
#### MissionDataUnpacker
|
|
||||||
|
|
||||||
Приложение распаковки миссий `.tma`.
|
|
||||||
|
|
||||||
Готово чтение ареалов и кланов. Пока в процессе.
|
|
||||||
|
|
||||||
#### ParkanPlayground
|
|
||||||
|
|
||||||
Пустой проект, использую для локальных тестов.
|
|
||||||
|
|
||||||
#### TextureDecoder
|
|
||||||
|
|
||||||
Приложение для экспорта текстур TEXM.
|
|
||||||
|
|
||||||
Изначально тут игрался с текстурами.
|
|
||||||
|
|
||||||
|
|
||||||
## Для Reverse Engineering-а использую Ghidra
|
## Для Reverse Engineering-а использую Ghidra
|
||||||
|
|
||||||
### Наблюдения
|
### Наблюдения
|
||||||
@@ -86,6 +47,335 @@ UI приложение на OpenGL + ImGui.
|
|||||||
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
|
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
|
||||||
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`.
|
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`.
|
||||||
|
|
||||||
|
## Как быстро найти текст среди всех файлов игры
|
||||||
|
|
||||||
|
```shell
|
||||||
|
grep -rl --include="*" "s_tree_05" .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как быстро найти байты среди всех файлов игры
|
||||||
|
|
||||||
|
```shell
|
||||||
|
grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как работает игра
|
||||||
|
|
||||||
|
Главное меню:
|
||||||
|
|
||||||
|
Игра сканирует хардкод папку `missions` на наличие файлов миссий. (буквально 01, 02, 03 и т.д.)
|
||||||
|
|
||||||
|
Сначала игра читает название миссии из файла `descr` - тут название для меню.
|
||||||
|
|
||||||
|
- Одиночные игры - `missions/single.{index}/descr`
|
||||||
|
- Тренировочные миссии - `missions/tutorial.{index}/descr`
|
||||||
|
- Кампания - `missions/campaign/campaign.{index1}/descr`
|
||||||
|
* Далее используются подпапки - `missions/campaign/campaign.{index1}/mission.{index2}/descr`
|
||||||
|
|
||||||
|
Как только игра не находит файл `descr`, заканчивается итерация по папкам (понял, т.к. пробуется файл 05 - он не существует).
|
||||||
|
|
||||||
|
Загрузка миссии:
|
||||||
|
|
||||||
|
Читается файл `ui/game_resources.cfg`
|
||||||
|
Из этого файла загружаются ресурсы
|
||||||
|
- `library = "ui\\ui.lib"` - загружается файл `ui.lib`
|
||||||
|
- `library = "ui\\font.lib"` - загружается файл `font.lib`
|
||||||
|
- `library = "sounds.lib"` - загружается файл `sounds.lib`
|
||||||
|
- `library = "voices.lib"` - загружается файл `voices.lib`
|
||||||
|
|
||||||
|
Затем игра читает `save/saveslots.cfg` - тут слоты сохранения
|
||||||
|
|
||||||
|
Затем `Comp.ini` - тут системные функции, которые используются для загрузки объектов.
|
||||||
|
|
||||||
|
```
|
||||||
|
IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Host.url` - этого файла нет
|
||||||
|
- `palettes.lib` - тут палитры, но этот NRes пустой
|
||||||
|
- `system.rlb` - не понятно что
|
||||||
|
- `Textures.lib` - тут текстуры
|
||||||
|
- `Material.lib` - тут какие-то материалы - не понятно
|
||||||
|
- `LightMap.lib` - видимо это карты освещения - не понятно
|
||||||
|
- `sys.lib` - не понятно
|
||||||
|
|
||||||
|
|
||||||
|
- `ScanCode.dsc` - текстовый файл с мапом клавиш
|
||||||
|
- `command.dsc` - текстовый файл с мапом клавиш
|
||||||
|
|
||||||
|
Тут видимо идёт конфигурация ввода
|
||||||
|
|
||||||
|
- `table_1.man` - текстовый файл
|
||||||
|
- `table_2.man` - текстовый файл
|
||||||
|
- `hero.man` - текстовый файл
|
||||||
|
- `addition.man` - текстовый файл
|
||||||
|
- Снова `table_1.man`
|
||||||
|
- Снова `table_1.man`
|
||||||
|
- `M1.tbl` - текстовый файл
|
||||||
|
- Снова `table_2.man`
|
||||||
|
- Снова `table_2.man`
|
||||||
|
- `M2.tbl` - текстовый файл
|
||||||
|
- Снова `hero.man`
|
||||||
|
- Снова `hero.man`
|
||||||
|
- `HERO.TBL`
|
||||||
|
- Снова `addition.man`
|
||||||
|
|
||||||
|
|
||||||
|
- `ui/hq.cfg`
|
||||||
|
- Снова `ui/hq.cfg`
|
||||||
|
|
||||||
|
|
||||||
|
Дальше непосредственно читается миссия
|
||||||
|
|
||||||
|
- `mission.cfg` - метадата миссии
|
||||||
|
- `units\\units\\prebld\\scr_pre1.dat` из метаданных `object prebuild` - `cp` файл (грузятся подряд все)
|
||||||
|
- Опять `ui/hq.cfg`
|
||||||
|
- `mistips.mis` - описание для игрока (экран F1)
|
||||||
|
- `scancode.dsc` - хз
|
||||||
|
- `command.dsc` - хз
|
||||||
|
- `ui_hero.man` - хз
|
||||||
|
- `ui_bots.man` - хз
|
||||||
|
- `ui_hq.man` - хз
|
||||||
|
- `ui_other.man` - хз
|
||||||
|
- Цикл чтения курсоров
|
||||||
|
* `ui/cursor.cfg` - тут настройки курсора.
|
||||||
|
* `ui/{name}` - курсор
|
||||||
|
- Снова `mission.cfg` - метадата миссии
|
||||||
|
- `descr` - название
|
||||||
|
- `data/textres.cfg` - конфиг текстов
|
||||||
|
- Снова `mission.cfg` - метадата миссии
|
||||||
|
- Ещё раз `mission.cfg` - метадата миссии
|
||||||
|
- `ui/minimap.lib` - NRes с текстурами миникарты.
|
||||||
|
- `messages.cfg` - Tutorial messages
|
||||||
|
|
||||||
|
УРА НАКОНЕЦ-ТО `data.tma`
|
||||||
|
|
||||||
|
- Из `.tma` берётся LAND строка (я её так назвал)
|
||||||
|
- `DATA\\MAPS\\SC_3\\land1.wea`
|
||||||
|
- `DATA\\MAPS\\SC_3\\land2.wea`
|
||||||
|
- `BuildDat.lst` - Behaviour will use these schemes to Build Fortification
|
||||||
|
- `DATA\\MAPS\\SC_3\\land.map`
|
||||||
|
- `DATA\\MAPS\\SC_3\\land.msh`
|
||||||
|
|
||||||
|
|
||||||
|
- `effects.rlb`
|
||||||
|
|
||||||
|
Цикл по кланам из `.tma`
|
||||||
|
- `MISSIONS\\SCRIPTS\\screampl.scr`
|
||||||
|
- `varset.var`
|
||||||
|
- `MISSIONS\\SCRIPTS\\varset.var`
|
||||||
|
- `MISSIONS\\SCRIPTS\\screampl.fml`
|
||||||
|
|
||||||
|
|
||||||
|
- `missions/single.01/sky.ske`
|
||||||
|
- `missions/single.01/sky.wea`
|
||||||
|
|
||||||
|
Дальше начинаются объекты игры
|
||||||
|
- `"UNITS\\BUILDS\\BUNKER\\mbunk01.dat"` - cp файл
|
||||||
|
|
||||||
|
## Загрузка `cp` файлов
|
||||||
|
|
||||||
|
`cp` файл - схема. Он содержит дерево частей объекта.
|
||||||
|
|
||||||
|
`cp` файл читается в `ArealMap.dll/CreateObjectFromScheme`
|
||||||
|
|
||||||
|
В зависимости от типа объекта внутри схемы (байты 4..8) выбирается функция, с помощью которой загружается схема.
|
||||||
|
|
||||||
|
Функция выбирается на основе файла `Comp.ini`.
|
||||||
|
- Для ClassBuilding (0x80000000) - вызывается функция c классом 3 (по таблице ниже Building).
|
||||||
|
- Для всех остальных - функция с классом 4 (по таблице ниже Agent).
|
||||||
|
|
||||||
|
На основе файла `Comp.ini` и первом вызове внутри функции `World3D.dll/CreateObject` ремаппинг id:
|
||||||
|
|
||||||
|
| Class ID | ClassName | Function |
|
||||||
|
|:----------:|:-------------:|--------------------------------|
|
||||||
|
| 1 | Landscape | `terrain.dll LoadLandscape` |
|
||||||
|
| 2 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 3 | Building | `terrain.dll LoadBuilding` |
|
||||||
|
| 4 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 5 | Camera | `terrain.dll LoadCamera` |
|
||||||
|
| 7 | Atmospehere | `terrain.dll CreateAtmosphere` |
|
||||||
|
| 9 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 10 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 11 | Research | `misload.dll LoadResearch` |
|
||||||
|
| 12 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
|
||||||
|
Будет дополняться по мере реверса.
|
||||||
|
|
||||||
|
Всем этим функциям передаётся `nres_file_name, nres_entry_name, 0, player_id`
|
||||||
|
|
||||||
|
## `fr FORT` файл
|
||||||
|
|
||||||
|
Всегда 0x80 байт
|
||||||
|
Содержит 2 ссылки на файлы:
|
||||||
|
- `.bas`
|
||||||
|
- `.ctl` - вызывается `LoadAgent`
|
||||||
|
|
||||||
|
## `.msh`
|
||||||
|
|
||||||
|
### Описание ниже валидно только для моделей роботов и зданий.
|
||||||
|
##### Land.msh использует другой формат, хотя 03 файл это всё ещё точки.
|
||||||
|
|
||||||
|
Загружается в `AniMesh.dll/LoadAniMesh`
|
||||||
|
|
||||||
|
- Тип 01 - заголовок. Он хранит список деталей (submesh) в разных LOD
|
||||||
|
```
|
||||||
|
нулевому элементу добавляется флаг 0x1000000
|
||||||
|
Содержит 2 ссылки на файлы анимаций (короткие - файл 13, длинные - файл 08)
|
||||||
|
Если интерполируется анимация -0.5s короче чем magic1 у файла 13
|
||||||
|
И у файла есть OffsetIntoFile13
|
||||||
|
И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда)
|
||||||
|
Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт)
|
||||||
|
```
|
||||||
|
- Тип 02 - описание одного LOD Submesh
|
||||||
|
```
|
||||||
|
Вначале идёт заголовок 0x8C (140) байт
|
||||||
|
В заголовке:
|
||||||
|
8 Vector3 (x,y,z) - bounding box
|
||||||
|
1 Vector4 - center
|
||||||
|
1 Vector3 - bottom
|
||||||
|
1 Vector3 - top
|
||||||
|
1 float - xy_radius
|
||||||
|
Далее инфа про куски меша
|
||||||
|
```
|
||||||
|
- Тип 03 - это вершины (vertex)
|
||||||
|
- Тип 06 - индексы треугольников в файле 03
|
||||||
|
- Тип 04 - скорее всего какие-то цвета RGBA или типа того
|
||||||
|
- Тип 08 - меш-анимации (см файл 01)
|
||||||
|
```
|
||||||
|
Индексируется по IndexInFile08 из файла 01 либо по файлу 13 через OffsetIntoFile13
|
||||||
|
Структура:
|
||||||
|
Vector3 position;
|
||||||
|
float time; // содержит только целые секунды
|
||||||
|
short rotation_x; // делится на 32767
|
||||||
|
short rotation_y; // делится на 32767
|
||||||
|
short rotation_z; // делится на 32767
|
||||||
|
short rotation_w; // делится на 32767
|
||||||
|
---
|
||||||
|
Игра интерполирует анимацию между текущим стейтом и следующим по time.
|
||||||
|
Если время интерполяции совпадает с исходным time, жёстко берётся первый стейт из 0x13.
|
||||||
|
Если время интерполяции совпадает с конечным time, жёстко берётся второй стейт из 0x13.
|
||||||
|
Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time)
|
||||||
|
```
|
||||||
|
- Тип 12 - microtexture mapping
|
||||||
|
- Тип 13 - короткие меш-анимации (почему я это не дописал?)
|
||||||
|
```
|
||||||
|
Буквально (hex)
|
||||||
|
00 01 01 02 ...
|
||||||
|
```
|
||||||
|
- Тип 0A - ссылка на части меша, не упакованные в текущий меш (например у бункера 4 и 5 части хранятся в parts.rlb)
|
||||||
|
```
|
||||||
|
Не имеет фиксированной длины. Хранит строки в следующем формате.
|
||||||
|
Игра обращается по индексу, пропуская суммарную длину и пропуская 4 байта на каждую строку (длина).
|
||||||
|
т.е. буквально файл выглядит так
|
||||||
|
00 00 00 00 - пустая строка
|
||||||
|
03 00 00 00 - длина строки 1
|
||||||
|
73 74 72 00 - строка "str" + null terminator
|
||||||
|
.. и повторяется до конца файла
|
||||||
|
Кол-во элементов из файла 01 должно быть равно кол-ву строк в этом файле, хотя игра это не проверяет.
|
||||||
|
Если у элемента эта строка равна "central", ему выставляется флаг (flag |= 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `.wea`
|
||||||
|
|
||||||
|
Загружается в `World3D.dll/LoadMatManager`
|
||||||
|
|
||||||
|
По сути это текстовый файл состоящий из 2 частей:
|
||||||
|
- Материалы
|
||||||
|
```
|
||||||
|
{count}
|
||||||
|
{id} {name}
|
||||||
|
```
|
||||||
|
- Карты освещения
|
||||||
|
```
|
||||||
|
LIGHTMAPS
|
||||||
|
{count}
|
||||||
|
{id} {name}
|
||||||
|
```
|
||||||
|
|
||||||
|
Может как-то анимироваться. Как - пока не понятно.
|
||||||
|
|
||||||
|
|
||||||
|
# Внутренняя система ID
|
||||||
|
|
||||||
|
- `1` -
|
||||||
|
- `4` - IShader
|
||||||
|
- `5` - ITerrain
|
||||||
|
- `6` - IGameObject (0x138)
|
||||||
|
- `7` - 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).
|
Вы можете связаться со мной в [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 rawPixel = span.Slice(i, 2);
|
||||||
|
|
||||||
var g = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
// swap endianess
|
||||||
var b = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||||
var r = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
|
||||||
|
|
||||||
result[i / 2 * 4 + 0] = r;
|
var r = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
||||||
result[i / 2 * 4 + 1] = g;
|
var g = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
||||||
result[i / 2 * 4 + 2] = b;
|
var b = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
||||||
result[i / 2 * 4 + 3] = r;
|
|
||||||
|
result[i / 2 * 4 + 0] = (byte)(0xff - r);
|
||||||
|
result[i / 2 * 4 + 1] = (byte)(0xff - g);
|
||||||
|
result[i / 2 * 4 + 2] = (byte)(0xff - b);
|
||||||
|
result[i / 2 * 4 + 3] = 0xff;
|
||||||
|
|
||||||
|
// swap endianess back
|
||||||
|
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -202,6 +208,9 @@ public class TexmFile
|
|||||||
{
|
{
|
||||||
var rawPixel = span.Slice(i, 2);
|
var rawPixel = span.Slice(i, 2);
|
||||||
|
|
||||||
|
// swap endianess
|
||||||
|
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||||
|
|
||||||
var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17);
|
var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17);
|
||||||
var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17);
|
var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17);
|
||||||
var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17);
|
var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17);
|
||||||
@@ -211,6 +220,9 @@ public class TexmFile
|
|||||||
result[i / 2 * 4 + 1] = g;
|
result[i / 2 * 4 + 1] = g;
|
||||||
result[i / 2 * 4 + 2] = b;
|
result[i / 2 * 4 + 2] = b;
|
||||||
result[i / 2 * 4 + 3] = a;
|
result[i / 2 * 4 + 3] = a;
|
||||||
|
|
||||||
|
// swap endianess back
|
||||||
|
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -247,16 +259,21 @@ public class TexmFile
|
|||||||
for (var i = 0; i < span.Length; i += 4)
|
for (var i = 0; i < span.Length; i += 4)
|
||||||
{
|
{
|
||||||
var rawPixel = span.Slice(i, 4);
|
var rawPixel = span.Slice(i, 4);
|
||||||
|
// swap endianess back
|
||||||
|
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
|
||||||
|
|
||||||
var b = rawPixel[0];
|
var r = rawPixel[0];
|
||||||
var g = rawPixel[1];
|
var g = rawPixel[1];
|
||||||
var r = rawPixel[2];
|
var b = rawPixel[2];
|
||||||
var a = rawPixel[3];
|
var a = rawPixel[3];
|
||||||
|
|
||||||
result[i + 0] = r;
|
result[i + 0] = r;
|
||||||
result[i + 1] = g;
|
result[i + 1] = g;
|
||||||
result[i + 2] = b;
|
result[i + 2] = b;
|
||||||
result[i + 3] = a;
|
result[i + 3] = a;
|
||||||
|
|
||||||
|
// swap endianess back
|
||||||
|
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@@ -1,13 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
<PackageReference Include="SixLabors.ImageSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -2,17 +2,10 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
<PackageReference Include="SixLabors.ImageSharp" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
3
VarsetLib/VarsetItem.cs
Normal file
3
VarsetLib/VarsetItem.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace VarsetLib;
|
||||||
|
|
||||||
|
public record VarsetItem(string Type, string Name, string Value);
|
3
VarsetLib/VarsetLib.csproj
Normal file
3
VarsetLib/VarsetLib.csproj
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
</Project>
|
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