diff --git a/Common/Common.csproj b/Common/Common.csproj
new file mode 100644
index 0000000..c632161
--- /dev/null
+++ b/Common/Common.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/MissionTmaLib/Parsing/Extensions.cs b/Common/Extensions.cs
similarity index 59%
rename from MissionTmaLib/Parsing/Extensions.cs
rename to Common/Extensions.cs
index ea009b9..4ab4f00 100644
--- a/MissionTmaLib/Parsing/Extensions.cs
+++ b/Common/Extensions.cs
@@ -1,7 +1,7 @@
using System.Buffers.Binary;
using System.Text;
-namespace MissionTmaLib.Parsing;
+namespace Common;
public static class Extensions
{
@@ -13,6 +13,14 @@ public static class Extensions
return BinaryPrimitives.ReadInt32LittleEndian(buf);
}
+ public static uint ReadUInt32LittleEndian(this FileStream fs)
+ {
+ Span buf = stackalloc byte[4];
+ fs.ReadExactly(buf);
+
+ return BinaryPrimitives.ReadUInt32LittleEndian(buf);
+ }
+
public static float ReadFloatLittleEndian(this FileStream fs)
{
Span buf = stackalloc byte[4];
@@ -21,6 +29,24 @@ public static class Extensions
return BinaryPrimitives.ReadSingleLittleEndian(buf);
}
+ public static string ReadNullTerminatedString(this FileStream fs)
+ {
+ var sb = new StringBuilder();
+
+ while (true)
+ {
+ var b = fs.ReadByte();
+ if (b == 0)
+ {
+ break;
+ }
+
+ sb.Append((char)b);
+ }
+
+ return sb.ToString();
+ }
+
public static string ReadLengthPrefixedString(this FileStream fs)
{
var len = fs.ReadInt32LittleEndian();
diff --git a/Common/IndexedEdge.cs b/Common/IndexedEdge.cs
new file mode 100644
index 0000000..b4fef7f
--- /dev/null
+++ b/Common/IndexedEdge.cs
@@ -0,0 +1,3 @@
+namespace Common;
+
+public record IndexedEdge(ushort Index1, ushort Index2);
\ No newline at end of file
diff --git a/Common/Vector3.cs b/Common/Vector3.cs
new file mode 100644
index 0000000..1f58696
--- /dev/null
+++ b/Common/Vector3.cs
@@ -0,0 +1,3 @@
+namespace Common;
+
+public record Vector3(float X, float Y, float Z);
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index e54c688..c8e53d5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -14,6 +14,6 @@
false
- $(NoWarn);NU1507
+ $(NoWarn);NU1507;CS1591
diff --git a/MissionTmaLib/ArealInfo.cs b/MissionTmaLib/ArealInfo.cs
index 31a751c..12f3169 100644
--- a/MissionTmaLib/ArealInfo.cs
+++ b/MissionTmaLib/ArealInfo.cs
@@ -1,3 +1,5 @@
-namespace MissionTmaLib;
+using Common;
+
+namespace MissionTmaLib;
public record ArealInfo(int Index, int CoordsCount, List Coords);
\ No newline at end of file
diff --git a/MissionTmaLib/GameObjectInfo.cs b/MissionTmaLib/GameObjectInfo.cs
index 35f1161..c124f5f 100644
--- a/MissionTmaLib/GameObjectInfo.cs
+++ b/MissionTmaLib/GameObjectInfo.cs
@@ -1,4 +1,6 @@
-namespace MissionTmaLib;
+using Common;
+
+namespace MissionTmaLib;
public class GameObjectInfo
{
diff --git a/MissionTmaLib/LodeInfo.cs b/MissionTmaLib/LodeInfo.cs
index b308f64..4630239 100644
--- a/MissionTmaLib/LodeInfo.cs
+++ b/MissionTmaLib/LodeInfo.cs
@@ -1,3 +1,5 @@
-namespace MissionTmaLib;
+using Common;
+
+namespace MissionTmaLib;
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);
\ No newline at end of file
diff --git a/MissionTmaLib/MissionTmaLib.csproj b/MissionTmaLib/MissionTmaLib.csproj
index c632161..38858c5 100644
--- a/MissionTmaLib/MissionTmaLib.csproj
+++ b/MissionTmaLib/MissionTmaLib.csproj
@@ -1,3 +1,5 @@
-
+
+
+
diff --git a/MissionTmaLib/Parsing/MissionTmaParser.cs b/MissionTmaLib/Parsing/MissionTmaParser.cs
index e324aba..3463df8 100644
--- a/MissionTmaLib/Parsing/MissionTmaParser.cs
+++ b/MissionTmaLib/Parsing/MissionTmaParser.cs
@@ -1,10 +1,12 @@
-namespace MissionTmaLib.Parsing;
+using Common;
+
+namespace MissionTmaLib.Parsing;
public class MissionTmaParser
{
public static MissionTmaParseResult ReadFile(string filePath)
{
- var fs = new FileStream(filePath, FileMode.Open);
+ using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
var arealData = LoadAreals(fs);
diff --git a/MissionTmaLib/UnknownClanTreeInfoPart.cs b/MissionTmaLib/UnknownClanTreeInfoPart.cs
index 8267951..47572c5 100644
--- a/MissionTmaLib/UnknownClanTreeInfoPart.cs
+++ b/MissionTmaLib/UnknownClanTreeInfoPart.cs
@@ -1,3 +1,5 @@
-namespace MissionTmaLib;
+using Common;
+
+namespace MissionTmaLib;
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
\ No newline at end of file
diff --git a/MissionTmaLib/Vector3.cs b/MissionTmaLib/Vector3.cs
deleted file mode 100644
index 9e3c654..0000000
--- a/MissionTmaLib/Vector3.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-namespace MissionTmaLib;
-
-public record Vector3(float X, float Y, float Z);
\ No newline at end of file
diff --git a/NResLib/NResParser.cs b/NResLib/NResParser.cs
index 7c04557..1da8854 100644
--- a/NResLib/NResParser.cs
+++ b/NResLib/NResParser.cs
@@ -7,7 +7,7 @@ public static class NResParser
{
public static NResParseResult ReadFile(string path)
{
- using FileStream nResFs = new FileStream(path, FileMode.Open);
+ using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (nResFs.Length < 16)
{
diff --git a/NResUI/OpenGlTexture.cs b/NResUI/OpenGlTexture.cs
index 26fc51c..0983599 100644
--- a/NResUI/OpenGlTexture.cs
+++ b/NResUI/OpenGlTexture.cs
@@ -137,9 +137,9 @@ namespace NResUI
public void SetLod(int @base, int min, int max)
{
- _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base);
- _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min);
- _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max);
+ _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, in @base);
+ _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, in min);
+ _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, in max);
}
public void SetWrap(TextureCoordinate coord, TextureWrapMode mode)
diff --git a/ParkanPlayground.slnx b/ParkanPlayground.slnx
index 15f65a3..272885f 100644
--- a/ParkanPlayground.slnx
+++ b/ParkanPlayground.slnx
@@ -4,6 +4,7 @@
+
@@ -16,4 +17,4 @@
-
+
\ No newline at end of file
diff --git a/ParkanPlayground/CpDatEntryConverter.cs b/ParkanPlayground/CpDatEntryConverter.cs
new file mode 100644
index 0000000..5981805
--- /dev/null
+++ b/ParkanPlayground/CpDatEntryConverter.cs
@@ -0,0 +1,161 @@
+using Common;
+using MissionTmaLib.Parsing;
+using NResLib;
+
+namespace ParkanPlayground;
+
+///
+/// Игра называет этот объект "схемой"
+///
+///
+/// В игре файл .dat читается в ArealMap.dll/CreateObjectFromScheme
+///
+///
+///
+/// struct Scheme
+/// {
+/// char[32] str1; // имя архива
+/// char[32] str2; // имя объекта в архиве
+/// undefined4 magic1;
+/// undefined4 magic2;
+/// char[32] str3; // описание объекта
+/// undefined4 magic3;
+/// }
+///
+///
+public class CpDatEntryConverter
+{
+ const string gameRoot = "C:\\Program Files (x86)\\Nikita\\Iron Strategy";
+ const string missionTmaPath = $"{gameRoot}\\MISSIONS\\Single.01\\data.tma";
+ const string staticRlbPath = $"{gameRoot}\\static.rlb";
+ const string objectsRlbPath = $"{gameRoot}\\objects.rlb";
+
+ // Схема такая:
+ // Файл обязан начинаться с 0xf1 0xf0 ("cp\0\0") - типа заголовок
+ // Далее 4 байта - тип объекта, который содержится в схеме (их я выдернул из .var файла)
+ // Далее 0x6c (108) байт - root объект
+
+ public void Convert()
+ {
+ var tma = MissionTmaParser.ReadFile(missionTmaPath);
+ var staticRlbResult = NResParser.ReadFile(staticRlbPath);
+ var objectsRlbResult = NResParser.ReadFile(objectsRlbPath);
+
+ var mission = tma.Mission!;
+ var sRlb = staticRlbResult.Archive!;
+ var oRlb = objectsRlbResult.Archive!;
+
+ Span f0f1 = stackalloc byte[4];
+ foreach (var gameObject in mission.GameObjectsData.GameObjectInfos)
+ {
+ var gameObjectDatPath = gameObject.DatString;
+
+ if (gameObjectDatPath.Contains('\\'))
+ {
+ // если это путь, то надо искать его в папке
+ string datFullPath = $"{gameRoot}\\{gameObjectDatPath}";
+
+ using FileStream fs = new FileStream(datFullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ fs.ReadExactly(f0f1);
+
+ if (f0f1[0] != 0xf1 || f0f1[1] != 0xf0)
+ {
+ _ = 5;
+ }
+
+ var fileFlags = (CpEntryType)fs.ReadInt32LittleEndian();
+
+ var entryLength = 0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов
+ if ((fs.Length - 8) % entryLength != 0)
+ {
+ _ = 5;
+ }
+
+ DatEntry entry = ReadEntryRecursive(fs);
+
+ // var objects = entries.Select(x => oRlb.Files.FirstOrDefault(y => y.FileName == x.ArchiveEntryName))
+ // .ToList();
+
+ _ = 5;
+ }
+ else
+ {
+ // это статический объект, который будет в objects.rlb
+ var sEntry = oRlb.Files.FirstOrDefault(x => x.FileName == gameObjectDatPath);
+
+ _ = 5;
+ }
+ }
+ }
+
+ private DatEntry ReadEntryRecursive(FileStream fs)
+ {
+ var str1 = fs.ReadNullTerminatedString();
+
+ fs.Seek(32 - str1.Length - 1, SeekOrigin.Current);
+
+ var str2 = fs.ReadNullTerminatedString();
+
+ fs.Seek(32 - str2.Length - 1, SeekOrigin.Current);
+ var magic1 = fs.ReadInt32LittleEndian();
+ var magic2 = fs.ReadInt32LittleEndian();
+
+ var descriptionString = fs.ReadNullTerminatedString();
+
+ fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current);
+ var magic3 = fs.ReadInt32LittleEndian();
+
+ // игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов
+ var childCount = fs.ReadInt32LittleEndian();
+
+ List children = new List();
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = ReadEntryRecursive(fs);
+ children.Add(child);
+ }
+
+ return new DatEntry(str1, str2, magic1, magic2, descriptionString, magic3, childCount, Children: children);
+ }
+
+ public record DatEntry(
+ string ArchiveFile,
+ string ArchiveEntryName,
+ int Magic1,
+ int Magic2,
+ string Description,
+ int Magic3,
+ int ChildCount, // игра не хранит это число в объекте, но оно есть в файле
+ List Children
+ );
+
+ enum CpEntryType : 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,
+ }
+}
\ No newline at end of file
diff --git a/ParkanPlayground/MshConverter.cs b/ParkanPlayground/MshConverter.cs
new file mode 100644
index 0000000..150112c
--- /dev/null
+++ b/ParkanPlayground/MshConverter.cs
@@ -0,0 +1,90 @@
+using System.Buffers.Binary;
+using Common;
+using NResLib;
+
+namespace ParkanPlayground;
+
+public class MshConverter
+{
+ public void Convert(string mshPath)
+ {
+ var mshNresResult = NResParser.ReadFile(mshPath);
+
+ var mshNres = mshNresResult.Archive!;
+
+ 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");
+ }
+
+ using var mshFs = new FileStream(mshPath, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ var vertices = ReadVertices(verticesFileEntry, mshFs);
+
+ var edgesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "06 00 00 00");
+
+ if (edgesFileEntry is null)
+ {
+ throw new Exception("Archive doesn't contain edges file (06)");
+ }
+
+ var edgesFile = new byte[edgesFileEntry.ElementCount * edgesFileEntry.ElementSize];
+ mshFs.Seek(edgesFileEntry.OffsetInFile, SeekOrigin.Begin);
+ mshFs.ReadExactly(edgesFile, 0, edgesFile.Length);
+
+ var edges = new List((int)edgesFileEntry.ElementCount / 2);
+
+ for (int i = 0; i < edgesFileEntry.ElementCount / 2; i++)
+ {
+ var index1 = BinaryPrimitives.ReadUInt16LittleEndian(edgesFile.AsSpan().Slice(i * 2));
+ var index2 = BinaryPrimitives.ReadUInt16LittleEndian(edgesFile.AsSpan().Slice(i * 2 + 2));
+ edges.Add(new IndexedEdge(index1, index2));
+ }
+
+ Export($"{Path.GetFileNameWithoutExtension(mshPath)}.obj", vertices, edges);
+
+ }
+
+ private static List ReadVertices(ListMetadataItem verticesFileEntry, FileStream mshFs)
+ {
+ 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;
+ }
+
+ void Export(string filePath, List vertices, List 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}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ParkanPlayground/ParkanPlayground.csproj b/ParkanPlayground/ParkanPlayground.csproj
index ee8a5ae..b749716 100644
--- a/ParkanPlayground/ParkanPlayground.csproj
+++ b/ParkanPlayground/ParkanPlayground.csproj
@@ -5,6 +5,7 @@
+
diff --git a/ParkanPlayground/Program.cs b/ParkanPlayground/Program.cs
index 719f55f..d8999eb 100644
--- a/ParkanPlayground/Program.cs
+++ b/ParkanPlayground/Program.cs
@@ -1,115 +1,13 @@
using System.Buffers.Binary;
-using System.Numerics;
-using System.Text.Json;
-using ScrLib;
-using VarsetLib;
+using Common;
+using MissionTmaLib.Parsing;
+using NResLib;
+using ParkanPlayground;
+var cpDatEntryConverter = new CpDatEntryConverter();
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\default.scr";
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scr_pl_1.scr";
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scream.scr";
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scream1.scr";
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS";
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\varset.var";
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\preload.lda";
-//
-// var fs = new FileStream(path, FileMode.Open);
-//
-// var count = fs.ReadInt32LittleEndian();
-//
-// Span data = stackalloc byte[0x124];
-//
-// for (var i = 0; i < count; i++)
-// {
-// fs.ReadExactly(data);
-// }
-//
-// Console.WriteLine(
-// fs.Position == fs.Length
-// );
+cpDatEntryConverter.Convert();
-// var items = VarsetParser.Parse(path);
+var converter = new MshConverter();
-// Console.WriteLine(items.Count);
-
-// Span flt = stackalloc byte[4];
-// flt[0] = 0x7f;
-// flt[1] = 0x7f;
-// flt[2] = 0xff;
-// flt[3] = 0xff;
-// var f = BinaryPrimitives.ReadSingleBigEndian(flt);
-//
-// Console.WriteLine(f);
-
-// return;
-
-// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MisLoad.dll";
-var path = "C:\\ParkanUnpacked\\Land.msh\\2_03 00 00 00_Land.bin";
-
-var fs = new FileStream(path, FileMode.Open);
-var outputFs = new FileStream("Land.obj", FileMode.Create);
-var sw = new StreamWriter(outputFs);
-
-List points = [];
-var count = 0;
-while (fs.Position < fs.Length)
-{
- var x = fs.ReadFloatLittleEndian();
- var y = fs.ReadFloatLittleEndian();
- var z = fs.ReadFloatLittleEndian();
-
- var vertex = new Vector3D(x, y, z);
- sw.WriteLine($"v {x} {y} {z}");
-
- var seenIndex = points.FindIndex(vec => vec == vertex);
- if (seenIndex != -1)
- {
- vertex.Duplicates = seenIndex;
- }
-
- points.Add(vertex);
- count++;
-}
-
-File.WriteAllText("human-readable.json", JsonSerializer.Serialize(points, new JsonSerializerOptions()
-{
- WriteIndented = true
-}));
-
-Console.WriteLine($"Total vertices: {count}");
-
-
-// for (int i = 0; i < count / 4; i++)
-
-public record Vector3D(float X, float Y, float Z)
-{
- public int Duplicates { get; set; }
-}
-// var indices = string.Join(" ", Enumerable.Range(1, count));
-//
-// sw.WriteLine($"l {indices}");
-
-//
-// fs.Seek(0x1000, SeekOrigin.Begin);
-//
-// byte[] buf = new byte[34];
-// fs.ReadExactly(buf);
-//
-// var disassembler = new SharpDisasm.Disassembler(buf, ArchitectureMode.x86_32);
-// foreach (var instruction in disassembler.Disassemble())
-// {
-// Console.WriteLine($"{instruction.PC - instruction.Offset}: {instruction}");
-//
-// new Instruction()
-// {
-// Action = instruction.Mnemonic.ToString(),
-// Arguments = {instruction.Operands[0].ToString()}
-// };
-// }
-
-public class Instruction
-{
- public string Action { get; set; } = "";
-
- public List Arguments { get; set; } = [];
-}
+converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\161_fr_b_tower.msh");
\ No newline at end of file
diff --git a/README.md b/README.md
index fb44ad3..944746b 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,195 @@
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`.
+## Как быстро найти текст среди всех файлов игры
+
+```shell
+grep -rl --include="*" "s_tree_05" .
+```
+
+## Как быстро найти байты среди всех файлов игры
+
+```shell
+grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
+```
+
+## Как работает игра
+
+Главное меню:
+
+Игра сканирует хардкод папку `missions` на наличие файлов миссий. (буквально 01, 02, 03 и т.д.)
+
+Сначала игра читает название миссии из файла `descr` - тут название для меню.
+
+- Одиночные игры - `missions/single.{index}/descr`
+- Тренировочные миссии - `missions/tutorial.{index}/descr`
+- Кампания - `missions/campaign/campaign.{index1}/descr`
+ * Далее используются подпапки - `missions/campaign/campaign.{index1}/mission.{index2}/descr`
+
+Как только игра не находит файл `descr`, заканчивается итерация по папкам (понял, т.к. пробуется файл 05 - он не существует).
+
+Загрузка миссии:
+
+Читается файл `ui/game_resources.cfg`
+Из этого файла загружаются ресурсы
+- `library = "ui\\ui.lib"` - загружается файл `ui.lib`
+- `library = "ui\\font.lib"` - загружается файл `font.lib`
+- `library = "sounds.lib"` - загружается файл `sounds.lib`
+- `library = "voices.lib"` - загружается файл `voices.lib`
+
+Затем игра читает `save/saveslots.cfg` - тут слоты сохранения
+
+Затем `Comp.ini` - тут системные функции, которые используются для загрузки объектов.
+
+- `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) - вызывается функция по индексу 3.
+- Для всех остальных - функция по индексу 4.
+
+Однако контент файла `Comp.ini` буквально выглядит так:
+```
+3 animesh.dll LoadAgent // comments...
+4 animesh.dll LoadAgent //
+```
+А значит независимо от класса объекта вызывается `animesh.dll/LoadAgent`.
+
+## `.msh`
+
+- Тип 03 - это вершины (vertex)
+- Тип 06 - это рёбра (edge)
+- Тип 04 - скорее всего какие-то цвета RGBA или типа того
+- Тип 12 - microtexture mapping
+
+Тип 02 имеет какой-то заголовок.
+Игра сначала читает из него первые 96 байт. А затем пропускает с начала 148 байт
+
+# Внутренняя система ID
+
+- `4` - IShader
+- `5` - ITerrain
+- `6` - IGameObject (0x138)
+- `8` - ICamera
+- `9` - Queue
+- `10` - IControl
+- `0xb` - IAnimation
+- `0xd` - IMatManager
+- `0x10` - unknown (implemented by Wizard in Wizard.dll, also by Hallway in ArealMap.dll)
+- `0x11` - IBasement
+- `0x12` - ICamera2 - BufferingCamera
+- `0x13` - IEffectManager
+- `0x14` - IPosition
+- `0x16` - ILifeSystem
+- `0x17` - IBuilding
+- `0x18` - IMesh2
+- `0x19` - unknown (implemented by Wizard in Wizard.dll)
+- `0x20` - IJointMesh
+- `0x21` - IShade
+- `0x24` - IGameObject2
+- `0x101` - 3DRender
+- `0x201` - IWizard
+- `0x202` - IItemManager
+- `0x203` - ICollManager
+- `0x301` - IArealMap
+- `0x302` - ISystemArealMap
+- `0x303` - IHallway
+- `0x401` - ISuperAI
+- `0x105` - NResFile
+- `0x106` - NResFileMetadata
+- `0x501` - MissionData
+- `0x502` - ResTree
+- `0x700` - NetWatcher
+- `0x701` - INetworkInterface
+- `0x10d` - CreateVertexBufferData
+
## Контакты
Вы можете связаться со мной в [Telegram](https://t.me/bird_egop).
diff --git a/ScrLib/Extensions.cs b/ScrLib/Extensions.cs
deleted file mode 100644
index 6880051..0000000
--- a/ScrLib/Extensions.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Buffers.Binary;
-using System.Text;
-
-namespace ScrLib;
-
-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();
-
- if (len == 0)
- {
- return "";
- }
-
- var buffer = new byte[len];
-
- fs.ReadExactly(buffer, 0, len);
-
- return Encoding.ASCII.GetString(buffer, 0, len);
- }
-}
\ No newline at end of file
diff --git a/ScrLib/ScrLib.csproj b/ScrLib/ScrLib.csproj
index c632161..38858c5 100644
--- a/ScrLib/ScrLib.csproj
+++ b/ScrLib/ScrLib.csproj
@@ -1,3 +1,5 @@
-
+
+
+
diff --git a/ScrLib/ScrParser.cs b/ScrLib/ScrParser.cs
index 78d20c0..dbd84f7 100644
--- a/ScrLib/ScrParser.cs
+++ b/ScrLib/ScrParser.cs
@@ -1,4 +1,6 @@
-namespace ScrLib;
+using Common;
+
+namespace ScrLib;
public class ScrParser
{