0
mirror of https://github.com/sampletext32/ParkanPlayground.git synced 2025-10-13 23:10:23 +03:00

improvements

This commit is contained in:
bird_egop
2025-10-05 18:17:18 +03:00
parent c1ea70efe0
commit a774db37a6
17 changed files with 472 additions and 128 deletions

View File

@@ -5,7 +5,7 @@ namespace Common;
public static class Extensions public static class Extensions
{ {
public static int ReadInt32LittleEndian(this FileStream fs) public static int ReadInt32LittleEndian(this Stream fs)
{ {
Span<byte> buf = stackalloc byte[4]; Span<byte> buf = stackalloc byte[4];
fs.ReadExactly(buf); fs.ReadExactly(buf);
@@ -13,7 +13,7 @@ public static class Extensions
return BinaryPrimitives.ReadInt32LittleEndian(buf); return BinaryPrimitives.ReadInt32LittleEndian(buf);
} }
public static uint ReadUInt32LittleEndian(this FileStream fs) public static uint ReadUInt32LittleEndian(this Stream fs)
{ {
Span<byte> buf = stackalloc byte[4]; Span<byte> buf = stackalloc byte[4];
fs.ReadExactly(buf); fs.ReadExactly(buf);
@@ -21,7 +21,7 @@ public static class Extensions
return BinaryPrimitives.ReadUInt32LittleEndian(buf); return BinaryPrimitives.ReadUInt32LittleEndian(buf);
} }
public static float ReadFloatLittleEndian(this FileStream fs) public static float ReadFloatLittleEndian(this Stream fs)
{ {
Span<byte> buf = stackalloc byte[4]; Span<byte> buf = stackalloc byte[4];
fs.ReadExactly(buf); fs.ReadExactly(buf);
@@ -29,7 +29,7 @@ public static class Extensions
return BinaryPrimitives.ReadSingleLittleEndian(buf); return BinaryPrimitives.ReadSingleLittleEndian(buf);
} }
public static string ReadNullTerminatedString(this FileStream fs) public static string ReadNullTerminatedString(this Stream fs)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
@@ -47,7 +47,25 @@ public static class Extensions
return sb.ToString(); return sb.ToString();
} }
public static string ReadLengthPrefixedString(this FileStream fs) public static string ReadNullTerminated1251String(this Stream fs)
{
var sb = new StringBuilder();
while (true)
{
var b = (byte)fs.ReadByte();
if (b == 0)
{
break;
}
sb.Append(Encoding.GetEncoding("windows-1251").GetString([b]));
}
return sb.ToString();
}
public static string ReadLengthPrefixedString(this Stream fs)
{ {
var len = fs.ReadInt32LittleEndian(); var len = fs.ReadInt32LittleEndian();

View File

@@ -6,7 +6,26 @@ public record CpDatEntry(
int Magic1, int Magic1,
int Magic2, int Magic2,
string Description, string Description,
int Magic3, DatEntryType Type,
int ChildCount, // игра не хранит это число в объекте, но оно есть в файле int ChildCount, // игра не хранит это число в объекте, но оно есть в файле
List<CpDatEntry> Children List<CpDatEntry> Children
); );
// Magic3 seems to be a type
// 0 - chassis
// 1 - turret (у зданий почему-то дефлектор тоже 1), может быть потому, что дефлектор вращается так же как башня у юнитов
// 2 - armour
// 3 - part
// 4 - cannon
// 5 - ammo
public enum DatEntryType
{
Unspecified = -1,
Chassis = 0,
Turret = 1,
Armour = 2,
Part = 3,
Cannon = 4,
Ammo = 5,
}

View File

@@ -6,10 +6,15 @@ public class CpDatParser
{ {
public static CpDatParseResult Parse(string filePath) public static CpDatParseResult Parse(string filePath)
{ {
Span<byte> f0f1 = stackalloc byte[4];
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
return Parse(fs);
}
public static CpDatParseResult Parse(Stream fs)
{
Span<byte> f0f1 = stackalloc byte[4];
if (fs.Length < 8) if (fs.Length < 8)
return new CpDatParseResult(null, "File too small to be a valid \"cp\" .dat file."); return new CpDatParseResult(null, "File too small to be a valid \"cp\" .dat file.");
@@ -22,7 +27,8 @@ public class CpDatParser
var schemeType = (SchemeType)fs.ReadInt32LittleEndian(); var schemeType = (SchemeType)fs.ReadInt32LittleEndian();
var entryLength = 0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов var entryLength =
0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов
if ((fs.Length - 8) % entryLength != 0) if ((fs.Length - 8) % entryLength != 0)
{ {
return new CpDatParseResult(null, "File size is not valid according to expected entry length."); return new CpDatParseResult(null, "File size is not valid according to expected entry length.");
@@ -35,7 +41,7 @@ public class CpDatParser
return new CpDatParseResult(scheme, null); return new CpDatParseResult(scheme, null);
} }
private static CpDatEntry ReadEntryRecursive(FileStream fs) private static CpDatEntry ReadEntryRecursive(Stream fs)
{ {
var str1 = fs.ReadNullTerminatedString(); var str1 = fs.ReadNullTerminatedString();
@@ -47,10 +53,10 @@ public class CpDatParser
var magic1 = fs.ReadInt32LittleEndian(); var magic1 = fs.ReadInt32LittleEndian();
var magic2 = fs.ReadInt32LittleEndian(); var magic2 = fs.ReadInt32LittleEndian();
var descriptionString = fs.ReadNullTerminatedString(); var descriptionString = fs.ReadNullTerminated1251String();
fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current); // -1 ignore null terminator fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
var magic3 = fs.ReadInt32LittleEndian(); var type = (DatEntryType)fs.ReadInt32LittleEndian();
// игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов // игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов
var childCount = fs.ReadInt32LittleEndian(); var childCount = fs.ReadInt32LittleEndian();
@@ -63,6 +69,6 @@ public class CpDatParser
children.Add(child); children.Add(child);
} }
return new CpDatEntry(str1, str2, magic1, magic2, descriptionString, magic3, childCount, Children: children); return new CpDatEntry(str1, str2, magic1, magic2, descriptionString, type, childCount, Children: children);
} }
} }

