mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-10-13 15:00:23 +03:00
improvements
This commit is contained in:
@@ -5,7 +5,7 @@ namespace Common;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static int ReadInt32LittleEndian(this FileStream fs)
|
||||
public static int ReadInt32LittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
@@ -13,7 +13,7 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32LittleEndian(this FileStream fs)
|
||||
public static uint ReadUInt32LittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
@@ -21,7 +21,7 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
public static float ReadFloatLittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
@@ -29,7 +29,7 @@ public static class Extensions
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedString(this FileStream fs)
|
||||
public static string ReadNullTerminatedString(this Stream fs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
@@ -47,7 +47,25 @@ public static class Extensions
|
||||
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();
|
||||
|
||||
|
@@ -6,7 +6,26 @@ public record CpDatEntry(
|
||||
int Magic1,
|
||||
int Magic2,
|
||||
string Description,
|
||||
int Magic3,
|
||||
DatEntryType Type,
|
||||
int ChildCount, // игра не хранит это число в объекте, но оно есть в файле
|
||||
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,
|
||||
}
|
@@ -6,10 +6,15 @@ public class CpDatParser
|
||||
{
|
||||
public static CpDatParseResult Parse(string filePath)
|
||||
{
|
||||
Span<byte> f0f1 = stackalloc byte[4];
|
||||
|
||||
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)
|
||||
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 entryLength = 0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов
|
||||
var entryLength =
|
||||
0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов
|
||||
if ((fs.Length - 8) % entryLength != 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private static CpDatEntry ReadEntryRecursive(FileStream fs)
|
||||
private static CpDatEntry ReadEntryRecursive(Stream fs)
|
||||
{
|
||||
var str1 = fs.ReadNullTerminatedString();
|
||||
|
||||
@@ -47,10 +53,10 @@ public class CpDatParser
|
||||
var magic1 = 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
|
||||
var magic3 = fs.ReadInt32LittleEndian();
|
||||
var type = (DatEntryType)fs.ReadInt32LittleEndian();
|
||||
|
||||
// игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов
|
||||
var childCount = fs.ReadInt32LittleEndian();
|
||||
@@ -63,6 +69,6 @@ public class CpDatParser
|
||||
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);
|
||||
}
|
||||
}
|
@@ -8,6 +8,11 @@ public class MissionTmaParser
|
||||
{
|
||||
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 clansData = LoadClans(fs);
|
||||
@@ -20,7 +25,7 @@ public class MissionTmaParser
|
||||
return new MissionTmaParseResult(missionDat, null);
|
||||
}
|
||||
|
||||
private static ArealsFileData LoadAreals(FileStream fileStream)
|
||||
private static ArealsFileData LoadAreals(Stream fileStream)
|
||||
{
|
||||
var unusedHeader = fileStream.ReadInt32LittleEndian();
|
||||
var arealCount = fileStream.ReadInt32LittleEndian();
|
||||
@@ -56,7 +61,7 @@ public class MissionTmaParser
|
||||
return new ArealsFileData(unusedHeader, arealCount, infos);
|
||||
}
|
||||
|
||||
private static ClansFileData? LoadClans(FileStream fileStream)
|
||||
private static ClansFileData? LoadClans(Stream fileStream)
|
||||
{
|
||||
var clanFeatureSet = fileStream.ReadInt32LittleEndian();
|
||||
|
||||
@@ -158,7 +163,7 @@ public class MissionTmaParser
|
||||
return clanInfo;
|
||||
}
|
||||
|
||||
private static GameObjectsFileData LoadGameObjects(FileStream fileStream)
|
||||
private static GameObjectsFileData LoadGameObjects(Stream fileStream)
|
||||
{
|
||||
var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian();
|
||||
|
||||
|
@@ -9,6 +9,11 @@ public static class NResParser
|
||||
{
|
||||
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)
|
||||
{
|
||||
return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт");
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ImGuiNET;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NResUI.Abstractions;
|
||||
@@ -33,6 +34,8 @@ public class App
|
||||
|
||||
public void Init(IWindow window, GL openGl, ImFontPtr openSansFont)
|
||||
{
|
||||
// Call this once at program startup
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
ImGui.StyleColorsLight();
|
||||
|
||||
IServiceCollection serviceCollection = new ServiceCollection();
|
||||
|
@@ -34,22 +34,60 @@ public class CpDatSchemeExplorer : IImGuiPanel
|
||||
|
||||
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("Magic1");
|
||||
ImGui.TableSetupColumn("Magic2");
|
||||
ImGui.TableSetupColumn("Описание");
|
||||
ImGui.TableSetupColumn("Magic3");
|
||||
ImGui.TableSetupColumn("Тип");
|
||||
|
||||
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++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(i.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Level.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveFile);
|
||||
@@ -62,7 +100,7 @@ public class CpDatSchemeExplorer : IImGuiPanel
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Description);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Magic3.ToString());
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Type.ToString("G"));
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
@@ -80,9 +118,9 @@ public class CpDatSchemeExplorer : IImGuiPanel
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.Magic2.ToString());
|
||||
|
||||
ImGui.Text("Magic3: ");
|
||||
ImGui.Text("Тип: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.Magic3.ToString());
|
||||
ImGui.Text(entry.Type.ToString());
|
||||
|
||||
ImGui.Text("Кол-во дочерних элементов: ");
|
||||
ImGui.SameLine();
|
||||
|
@@ -1,23 +1,43 @@
|
||||
using ImGuiNET;
|
||||
using CpDatLib;
|
||||
using ImGuiNET;
|
||||
using MissionTmaLib.Parsing;
|
||||
using NResLib;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
using ScrLib;
|
||||
using TexmLib;
|
||||
using VarsetLib;
|
||||
|
||||
namespace NResUI.ImGuiUI;
|
||||
|
||||
public class NResExplorerPanel : IImGuiPanel
|
||||
{
|
||||
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;
|
||||
_texmExplorerViewModel = texmExplorerViewModel;
|
||||
_varsetViewModel = varsetViewModel;
|
||||
_cpDatSchemeViewModel = cpDatSchemeViewModel;
|
||||
_missionTmaViewModel = missionTmaViewModel;
|
||||
_scrViewModel = scrViewModel;
|
||||
}
|
||||
|
||||
int contextMenuRow = -1;
|
||||
|
||||
public void OnImGuiRender()
|
||||
{
|
||||
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();
|
||||
|
||||
if (!_viewModel.HasFile)
|
||||
@@ -48,8 +68,8 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
|
||||
|
||||
|
||||
if (ImGui.BeginTable("content", 12, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
if (ImGui.BeginTable("content", 12,
|
||||
ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
{
|
||||
ImGui.TableSetupColumn("Тип файла");
|
||||
ImGui.TableSetupColumn("Кол-во элементов");
|
||||
@@ -70,6 +90,17 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
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.TableNextColumn();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ public class CpDatSchemeViewModel
|
||||
|
||||
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; }
|
||||
|
||||
@@ -22,9 +22,20 @@ public class CpDatSchemeViewModel
|
||||
Path = path;
|
||||
|
||||
if (CpDatScheme is not null)
|
||||
{
|
||||
RebuildFlatList();
|
||||
}
|
||||
}
|
||||
|
||||
public void RebuildFlatList()
|
||||
{
|
||||
FlatList = [];
|
||||
|
||||
if (CpDatScheme is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CollectEntries(CpDatScheme.Root, 0);
|
||||
|
||||
void CollectEntries(CpDatEntry entry, int level)
|
||||
@@ -37,4 +48,3 @@ public class CpDatSchemeViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputType Condition="'$(OS)' == 'Windows_NT'">WinExe</OutputType>
|
||||
<OutputType Condition="'$(OS)' != 'Windows_NT'">Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -29,42 +29,42 @@ public static class Msh02
|
||||
var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c));
|
||||
|
||||
var bb = new BoundingBox();
|
||||
bb.Vec1 = new Vector3(
|
||||
bb.BottomFrontLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8))
|
||||
);
|
||||
bb.Vec2 = new Vector3(
|
||||
bb.BottomFrontRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20))
|
||||
);
|
||||
bb.Vec3 = new Vector3(
|
||||
bb.BottomBackRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32))
|
||||
);
|
||||
bb.Vec4 = new Vector3(
|
||||
bb.BottomBackLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44))
|
||||
);
|
||||
bb.Vec5 = new Vector3(
|
||||
bb.TopFrontLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56))
|
||||
);
|
||||
bb.Vec6 = new Vector3(
|
||||
bb.TopFrontRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68))
|
||||
);
|
||||
bb.Vec7 = new Vector3(
|
||||
bb.TopBackRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80))
|
||||
);
|
||||
bb.Vec8 = new Vector3(
|
||||
bb.TopBackLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92))
|
||||
@@ -149,6 +149,9 @@ public static class Msh02
|
||||
public List<Msh02Element> Elements { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 140 байт в начале файла
|
||||
/// </summary>
|
||||
public class Msh02Header
|
||||
{
|
||||
public BoundingBox BoundingBox { get; set; }
|
||||
@@ -172,15 +175,19 @@ public static class Msh02
|
||||
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 Vector3 Vec1 { get; set; }
|
||||
public Vector3 Vec2 { get; set; }
|
||||
public Vector3 Vec3 { get; set; }
|
||||
public Vector3 Vec4 { get; set; }
|
||||
public Vector3 Vec5 { get; set; }
|
||||
public Vector3 Vec6 { get; set; }
|
||||
public Vector3 Vec7 { get; set; }
|
||||
public Vector3 Vec8 { get; set; }
|
||||
public Vector3 BottomFrontLeft { get; set; }
|
||||
public Vector3 BottomFrontRight { get; set; }
|
||||
public Vector3 BottomBackRight { get; set; }
|
||||
public Vector3 BottomBackLeft { get; set; }
|
||||
public Vector3 TopFrontLeft { get; set; }
|
||||
public Vector3 TopFrontRight { get; set; }
|
||||
public Vector3 TopBackRight { get; set; }
|
||||
public Vector3 TopBackLeft { get; set; }
|
||||
}
|
||||
}
|
55
ParkanPlayground/Msh15.cs
Normal file
55
ParkanPlayground/Msh15.cs
Normal 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; }
|
||||
}
|
||||
}
|
@@ -15,6 +15,8 @@ public class MshConverter
|
||||
|
||||
var component01 = Msh01.ReadComponent(mshFs, mshNres);
|
||||
var component02 = Msh02.ReadComponent(mshFs, mshNres);
|
||||
var component15 = Msh15.ReadComponent(mshFs, mshNres);
|
||||
|
||||
var component0A = Msh0A.ReadComponent(mshFs, mshNres);
|
||||
var component07 = Msh07.ReadComponent(mshFs, mshNres);
|
||||
var component0D = Msh0D.ReadComponent(mshFs, mshNres);
|
||||
|
@@ -9,8 +9,8 @@ using ParkanPlayground;
|
||||
|
||||
var converter = new MshConverter();
|
||||
|
||||
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("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("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\\static.rlb\\2_MESH_s_stn_0_01.msh");
|
||||
|
@@ -302,7 +302,7 @@ IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
|
||||
- `4` - IShader
|
||||
- `5` - ITerrain
|
||||
- `6` - IGameObject (0x138)
|
||||
- `7` - IShadeConfig (у меня в папке с игрой его не оказалось)
|
||||
- `7` - IAtmosphereObject
|
||||
- `8` - ICamera
|
||||
- `9` - IQueue
|
||||
- `10` - IControl
|
||||
@@ -329,6 +329,7 @@ IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
|
||||
- `0x101` - 3DRender
|
||||
- `0x105` - NResFile
|
||||
- `0x106` - NResFileMetadata
|
||||
- `0x107` - 3DSound
|
||||
- `0x201` - IWizard
|
||||
- `0x202` - IItemManager
|
||||
- `0x203` - ICollManager
|
||||
|
@@ -6,8 +6,13 @@ public class ScrParser
|
||||
{
|
||||
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();
|
||||
|
||||
scrFile.Magic = fs.ReadInt32LittleEndian();
|
||||
|
@@ -4,8 +4,15 @@ public class VarsetParser
|
||||
{
|
||||
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);
|
||||
|
||||
List<VarsetItem> varsetItems = [];
|
||||
@@ -36,14 +43,16 @@ public class VarsetParser
|
||||
var openParenthesisIndex = 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");
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var arguments = line.Substring(openParenthesisIndex + 1, closeParenthesisIndex - openParenthesisIndex - 1);
|
||||
var arguments = line.Substring(openParenthesisIndex + 1,
|
||||
closeParenthesisIndex - openParenthesisIndex - 1);
|
||||
|
||||
var parts = arguments.Trim()
|
||||
.Split(',');
|
||||
@@ -65,4 +74,9 @@ public class VarsetParser
|
||||
|
||||
return varsetItems;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user