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 {