View File

@@ -8,6 +8,11 @@ public class MissionTmaParser
{ {
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
return ReadFile(fs);
}
public static MissionTmaParseResult ReadFile(Stream fs)
{
var arealData = LoadAreals(fs); var arealData = LoadAreals(fs);
var clansData = LoadClans(fs); var clansData = LoadClans(fs);
@@ -20,7 +25,7 @@ public class MissionTmaParser
return new MissionTmaParseResult(missionDat, null); return new MissionTmaParseResult(missionDat, null);
} }
private static ArealsFileData LoadAreals(FileStream fileStream) private static ArealsFileData LoadAreals(Stream fileStream)
{ {
var unusedHeader = fileStream.ReadInt32LittleEndian(); var unusedHeader = fileStream.ReadInt32LittleEndian();
var arealCount = fileStream.ReadInt32LittleEndian(); var arealCount = fileStream.ReadInt32LittleEndian();
@@ -56,7 +61,7 @@ public class MissionTmaParser
return new ArealsFileData(unusedHeader, arealCount, infos); return new ArealsFileData(unusedHeader, arealCount, infos);
} }
private static ClansFileData? LoadClans(FileStream fileStream) private static ClansFileData? LoadClans(Stream fileStream)
{ {
var clanFeatureSet = fileStream.ReadInt32LittleEndian(); var clanFeatureSet = fileStream.ReadInt32LittleEndian();
@@ -158,7 +163,7 @@ public class MissionTmaParser
return clanInfo; return clanInfo;
} }
private static GameObjectsFileData LoadGameObjects(FileStream fileStream) private static GameObjectsFileData LoadGameObjects(Stream fileStream)
{ {
var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian(); var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian();

View File

@@ -9,6 +9,11 @@ public static class NResParser
{ {
using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return ReadFile(nResFs);
}
public static NResParseResult ReadFile(Stream nResFs)
{
if (nResFs.Length < 16) if (nResFs.Length < 16)
{ {
return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт"); return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт");

View File

@@ -1,4 +1,5 @@
using System.Numerics; using System.Numerics;
using System.Text;
using ImGuiNET; using ImGuiNET;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NResUI.Abstractions; using NResUI.Abstractions;
@@ -33,6 +34,8 @@ public class App
public void Init(IWindow window, GL openGl, ImFontPtr openSansFont) public void Init(IWindow window, GL openGl, ImFontPtr openSansFont)
{ {
// Call this once at program startup
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
ImGui.StyleColorsLight(); ImGui.StyleColorsLight();
IServiceCollection serviceCollection = new ServiceCollection(); IServiceCollection serviceCollection = new ServiceCollection();

View File

@@ -34,22 +34,60 @@ public class CpDatSchemeExplorer : IImGuiPanel
ImGui.Separator(); ImGui.Separator();
if (ImGui.BeginTable("content", 7, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) if (ImGui.BeginTable("content", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX | ImGuiTableFlags.Sortable))
{ {
ImGui.TableSetupColumn("Индекс");
ImGui.TableSetupColumn("Уровень вложенности"); ImGui.TableSetupColumn("Уровень вложенности");
ImGui.TableSetupColumn("Архив"); ImGui.TableSetupColumn("Архив");
ImGui.TableSetupColumn("Элемент"); ImGui.TableSetupColumn("Элемент");
ImGui.TableSetupColumn("Magic1"); ImGui.TableSetupColumn("Magic1");
ImGui.TableSetupColumn("Magic2"); ImGui.TableSetupColumn("Magic2");
ImGui.TableSetupColumn("Описание"); ImGui.TableSetupColumn("Описание");
ImGui.TableSetupColumn("Magic3"); ImGui.TableSetupColumn("Тип");
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
// Handle sorting
ImGuiTableSortSpecsPtr sortSpecs = ImGui.TableGetSortSpecs();
if (sortSpecs.SpecsDirty)
{
// Only handle the first sort spec for simplicity
var sortSpec = sortSpecs.Specs;
if (sortSpec.ColumnIndex == 0)
{
_viewModel.RebuildFlatList();
}
else
{
_viewModel.FlatList.Sort((a, b) =>
{
int result = 0;
switch (sortSpec.ColumnIndex)
{
case 1: result = a.Level.CompareTo(b.Level); break;
case 2: result = string.Compare(a.Entry.ArchiveFile, b.Entry.ArchiveFile, StringComparison.Ordinal); break;
case 3: result = string.Compare(a.Entry.ArchiveEntryName, b.Entry.ArchiveEntryName, StringComparison.Ordinal); break;
case 4: result = a.Entry.Magic1.CompareTo(b.Entry.Magic1); break;
case 5: result = a.Entry.Magic2.CompareTo(b.Entry.Magic2); break;
case 6: result = string.Compare(a.Entry.Description, b.Entry.Description, StringComparison.Ordinal); break;
case 7: result = a.Entry.Type.CompareTo(b.Entry.Type); break;
}
return sortSpec.SortDirection == ImGuiSortDirection.Descending ? -result : result;
});
}
sortSpecs.SpecsDirty = false;
}
for (int i = 0; i < _viewModel.FlatList.Count; i++) for (int i = 0; i < _viewModel.FlatList.Count; i++)
{ {
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(i.ToString());
ImGui.TableNextColumn();
ImGui.Text(_viewModel.FlatList[i].Level.ToString()); ImGui.Text(_viewModel.FlatList[i].Level.ToString());
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveFile); ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveFile);
@@ -62,7 +100,7 @@ public class CpDatSchemeExplorer : IImGuiPanel
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(_viewModel.FlatList[i].Entry.Description); ImGui.Text(_viewModel.FlatList[i].Entry.Description);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(_viewModel.FlatList[i].Entry.Magic3.ToString()); ImGui.Text(_viewModel.FlatList[i].Entry.Type.ToString("G"));
} }
ImGui.EndTable(); ImGui.EndTable();
@@ -80,9 +118,9 @@ public class CpDatSchemeExplorer : IImGuiPanel
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(entry.Magic2.ToString()); ImGui.Text(entry.Magic2.ToString());
ImGui.Text("Magic3: "); ImGui.Text("Тип: ");
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(entry.Magic3.ToString()); ImGui.Text(entry.Type.ToString());
ImGui.Text("Кол-во дочерних элементов: "); ImGui.Text("Кол-во дочерних элементов: ");
ImGui.SameLine(); ImGui.SameLine();

View File

@@ -1,23 +1,43 @@
using ImGuiNET; using CpDatLib;
using ImGuiNET;
using MissionTmaLib.Parsing;
using NResLib;
using NResUI.Abstractions; using NResUI.Abstractions;
using NResUI.Models; using NResUI.Models;
using ScrLib;
using TexmLib;
using VarsetLib;
namespace NResUI.ImGuiUI; namespace NResUI.ImGuiUI;
public class NResExplorerPanel : IImGuiPanel public class NResExplorerPanel : IImGuiPanel
{ {
private readonly NResExplorerViewModel _viewModel; private readonly NResExplorerViewModel _viewModel;
private readonly TexmExplorerViewModel _texmExplorerViewModel;
private readonly VarsetViewModel _varsetViewModel;
private readonly CpDatSchemeViewModel _cpDatSchemeViewModel;
private readonly MissionTmaViewModel _missionTmaViewModel;
private readonly ScrViewModel _scrViewModel;
public NResExplorerPanel(NResExplorerViewModel viewModel) public NResExplorerPanel(NResExplorerViewModel viewModel, TexmExplorerViewModel texmExplorerViewModel,
VarsetViewModel varsetViewModel, CpDatSchemeViewModel cpDatSchemeViewModel, MissionTmaViewModel missionTmaViewModel, ScrViewModel scrViewModel)
{ {
_viewModel = viewModel; _viewModel = viewModel;
_texmExplorerViewModel = texmExplorerViewModel;
_varsetViewModel = varsetViewModel;
_cpDatSchemeViewModel = cpDatSchemeViewModel;
_missionTmaViewModel = missionTmaViewModel;
_scrViewModel = scrViewModel;
} }
int contextMenuRow = -1;
public void OnImGuiRender() public void OnImGuiRender()
{ {
if (ImGui.Begin("NRes Explorer")) if (ImGui.Begin("NRes Explorer"))
{ {
ImGui.Text("NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res"); ImGui.Text(
"NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res");
ImGui.Separator(); ImGui.Separator();
if (!_viewModel.HasFile) if (!_viewModel.HasFile)
@@ -48,8 +68,8 @@ public class NResExplorerPanel : IImGuiPanel
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString()); ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
if (ImGui.BeginTable("content", 12,
if (ImGui.BeginTable("content", 12, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX)) ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{ {
ImGui.TableSetupColumn("Тип файла"); ImGui.TableSetupColumn("Тип файла");
ImGui.TableSetupColumn("Кол-во элементов"); ImGui.TableSetupColumn("Кол-во элементов");
@@ -70,6 +90,17 @@ public class NResExplorerPanel : IImGuiPanel
{ {
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Selectable("##row_select" + i, false, ImGuiSelectableFlags.SpanAllColumns);
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
Console.WriteLine("Context menu for row " + i);
contextMenuRow = i;
ImGui.OpenPopup("row_context_menu");
}
ImGui.SameLine();
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.Text(_viewModel.Archive.Files[i].ElementCount.ToString());
@@ -122,6 +153,130 @@ public class NResExplorerPanel : IImGuiPanel
); );
} }
if (ImGui.BeginPopup("row_context_menu"))
{
if (contextMenuRow == -1 || contextMenuRow > _viewModel.Archive.Files.Count)
{
ImGui.Text("Broken context menu :(. Reopen");
}
else
{
var file = _viewModel.Archive.Files[contextMenuRow];
ImGui.Text("Actions for file " + file.FileName);
ImGui.TextDisabled("Program has no understading of file format(");
ImGui.Separator();
if (ImGui.MenuItem("Open as Texture TEXM"))
{
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
FileShare.Read);
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
var buffer = new byte[file.FileLength];
fs.ReadExactly(buffer, 0, file.FileLength);
using var ms = new MemoryStream(buffer);
var parseResult = TexmParser.ReadFromStream(ms, file.FileName);
_texmExplorerViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
Console.WriteLine("Read TEXM from context menu");
}
if (ImGui.MenuItem("Open as Archive NRes"))
{
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
FileShare.Read);
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
var buffer = new byte[file.FileLength];
fs.ReadExactly(buffer, 0, file.FileLength);
using var ms = new MemoryStream(buffer);
var parseResult = NResParser.ReadFile(ms);
_viewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
Console.WriteLine("Read NRes from context menu");
}
if (ImGui.MenuItem("Open as Varset .var"))
{
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
FileShare.Read);
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
var buffer = new byte[file.FileLength];
fs.ReadExactly(buffer, 0, file.FileLength);
using var ms = new MemoryStream(buffer);
var parseResult = VarsetParser.Parse(ms);
_varsetViewModel.Items = parseResult;
Console.WriteLine("Read Varset from context menu");
}
if (ImGui.MenuItem("Open as Scheme cp.dat"))
{
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
FileShare.Read);
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
var buffer = new byte[file.FileLength];
fs.ReadExactly(buffer, 0, file.FileLength);
using var ms = new MemoryStream(buffer);
var parseResult = CpDatParser.Parse(ms);
_cpDatSchemeViewModel.SetParseResult(parseResult, file.FileName);
Console.WriteLine("Read cp.dat from context menu");
}
if (ImGui.MenuItem("Open as Mission .tma"))
{
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
FileShare.Read);
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
var buffer = new byte[file.FileLength];
fs.ReadExactly(buffer, 0, file.FileLength);
using var ms = new MemoryStream(buffer);
var parseResult = MissionTmaParser.ReadFile(ms);
_missionTmaViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
Console.WriteLine("Read .tma from context menu");
}
if (ImGui.MenuItem("Open as Scripts .scr"))
{
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
FileShare.Read);
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
var buffer = new byte[file.FileLength];
fs.ReadExactly(buffer, 0, file.FileLength);
using var ms = new MemoryStream(buffer);
var parseResult = ScrParser.ReadFile(ms);
_scrViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
Console.WriteLine("Read .scr from context menu");
}
}
ImGui.EndPopup();
}
ImGui.EndTable(); ImGui.EndTable();
} }
} }

View File

@@ -10,7 +10,7 @@ public class CpDatSchemeViewModel
public CpDatScheme? CpDatScheme { get; set; } public CpDatScheme? CpDatScheme { get; set; }
public List<(int Level, CpDatEntry Entry)> FlatList { get; set; } public List<(int Level, CpDatEntry Entry)> FlatList { get; set; } = [];
public string? Path { get; set; } public string? Path { get; set; }
@@ -22,9 +22,20 @@ public class CpDatSchemeViewModel
Path = path; Path = path;
if (CpDatScheme is not null) if (CpDatScheme is not null)
{
RebuildFlatList();
}
}
public void RebuildFlatList()
{ {
FlatList = []; FlatList = [];
if (CpDatScheme is null)
{
return;
}
CollectEntries(CpDatScheme.Root, 0); CollectEntries(CpDatScheme.Root, 0);
void CollectEntries(CpDatEntry entry, int level) void CollectEntries(CpDatEntry entry, int level)
@@ -36,5 +47,4 @@ public class CpDatSchemeViewModel
} }
} }
} }
}
} }

