mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-08-23 17:40:26 +03:00
Parse cp .dat files. Object schemes.
Test parsing of .msh
This commit is contained in:
3
Common/Common.csproj
Normal file
3
Common/Common.csproj
Normal file
@@ -0,0 +1,3 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
</Project>
|
@@ -1,7 +1,7 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace MissionTmaLib.Parsing;
|
||||
namespace Common;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
@@ -13,6 +13,14 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32LittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
@@ -21,6 +29,24 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedString(this FileStream fs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var b = fs.ReadByte();
|
||||
if (b == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
sb.Append((char)b);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||
{
|
||||
var len = fs.ReadInt32LittleEndian();
|
3
Common/IndexedEdge.cs
Normal file
3
Common/IndexedEdge.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Common;
|
||||
|
||||
public record IndexedEdge(ushort Index1, ushort Index2);
|
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);
|
@@ -14,6 +14,6 @@
|
||||
<EnablePackageVersionOverride>false</EnablePackageVersionOverride>
|
||||
|
||||
<!-- Suppress package version warnings -->
|
||||
<NoWarn>$(NoWarn);NU1507</NoWarn>
|
||||
<NoWarn>$(NoWarn);NU1507;CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
@@ -1,4 +1,6 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public class GameObjectInfo
|
||||
{
|
||||
|
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);
|
@@ -1,3 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
@@ -1,3 +0,0 @@
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record Vector3(float X, float Y, float Z);
|
@@ -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)
|
||||
{
|
||||
|
@@ -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)
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<File Path="Directory.Packages.props" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="Common\Common.csproj" Type="Classic C#" />
|
||||
<Project Path="MeshUnpacker/MeshUnpacker.csproj" />
|
||||
<Project Path="MissionDataUnpacker/MissionDataUnpacker.csproj" />
|
||||
<Project Path="MissionTmaLib/MissionTmaLib.csproj" />
|
||||
@@ -16,4 +17,4 @@
|
||||
<Project Path="TextureDecoder/TextureDecoder.csproj" />
|
||||
<Project Path="VarsetLib/VarsetLib.csproj" />
|
||||
<Project Path="Visualisator/Visualisator.csproj" />
|
||||
</Solution>
|
||||
</Solution>
|
161
ParkanPlayground/CpDatEntryConverter.cs
Normal file
161
ParkanPlayground/CpDatEntryConverter.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using Common;
|
||||
using MissionTmaLib.Parsing;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
/// <summary>
|
||||
/// Игра называет этот объект "схемой"
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// В игре файл .dat читается в ArealMap.dll/CreateObjectFromScheme
|
||||
/// </remarks>
|
||||
/// <code>
|
||||
///
|
||||
/// struct Scheme
|
||||
/// {
|
||||
/// char[32] str1; // имя архива
|
||||
/// char[32] str2; // имя объекта в архиве
|
||||
/// undefined4 magic1;
|
||||
/// undefined4 magic2;
|
||||
/// char[32] str3; // описание объекта
|
||||
/// undefined4 magic3;
|
||||
/// }
|
||||
///
|
||||
/// </code>
|
||||
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<byte> 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<DatEntry> children = new List<DatEntry>();
|
||||
|
||||
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<DatEntry> 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,
|
||||
}
|
||||
}
|
90
ParkanPlayground/MshConverter.cs
Normal file
90
ParkanPlayground/MshConverter.cs
Normal file
@@ -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<IndexedEdge>((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<Vector3> 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<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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||
|
@@ -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<byte> 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<byte> 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<Vector3D> 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<string> Arguments { get; set; } = [];
|
||||
}
|
||||
converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\161_fr_b_tower.msh");
|
189
README.md
189
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).
|
||||
|
@@ -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<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> 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);
|
||||
}
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@@ -1,4 +1,6 @@
|
||||
namespace ScrLib;
|
||||
using Common;
|
||||
|
||||
namespace ScrLib;
|
||||
|
||||
public class ScrParser
|
||||
{
|
||||
|
Reference in New Issue
Block a user