diff --git a/MissionDataUnpacker/MissionDataUnpacker.csproj b/MissionDataUnpacker/MissionDataUnpacker.csproj
new file mode 100644
index 0000000..2f4fc77
--- /dev/null
+++ b/MissionDataUnpacker/MissionDataUnpacker.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/MissionDataUnpacker/Program.cs b/MissionDataUnpacker/Program.cs
new file mode 100644
index 0000000..4c09068
--- /dev/null
+++ b/MissionDataUnpacker/Program.cs
@@ -0,0 +1,219 @@
+using System.Buffers.Binary;
+using System.Text;
+
+// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Autodemo.00\\data.tma";
+// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Tutorial.01\\data.tma";
+var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\CAMPAIGN\\CAMPAIGN.01\\Mission.03\\data.tma";
+// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Single.01\\data.tma";
+var fs = new FileStream(missionFilePath, FileMode.Open);
+
+var arealData = LoadAreals(fs);
+
+var clans = LoadClans(fs);
+
+_ = 5;
+// LoadGameObjects(fs);
+
+ArealsFileData LoadAreals(FileStream fileStream)
+{
+ var unusedHeader = fileStream.ReadInt32LittleEndian();
+ var arealCount = fileStream.ReadInt32LittleEndian();
+
+ // В демо миссии нет ареалов, ровно как и в первой миссии кампании
+ // Span arealBuffer = stackalloc byte[12];
+
+ List infos = [];
+ for (var i = 0; i < arealCount; i++)
+ {
+ // игра читает 4 байта - видимо количество
+ var unknown4Bytes = fileStream.ReadInt32LittleEndian();
+
+ var count = fileStream.ReadInt32LittleEndian();
+
+ List vectors = [];
+ if (0 < count)
+ {
+ for (var i1 = 0; i1 < count; i1++)
+ {
+ // потом читает 12 байт разом (тут видимо какой-то вектор)
+ var unknownFloat1 = fileStream.ReadFloatLittleEndian();
+ var unknownFloat2 = fileStream.ReadFloatLittleEndian();
+ var unknownFloat3 = fileStream.ReadFloatLittleEndian();
+
+ vectors.Add(new Vector3(unknownFloat1, unknownFloat2, unknownFloat3));
+ }
+ }
+
+ infos.Add(new ArealInfo(unknown4Bytes, count, vectors));
+ }
+
+ return new ArealsFileData(unusedHeader, arealCount, infos);
+}
+
+ClansFileData? LoadClans(FileStream fileStream)
+{
+ var clanFeatureSet = fileStream.ReadInt32LittleEndian();
+
+ if (clanFeatureSet is <= 0 or >= 7) return null;
+
+ var treeInfoCount = fileStream.ReadInt32LittleEndian();
+
+ List infos = [];
+ for (var i = 0; i < treeInfoCount; i++)
+ {
+ var clanTreeInfo = new ClanTreeInfo();
+
+ clanTreeInfo.ClanName = fileStream.ReadLengthPrefixedString();
+ clanTreeInfo.UnkInt1 = fileStream.ReadInt32LittleEndian();
+ clanTreeInfo.X = fileStream.ReadFloatLittleEndian();
+ clanTreeInfo.Y = fileStream.ReadFloatLittleEndian();
+ clanTreeInfo.ClanType = fileStream.ReadInt32LittleEndian();
+
+ if (1 < clanFeatureSet)
+ {
+ // MISSIONS\SCRIPTS\default
+ // MISSIONS\SCRIPTS\tut1_pl
+ // MISSIONS\SCRIPTS\tut1_en
+ clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString();
+ }
+
+ if (2 < clanFeatureSet)
+ {
+ clanTreeInfo.UnknownClanPartCount = fileStream.ReadInt32LittleEndian();
+
+ // тут игра читает число, затем 12 байт и ещё 2 числа
+
+ List unknownClanTreeInfoParts = [];
+ for (var i1 = 0; i1 < clanTreeInfo.UnknownClanPartCount; i1++)
+ {
+ unknownClanTreeInfoParts.Add(
+ new UnknownClanTreeInfoPart(
+ fileStream.ReadInt32LittleEndian(),
+ new Vector3(
+ fileStream.ReadFloatLittleEndian(),
+ fileStream.ReadFloatLittleEndian(),
+ fileStream.ReadFloatLittleEndian()
+ ),
+ fileStream.ReadFloatLittleEndian(),
+ fileStream.ReadFloatLittleEndian()
+ )
+ );
+ }
+
+ clanTreeInfo.UnknownParts = unknownClanTreeInfoParts;
+ }
+
+ if (3 < clanFeatureSet)
+ {
+ // MISSIONS\SCRIPTS\auto.trf
+ // MISSIONS\SCRIPTS\data.trf
+ // указатель на NRes файл с данными
+ // может быть пустым, например у Ntrl в туториале
+ clanTreeInfo.ResearchNResPath = fileStream.ReadLengthPrefixedString();
+ }
+
+ if (4 < clanFeatureSet)
+ {
+ clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian();
+ }
+
+ if (5 < clanFeatureSet)
+ {
+ clanTreeInfo.AlliesMapCount = fileStream.ReadInt32LittleEndian();
+
+ // тут какая-то мапа
+ // в демо миссии тут
+ // player -> 1
+ // player2 -> 0
+
+ // в туториале
+ // Plr -> 1
+ // Trgt -> 1
+ // Enm -> 0
+ // Ntrl -> 1
+ Dictionary map = [];
+ for (var i1 = 0; i1 < clanTreeInfo.AlliesMapCount; i1++)
+ {
+ var keyIdString = fileStream.ReadLengthPrefixedString();
+ // это число всегда либо 0 либо 1
+ var unkNumber = fileStream.ReadInt32LittleEndian();
+
+ map[keyIdString] = unkNumber;
+ }
+
+ clanTreeInfo.AlliesMap = map;
+ }
+
+ infos.Add(clanTreeInfo);
+ }
+
+ var clanInfo = new ClansFileData(clanFeatureSet, treeInfoCount, infos);
+
+ return clanInfo;
+}
+
+public static class Extensions
+{
+ public static int ReadInt32LittleEndian(this FileStream fs)
+ {
+ Span buf = stackalloc byte[4];
+ fs.ReadExactly(buf);
+
+ return BinaryPrimitives.ReadInt32LittleEndian(buf);
+ }
+
+ public static float ReadFloatLittleEndian(this FileStream fs)
+ {
+ Span buf = stackalloc byte[4];
+ fs.ReadExactly(buf);
+
+ return BinaryPrimitives.ReadSingleLittleEndian(buf);
+ }
+
+ public static string ReadLengthPrefixedString(this FileStream fs)
+ {
+ var len = fs.ReadInt32LittleEndian();
+
+ var buffer = new byte[len];
+
+ fs.ReadExactly(buffer, 0, len);
+
+ return Encoding.ASCII.GetString(buffer, 0, len);
+ }
+}
+
+public record ArealsFileData(int UnusedHeader, int ArealCount, List ArealInfos);
+
+public record ArealInfo(int Index, int CoordsCount, List Coords);
+
+public record Vector3(float X, float Y, float Z);
+
+// ----
+
+public record ClansFileData(int ClanFeatureSet, int TreeCount, List TreeInfos);
+
+public class ClanTreeInfo
+{
+ public string ClanName { get; set; }
+ public int UnkInt1 { get; set; }
+ public float X { get; set; }
+ public float Y { get; set; }
+
+ ///
+ /// 1 - игрок, 2 AI, 3 - нейтральный
+ ///
+ public int ClanType { get; set; }
+ public string UnkString2 { get; set; }
+ public int UnknownClanPartCount { get; set; }
+ public List UnknownParts { get; set; }
+ public string ResearchNResPath { get; set; }
+ public int UnkInt3 { get; set; }
+ public int AlliesMapCount { get; set; }
+
+ ///
+ /// мапа союзников (ключ - имя клана, значение - число, всегда либо 0 либо 1)
+ ///
+ public Dictionary AlliesMap { get; set; }
+}
+
+public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
\ No newline at end of file
diff --git a/ParkanPlayground.sln b/ParkanPlayground.sln
index 4f762b2..a551a8a 100644
--- a/ParkanPlayground.sln
+++ b/ParkanPlayground.sln
@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshUnpacker", "MeshUnpacke
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -48,5 +50,9 @@ Global
{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
EndGlobalSection
EndGlobal