View File

@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType Condition="'$(OS)' == 'Windows_NT'">WinExe</OutputType>
<OutputType Condition="'$(OS)' != 'Windows_NT'">Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -29,42 +29,42 @@ public static class Msh02
var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c)); var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c));
var bb = new BoundingBox(); var bb = new BoundingBox();
bb.Vec1 = new Vector3( bb.BottomFrontLeft = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8))
); );
bb.Vec2 = new Vector3( bb.BottomFrontRight = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20))
); );
bb.Vec3 = new Vector3( bb.BottomBackRight = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32))
); );
bb.Vec4 = new Vector3( bb.BottomBackLeft = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44))
); );
bb.Vec5 = new Vector3( bb.TopFrontLeft = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56))
); );
bb.Vec6 = new Vector3( bb.TopFrontRight = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68))
); );
bb.Vec7 = new Vector3( bb.TopBackRight = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80))
); );
bb.Vec8 = new Vector3( bb.TopBackLeft = new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)),
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92)) BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92))
@@ -149,6 +149,9 @@ public static class Msh02
public List<Msh02Element> Elements { get; set; } public List<Msh02Element> Elements { get; set; }
} }
/// <summary>
/// 140 байт в начале файла
/// </summary>
public class Msh02Header public class Msh02Header
{ {
public BoundingBox BoundingBox { get; set; } public BoundingBox BoundingBox { get; set; }
@@ -172,15 +175,19 @@ public static class Msh02
public Vector3 Vector5 { get; set; } public Vector3 Vector5 { get; set; }
} }
/// <summary>
/// 96 bytes - bounding box (8 points each 3 float = 96 bytes)
/// 0x60 bytes or 0x18 by 4 bytes
/// </summary>
public class BoundingBox public class BoundingBox
{ {
public Vector3 Vec1 { get; set; } public Vector3 BottomFrontLeft { get; set; }
public Vector3 Vec2 { get; set; } public Vector3 BottomFrontRight { get; set; }
public Vector3 Vec3 { get; set; } public Vector3 BottomBackRight { get; set; }
public Vector3 Vec4 { get; set; } public Vector3 BottomBackLeft { get; set; }
public Vector3 Vec5 { get; set; } public Vector3 TopFrontLeft { get; set; }
public Vector3 Vec6 { get; set; } public Vector3 TopFrontRight { get; set; }
public Vector3 Vec7 { get; set; } public Vector3 TopBackRight { get; set; }
public Vector3 Vec8 { get; set; } public Vector3 TopBackLeft { get; set; }
} }
} }

55
ParkanPlayground/Msh15.cs Normal file
View File

@@ -0,0 +1,55 @@
using System.Buffers.Binary;
using NResLib;
namespace ParkanPlayground;
public static class Msh15
{
public static List<Msh15Element> ReadComponent(
FileStream mshFs, NResArchive archive)
{
var entry = archive.Files.FirstOrDefault(x => x.FileType == "15 00 00 00");
if (entry is null)
{
throw new Exception("Archive doesn't contain file (15)");
}
var data = new byte[entry.ElementCount * entry.ElementSize];
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
mshFs.ReadExactly(data, 0, data.Length);
var elementBytes = data.Chunk(28);
var elements = elementBytes.Select(x => new Msh15Element()
{
Flags = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0)),
Magic04 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)),
Vertex1Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)),
Vertex2Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(10)),
Vertex3Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(12)),
Magic0E = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(14)),
Magic12 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(18)),
Magic16 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(22)),
Magic1A = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(26)),
}).ToList();
return elements;
}
public class Msh15Element
{
public uint Flags { get; set; }
public uint Magic04 { get; set; }
public ushort Vertex1Index { get; set; }
public ushort Vertex2Index { get; set; }
public ushort Vertex3Index { get; set; }
public uint Magic0E { get; set; }
public uint Magic12 { get; set; }
public uint Magic16 { get; set; }
public ushort Magic1A { get; set; }
}
}

View File

@@ -15,6 +15,8 @@ public class MshConverter
var component01 = Msh01.ReadComponent(mshFs, mshNres); var component01 = Msh01.ReadComponent(mshFs, mshNres);
var component02 = Msh02.ReadComponent(mshFs, mshNres); var component02 = Msh02.ReadComponent(mshFs, mshNres);
var component15 = Msh15.ReadComponent(mshFs, mshNres);
var component0A = Msh0A.ReadComponent(mshFs, mshNres); var component0A = Msh0A.ReadComponent(mshFs, mshNres);
var component07 = Msh07.ReadComponent(mshFs, mshNres); var component07 = Msh07.ReadComponent(mshFs, mshNres);
var component0D = Msh0D.ReadComponent(mshFs, mshNres); var component0D = Msh0D.ReadComponent(mshFs, mshNres);

View File

@@ -9,8 +9,8 @@ using ParkanPlayground;
var converter = new MshConverter(); var converter = new MshConverter();
converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\133_fr_m_bunker.msh"); // 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"); converter.Convert("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\DATA\\MAPS\\SC_1\\Land.msh");
// converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\73_fr_m_brige.msh"); // converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\73_fr_m_brige.msh");
// converter.Convert("E:\\ParkanUnpacked\\intsys.rlb\\277_MESH_o_pws_l_01.msh"); // 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\\static.rlb\\2_MESH_s_stn_0_01.msh");

View File

@@ -302,7 +302,7 @@ IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
- `4` - IShader - `4` - IShader
- `5` - ITerrain - `5` - ITerrain
- `6` - IGameObject (0x138) - `6` - IGameObject (0x138)
- `7` - IShadeConfig (у меня в папке с игрой его не оказалось) - `7` - IAtmosphereObject
- `8` - ICamera - `8` - ICamera
- `9` - IQueue - `9` - IQueue
- `10` - IControl - `10` - IControl
@@ -329,6 +329,7 @@ IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
- `0x101` - 3DRender - `0x101` - 3DRender
- `0x105` - NResFile - `0x105` - NResFile
- `0x106` - NResFileMetadata - `0x106` - NResFileMetadata
- `0x107` - 3DSound
- `0x201` - IWizard - `0x201` - IWizard
- `0x202` - IItemManager - `0x202` - IItemManager
- `0x203` - ICollManager - `0x203` - ICollManager

View File

@@ -6,8 +6,13 @@ public class ScrParser
{ {
public static ScrFile ReadFile(string filePath) public static ScrFile ReadFile(string filePath)
{ {
var fs = new FileStream(filePath, FileMode.Open); using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
return ReadFile(fs);
}
public static ScrFile ReadFile(Stream fs)
{
var scrFile = new ScrFile(); var scrFile = new ScrFile();
scrFile.Magic = fs.ReadInt32LittleEndian(); scrFile.Magic = fs.ReadInt32LittleEndian();

View File

@@ -4,8 +4,15 @@ public class VarsetParser
{ {
public static List<VarsetItem> Parse(string path) public static List<VarsetItem> Parse(string path)
{ {
FileStream fs = new FileStream(path, FileMode.Open); using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return Parse(fs);
}
public static List<VarsetItem> Parse(Stream fs)
{
try
{
var reader = new StreamReader(fs); var reader = new StreamReader(fs);
List<VarsetItem> varsetItems = []; List<VarsetItem> varsetItems = [];
@@ -36,14 +43,16 @@ public class VarsetParser
var openParenthesisIndex = line.IndexOf("("); var openParenthesisIndex = line.IndexOf("(");
var closeParenthesisIndex = line.IndexOf(")"); var closeParenthesisIndex = line.IndexOf(")");
if (openParenthesisIndex == -1 || closeParenthesisIndex == -1 || closeParenthesisIndex <= openParenthesisIndex) if (openParenthesisIndex == -1 || closeParenthesisIndex == -1 ||
closeParenthesisIndex <= openParenthesisIndex)
{ {
Console.WriteLine($"Error on line: {lineIndex}! VAR() format invalid"); Console.WriteLine($"Error on line: {lineIndex}! VAR() format invalid");
lineIndex++; lineIndex++;
continue; continue;
} }
var arguments = line.Substring(openParenthesisIndex + 1, closeParenthesisIndex - openParenthesisIndex - 1); var arguments = line.Substring(openParenthesisIndex + 1,
closeParenthesisIndex - openParenthesisIndex - 1);
var parts = arguments.Trim() var parts = arguments.Trim()
.Split(','); .Split(',');
@@ -65,4 +74,9 @@ public class VarsetParser
return varsetItems; return varsetItems;
} }
catch
{
return [];
}
}
} }