0
mirror of https://github.com/sampletext32/ParkanPlayground.git synced 2025-05-19 11:51:17 +03:00

Compare commits

...

10 Commits

Author SHA1 Message Date
bird_egop
f5bacc018c test 2025-04-12 16:42:44 +03:00
bird_egop
a6057bf072 unfuck 565 and 4444 textures 2025-03-11 04:36:05 +03:00
bird_egop
a419be1fce update NRES file with element count and element size, seen in ResTree .trf 2025-03-09 22:56:59 +03:00
bird_egop
8c4fc8f096 комментарии и дополнительные изыскания 2025-03-05 18:15:48 +03:00
bird_egop
135777a4c6 add varset view 2025-03-01 23:03:13 +03:00
bird_egop
76ef68635e scr reversed type 2025-03-01 22:45:15 +03:00
bird_egop
d7eb23e9e0 Implement SCR UI 2025-02-26 04:27:16 +03:00
bird_egop
b47a9aff5d Implement scr parsing 2025-02-25 01:51:28 +03:00
bird_egop
ba7c2afe2a unknown fixes 2025-02-24 23:35:55 +03:00
bird_egop
c50512ea52 add gameobjects view 2024-11-28 05:07:17 +03:00
40 changed files with 2486 additions and 123 deletions

View File

@ -12,7 +12,7 @@ public class ClanInfo
/// </summary> /// </summary>
public ClanType ClanType { get; set; } public ClanType ClanType { get; set; }
public string UnkString2 { get; set; } public string ScriptsString { get; set; }
public int UnknownClanPartCount { get; set; } public int UnknownClanPartCount { get; set; }
public List<UnknownClanTreeInfoPart> UnknownParts { get; set; } public List<UnknownClanTreeInfoPart> UnknownParts { get; set; }
@ -20,7 +20,7 @@ public class ClanInfo
/// Игра называет этот путь TreeName /// Игра называет этот путь TreeName
/// </summary> /// </summary>
public string ResearchNResPath { get; set; } public string ResearchNResPath { get; set; }
public int UnkInt3 { get; set; } public int Brains { get; set; }
public int AlliesMapCount { get; set; } public int AlliesMapCount { get; set; }
/// <summary> /// <summary>

View File

@ -7,18 +7,3 @@ public enum ClanType
AI = 2, AI = 2,
Neutral = 3 Neutral = 3
} }
public static class Extensions
{
public static string ToReadableString(this ClanType clanType)
{
return clanType switch
{
ClanType.Environment => $"Окружение ({clanType:D})",
ClanType.Player => $"Игрок ({clanType:D})",
ClanType.AI => $"AI ({clanType:D})",
ClanType.Neutral => $"Нейтральный ({clanType:D})",
_ => $"Неизвестный ({clanType:D})"
};
}
}

View File

@ -0,0 +1,28 @@
namespace MissionTmaLib;
public static class Extensions
{
public static string ToReadableString(this ClanType clanType)
{
return clanType switch
{
ClanType.Environment => $"Окружение ({clanType:D})",
ClanType.Player => $"Игрок ({clanType:D})",
ClanType.AI => $"AI ({clanType:D})",
ClanType.Neutral => $"Нейтральный ({clanType:D})",
_ => $"Неизвестный ({clanType:D})"
};
}
public static string ToReadableString(this GameObjectType type)
{
return type switch
{
GameObjectType.Building => $"Строение {type:D}",
GameObjectType.Warbot => $"Варбот {type:D}",
GameObjectType.Tree => $"Дерево {type:D}",
GameObjectType.Stone => $"Камень {type:D}",
_ => $"Неизвестный ({type:D})"
};
}
}

View File

@ -22,7 +22,7 @@ public class GameObjectInfo
/// </remarks> /// </remarks>
public int OwningClanIndex { get; set; } public int OwningClanIndex { get; set; }
public int UnknownInt3 { get; set; } public int Order { get; set; }
public Vector3 Position { get; set; } public Vector3 Position { get; set; }
public Vector3 Rotation { get; set; } public Vector3 Rotation { get; set; }

View File

@ -1,3 +1,3 @@
namespace MissionTmaLib; namespace MissionTmaLib;
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownInt2, float UnknownFloat, int UnknownInt3); public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);

View File

@ -0,0 +1,3 @@
namespace MissionTmaLib.Parsing;
public record MissionTma(ArealsFileData ArealData, ClansFileData ClansData, GameObjectsFileData GameObjectsData);

View File

@ -78,7 +78,7 @@ public class MissionTmaParser
// MISSIONS\SCRIPTS\default // MISSIONS\SCRIPTS\default
// MISSIONS\SCRIPTS\tut1_pl // MISSIONS\SCRIPTS\tut1_pl
// MISSIONS\SCRIPTS\tut1_en // MISSIONS\SCRIPTS\tut1_en
clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString(); clanTreeInfo.ScriptsString = fileStream.ReadLengthPrefixedString();
} }
if (2 < clanFeatureSet) if (2 < clanFeatureSet)
@ -118,7 +118,7 @@ public class MissionTmaParser
if (4 < clanFeatureSet) if (4 < clanFeatureSet)
{ {
clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian(); clanTreeInfo.Brains = fileStream.ReadInt32LittleEndian();
} }
if (5 < clanFeatureSet) if (5 < clanFeatureSet)
@ -185,7 +185,11 @@ public class MissionTmaParser
if (3 < gameObjectsFeatureSet) if (3 < gameObjectsFeatureSet)
{ {
gameObjectInfo.UnknownInt3 = fileStream.ReadInt32LittleEndian(); gameObjectInfo.Order = fileStream.ReadInt32LittleEndian();
if (gameObjectInfo.Type == GameObjectType.Building)
{
gameObjectInfo.Order += int.MaxValue;
}
} }
// читает 12 байт // читает 12 байт
@ -379,5 +383,3 @@ public class MissionTmaParser
); );
} }
} }
public record MissionTma(ArealsFileData ArealData, ClansFileData ClansData, GameObjectsFileData GameObjectsData);

View File

@ -19,10 +19,11 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
/// каждый элемент это 64 байта, /// каждый элемент это 64 байта,
/// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64) /// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64)
/// </summary> /// </summary>
/// <param name="FileType">[0..8] ASCII описание типа файла, например TEXM или MAT0</param> /// <param name="FileType">[0..4] ASCII описание типа файла, например TEXM или MAT0</param>
/// <param name="ElementCount">[4..8] Количество элементов в файле (если файл составной, например .trf) </param>
/// <param name="Magic1">[8..12] Неизвестное число</param> /// <param name="Magic1">[8..12] Неизвестное число</param>
/// <param name="FileLength">[12..16] Длина файла в байтах</param> /// <param name="FileLength">[12..16] Длина файла в байтах</param>
/// <param name="Magic2">[16..20] Неизвестное число</param> /// <param name="ElementSize">[16..20] Размер элемента в файле (если файл составной, например .trf) </param>
/// <param name="FileName">[20..40] ASCII имя файла</param> /// <param name="FileName">[20..40] ASCII имя файла</param>
/// <param name="Magic3">[40..44] Неизвестное число</param> /// <param name="Magic3">[40..44] Неизвестное число</param>
/// <param name="Magic4">[44..48] Неизвестное число</param> /// <param name="Magic4">[44..48] Неизвестное число</param>
@ -32,9 +33,10 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
/// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param> /// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param>
public record ListMetadataItem( public record ListMetadataItem(
string FileType, string FileType,
uint ElementCount,
int Magic1, int Magic1,
int FileLength, int FileLength,
int Magic2, int ElementSize,
string FileName, string FileName,
int Magic3, int Magic3,
int Magic4, int Magic4,

View File

@ -30,7 +30,7 @@ public class NResExporter
extension = ".bin"; extension = ".bin";
} }
var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{fileName}{extension}"); var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{archiveFile.FileType}_{fileName}{extension}");
File.WriteAllBytes(targetFilePath, buffer); File.WriteAllBytes(targetFilePath, buffer);
} }

View File

@ -48,13 +48,32 @@ public static class NResParser
for (int i = 0; i < header.FileCount; i++) for (int i = 0; i < header.FileCount; i++)
{ {
nResFs.ReadExactly(metaDataBuffer); nResFs.ReadExactly(metaDataBuffer);
var type = "";
for (int j = 0; j < 4; j++)
{
if (!char.IsLetterOrDigit((char)metaDataBuffer[j]))
{
type += metaDataBuffer[j]
.ToString("X2") + " ";
}
else
{
type += (char)metaDataBuffer[j];
}
}
var type2 = BinaryPrimitives.ReadUInt32LittleEndian(metaDataBuffer.Slice(4));
type = type.Trim();
elements.Add( elements.Add(
new ListMetadataItem( new ListMetadataItem(
FileType: Encoding.ASCII.GetString(metaDataBuffer[..8]).TrimEnd('\0'), FileType: type,
ElementCount: type2,
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]), Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]), FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]),
Magic2: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]), ElementSize: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'), FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'),
Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]), Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]),
Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]), Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]),

View File

@ -32,10 +32,11 @@
3. В конце файла есть метаданные. 3. В конце файла есть метаданные.
Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта. Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта.
+ [0..8] ASCII описание типа файла, например TEXM или MAT0 + [0..4] ASCII описание типа файла, например TEXM или MAT0
+ [4..8] Количество элементов в файле (если файл составной, например .trf)
+ [8..12] Неизвестное число + [8..12] Неизвестное число
+ [12..16] Длина файла в байтах + [12..16] Длина файла в байтах
+ [16..20] Неизвестное число + [16..20] Размер элемента в файле (если файл составной, например .trf)
+ [20..40] ASCII имя файла + [20..40] ASCII имя файла
+ [40..44] Неизвестное число + [40..44] Неизвестное число
+ [44..48] Неизвестное число + [44..48] Неизвестное число

View File

@ -53,6 +53,9 @@ public class App
serviceCollection.AddSingleton(new NResExplorerViewModel()); serviceCollection.AddSingleton(new NResExplorerViewModel());
serviceCollection.AddSingleton(new TexmExplorerViewModel()); serviceCollection.AddSingleton(new TexmExplorerViewModel());
serviceCollection.AddSingleton(new MissionTmaViewModel()); serviceCollection.AddSingleton(new MissionTmaViewModel());
serviceCollection.AddSingleton(new BinaryExplorerViewModel());
serviceCollection.AddSingleton(new ScrViewModel());
serviceCollection.AddSingleton(new VarsetViewModel());
var serviceProvider = serviceCollection.BuildServiceProvider(); var serviceProvider = serviceCollection.BuildServiceProvider();

View File

@ -0,0 +1,262 @@
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Text;
using System.Text.Json;
using ImGuiNET;
using NativeFileDialogSharp;
using NResUI.Abstractions;
using NResUI.Models;
namespace NResUI.ImGuiUI;
public class BinaryExplorerPanel : IImGuiPanel
{
private readonly BinaryExplorerViewModel _viewModel;
public BinaryExplorerPanel(BinaryExplorerViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnImGuiRender()
{
return;
if (ImGui.Begin("Binary Explorer"))
{
if (ImGui.Button("Open File"))
{
OpenFile();
}
if (_viewModel.HasFile)
{
ImGui.SameLine();
ImGui.Text(_viewModel.Path);
if (ImGui.Button("Сохранить регионы"))
{
File.WriteAllText("preset.json", JsonSerializer.Serialize(_viewModel.Regions));
}
ImGui.SameLine();
if (ImGui.Button("Загрузить регионы"))
{
_viewModel.Regions = JsonSerializer.Deserialize<List<Region>>(File.ReadAllText("preset.json"))!;
}
const int bytesPerRow = 16;
if (ImGui.BeginTable("HexTable", bytesPerRow + 1, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{
ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.WidthFixed);
for (var i = 0; i < bytesPerRow; i++)
{
ImGui.TableSetupColumn(i.ToString());
}
ImGui.TableHeadersRow();
for (int i = 0; i < _viewModel.Data.Length; i += bytesPerRow)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text($"{i:x8} ");
for (int j = 0; j < 16; j++)
{
var index = i + j;
ImGui.TableNextColumn();
if (index < _viewModel.Data.Length)
{
uint? regionColor = GetRegionColor(i + j);
if (regionColor is not null)
{
ImGui.PushStyleColor(ImGuiCol.Header, regionColor.Value);
}
if (ImGui.Selectable($"{_viewModel.Data[i + j]}##sel{i + j}", regionColor.HasValue))
{
HandleRegionSelect(i + j);
}
if (regionColor is not null)
{
ImGui.PopStyleColor();
}
}
else
{
ImGui.Text(" ");
}
}
}
ImGui.EndTable();
}
ImGui.SameLine();
ImGui.SetNextItemWidth(200f);
if (ImGui.ColorPicker4("NextColor", ref _viewModel.NextColor, ImGuiColorEditFlags.Float))
{
}
if (ImGui.BeginTable("Регионы", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{
ImGui.TableSetupColumn("Номер");
ImGui.TableSetupColumn("Старт");
ImGui.TableSetupColumn("Длина");
ImGui.TableSetupColumn("Значение");
ImGui.TableSetupColumn("Действия");
ImGui.TableHeadersRow();
for (int k = 0; k < _viewModel.Regions.Count; k++)
{
var region = _viewModel.Regions[k];
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(k.ToString());
ImGui.TableNextColumn();
ImGui.Text(region.Begin.ToString());
ImGui.TableNextColumn();
ImGui.Text(region.Length.ToString());
ImGui.TableNextColumn();
ImGui.Text(region.Value ?? "unknown");
ImGui.TableNextColumn();
if (ImGui.Button($"float##f{k}"))
{
region.Value = BinaryPrimitives.ReadSingleLittleEndian(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)])
.ToString("F2");
}
ImGui.SameLine();
if (ImGui.Button($"int##i{k}"))
{
region.Value = BinaryPrimitives.ReadInt32LittleEndian(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)])
.ToString();
}
ImGui.SameLine();
if (ImGui.Button($"ASCII##a{k}"))
{
region.Value = Encoding.ASCII.GetString(_viewModel.Data.AsSpan()[region.Begin..(region.Begin + region.Length)]);
}
ImGui.SameLine();
if (ImGui.Button($"raw##r{k}"))
{
region.Value = string.Join(
"",
_viewModel.Data[region.Begin..(region.Begin + region.Length)]
.Select(x => x.ToString("x2"))
);
}
}
ImGui.EndTable();
}
}
ImGui.End();
}
}
private uint? GetRegionColor(int index)
{
Region? inRegion = _viewModel.Regions.Find(x => x.Begin <= index && x.Begin + x.Length > index);
return inRegion?.Color;
}
private void HandleRegionSelect(int index)
{
Region? inRegion = _viewModel.Regions.FirstOrDefault(x => x.Begin <= index && index < x.Begin + x.Length);
if (inRegion is null)
{
// not in region
Region? prependRegion;
Region? appendRegion;
if ((prependRegion = _viewModel.Regions.Find(x => x.Begin + x.Length == index)) is not null)
{
if (prependRegion.Color == GetImGuiColor(_viewModel.NextColor))
{
prependRegion.Length += 1;
return;
}
}
if ((appendRegion = _viewModel.Regions.Find(x => x.Begin - 1 == index)) is not null)
{
if (appendRegion.Color == GetImGuiColor(_viewModel.NextColor))
{
appendRegion.Begin--;
appendRegion.Length += 1;
return;
}
}
var color = ImGui.ColorConvertFloat4ToU32(_viewModel.NextColor);
color = unchecked((uint) (0xFF << 24)) | color;
_viewModel.Regions.Add(
new Region()
{
Begin = index,
Length = 1,
Color = color
}
);
}
else
{
if (inRegion.Length == 1)
{
_viewModel.Regions.Remove(inRegion);
}
else
{
if (inRegion.Begin == index)
{
inRegion.Begin++;
inRegion.Length--;
}
if (inRegion.Begin + inRegion.Length - 1 == index)
{
inRegion.Length--;
}
}
}
}
private static uint GetImGuiColor(Vector4 vec)
{
var color = ImGui.ColorConvertFloat4ToU32(vec);
color = unchecked((uint) (0xFF << 24)) | color;
return color;
}
private bool OpenFile()
{
var result = Dialog.FileOpen("*");
if (result.IsOk)
{
var path = result.Path;
var bytes = File.ReadAllBytes(path);
_viewModel.HasFile = true;
_viewModel.Data = bytes;
_viewModel.Path = path;
}
return false;
}
}

View File

@ -1,29 +1,26 @@
using System.Numerics; using System.Numerics;
using ImGuiNET; using ImGuiNET;
using MissionTmaLib;
using MissionTmaLib.Parsing; using MissionTmaLib.Parsing;
using NativeFileDialogSharp; using NativeFileDialogSharp;
using NResLib; using NResLib;
using NResUI.Abstractions; using NResUI.Abstractions;
using NResUI.Models; using NResUI.Models;
using ScrLib;
using TexmLib; using TexmLib;
using VarsetLib;
namespace NResUI.ImGuiUI namespace NResUI.ImGuiUI
{ {
public class MainMenuBar : IImGuiPanel public class MainMenuBar(
NResExplorerViewModel nResExplorerViewModel,
TexmExplorerViewModel texmExplorerViewModel,
ScrViewModel scrViewModel,
MissionTmaViewModel missionTmaViewModel,
VarsetViewModel varsetViewModel,
MessageBoxModalPanel messageBox)
: IImGuiPanel
{ {
private readonly NResExplorerViewModel _nResExplorerViewModel;
private readonly TexmExplorerViewModel _texmExplorerViewModel;
private readonly MissionTmaViewModel _missionTmaViewModel;
private readonly MessageBoxModalPanel _messageBox;
public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox, MissionTmaViewModel missionTmaViewModel)
{
_nResExplorerViewModel = nResExplorerViewModel;
_texmExplorerViewModel = texmExplorerViewModel;
_messageBox = messageBox;
_missionTmaViewModel = missionTmaViewModel;
}
public void OnImGuiRender() public void OnImGuiRender()
{ {
if (ImGui.BeginMenuBar()) if (ImGui.BeginMenuBar())
@ -40,7 +37,7 @@ namespace NResUI.ImGuiUI
var parseResult = NResParser.ReadFile(path); var parseResult = NResParser.ReadFile(path);
_nResExplorerViewModel.SetParseResult(parseResult, path); nResExplorerViewModel.SetParseResult(parseResult, path);
Console.WriteLine("Read NRES"); Console.WriteLine("Read NRES");
} }
} }
@ -57,7 +54,7 @@ namespace NResUI.ImGuiUI
var parseResult = TexmParser.ReadFromStream(fs, path); var parseResult = TexmParser.ReadFromStream(fs, path);
_texmExplorerViewModel.SetParseResult(parseResult, path); texmExplorerViewModel.SetParseResult(parseResult, path);
Console.WriteLine("Read TEXM"); Console.WriteLine("Read TEXM");
} }
} }
@ -76,8 +73,8 @@ namespace NResUI.ImGuiUI
var parseResult = TexmParser.ReadFromStream(fs, path); var parseResult = TexmParser.ReadFromStream(fs, path);
_texmExplorerViewModel.SetParseResult(parseResult, path); texmExplorerViewModel.SetParseResult(parseResult, path);
Console.WriteLine("Read TEXM"); Console.WriteLine("Read TFNT TEXM");
} }
} }
@ -90,11 +87,41 @@ namespace NResUI.ImGuiUI
var path = result.Path; var path = result.Path;
var parseResult = MissionTmaParser.ReadFile(path); var parseResult = MissionTmaParser.ReadFile(path);
_missionTmaViewModel.SetParseResult(parseResult, path); missionTmaViewModel.SetParseResult(parseResult, path);
Console.WriteLine("Read TMA");
} }
} }
if (_nResExplorerViewModel.HasFile) if (ImGui.MenuItem("Open SCR Scripts File"))
{
var result = Dialog.FileOpen("scr");
if (result.IsOk)
{
var path = result.Path;
var parseResult = ScrParser.ReadFile(path);
scrViewModel.SetParseResult(parseResult, path);
Console.WriteLine("Read SCR");
}
}
if (ImGui.MenuItem("Open Varset File"))
{
var result = Dialog.FileOpen("var");
if (result.IsOk)
{
var path = result.Path;
var parseResult = VarsetParser.Parse(path);
varsetViewModel.Items = parseResult;
Console.WriteLine("Read VARSET");
}
}
if (nResExplorerViewModel.HasFile)
{ {
if (ImGui.MenuItem("Экспортировать NRes")) if (ImGui.MenuItem("Экспортировать NRes"))
{ {
@ -104,9 +131,9 @@ namespace NResUI.ImGuiUI
{ {
var path = result.Path; var path = result.Path;
NResExporter.Export(_nResExplorerViewModel.Archive!, path, _nResExplorerViewModel.Path!); NResExporter.Export(nResExplorerViewModel.Archive!, path, nResExplorerViewModel.Path!);
_messageBox.Show("Успешно экспортировано"); messageBox.Show("Успешно экспортировано");
} }
} }
} }
@ -117,6 +144,5 @@ namespace NResUI.ImGuiUI
ImGui.EndMenuBar(); ImGui.EndMenuBar();
} }
} }
} }
} }

View File

@ -21,6 +21,8 @@ public class MissionTmaExplorer : IImGuiPanel
var mission = _viewModel.Mission; var mission = _viewModel.Mission;
if (_viewModel.HasFile && mission is not null) if (_viewModel.HasFile && mission is not null)
{ {
ImGui.Columns(2);
ImGui.Text("Путь к файлу: "); ImGui.Text("Путь к файлу: ");
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(_viewModel.Path); ImGui.Text(_viewModel.Path);
@ -65,20 +67,32 @@ public class MissionTmaExplorer : IImGuiPanel
{ {
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(arealInfo.Coords[k].X.ToString("F2")); ImGui.Text(
arealInfo.Coords[k]
.X.ToString("F2")
);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(arealInfo.Coords[k].Y.ToString("F2")); ImGui.Text(
arealInfo.Coords[k]
.Y.ToString("F2")
);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(arealInfo.Coords[k].Z.ToString("F2")); ImGui.Text(
arealInfo.Coords[k]
.Z.ToString("F2")
);
} }
ImGui.EndTable(); ImGui.EndTable();
} }
ImGui.TreePop(); ImGui.TreePop();
} }
} }
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.TreePop(); ImGui.TreePop();
} }
@ -118,10 +132,10 @@ public class MissionTmaExplorer : IImGuiPanel
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(clanInfo.ClanType.ToReadableString()); ImGui.Text(clanInfo.ClanType.ToReadableString());
ImGui.Text("Неизвестная строка 1: "); ImGui.Text("Скрипты поведения (Mission Scripts): ");
Utils.ShowHint("Кажется это путь к файлу поведения (Behavior), но пока не понятно. Обычно пути соответствуют 2 файла."); Utils.ShowHint("Пути к файлам .scr и .fml описывающих настройку объектов и поведение AI");
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(clanInfo.UnkString2); ImGui.Text(clanInfo.ScriptsString);
if (clanInfo.UnknownParts.Count > 0) if (clanInfo.UnknownParts.Count > 0)
{ {
@ -162,14 +176,14 @@ public class MissionTmaExplorer : IImGuiPanel
ImGui.Text("Отсутствует неизвестная часть"); ImGui.Text("Отсутствует неизвестная часть");
} }
ImGui.Text("Путь к файлу .trf: "); ImGui.Text("Дерево исследований: ");
Utils.ShowHint("Не до конца понятно, что означает, вероятно это NRes с деревом исследований"); Utils.ShowHint("NRes с деревом исследований");
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(clanInfo.ResearchNResPath); ImGui.Text(clanInfo.ResearchNResPath);
ImGui.Text("Неизвестное число 3: "); ImGui.Text("Количество мозгов (Brains))): ");
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(clanInfo.UnkInt3.ToString()); ImGui.Text(clanInfo.Brains.ToString());
ImGui.Text("Матрица союзников"); ImGui.Text("Матрица союзников");
Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга"); Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга");
@ -185,7 +199,10 @@ public class MissionTmaExplorer : IImGuiPanel
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(alliesMapKey); ImGui.Text(alliesMapKey);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(clanInfo.AlliesMap[alliesMapKey].ToString()); ImGui.Text(
clanInfo.AlliesMap[alliesMapKey]
.ToString()
);
} }
ImGui.EndTable(); ImGui.EndTable();
@ -204,8 +221,189 @@ public class MissionTmaExplorer : IImGuiPanel
if (ImGui.TreeNodeEx("Объекты")) if (ImGui.TreeNodeEx("Объекты"))
{ {
var gameObjectsData = mission.GameObjectsData;
ImGui.Text("Фиче-сет: ");
Utils.ShowHint("Магическое число из файла, на основе которого игра читает разные секции об объекте");
ImGui.SameLine();
ImGui.Text(gameObjectsData.GameObjectsFeatureSet.ToString());
ImGui.Text("Кол-во объектов: ");
ImGui.SameLine();
ImGui.Text(gameObjectsData.GameObjectsCount.ToString());
for (var i = 0; i < gameObjectsData.GameObjectInfos.Count; i++)
{
var gameObjectInfo = gameObjectsData.GameObjectInfos[i];
if (ImGui.TreeNodeEx($"Объект {i} - {gameObjectInfo.DatString}"))
{
ImGui.Text("Тип объекта: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.Type.ToReadableString());
ImGui.Text("Неизвестные флаги: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.UnknownFlags.ToString("X8"));
ImGui.Text("Путь к файлу .dat: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.DatString);
ImGui.Text("Индекс владеющего клана: ");
Utils.ShowHint("-1 если объект никому не принадлежит");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.OwningClanIndex.ToString());
ImGui.Text("Порядковый номер: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.Order.ToString());
ImGui.Text("Вектор позиции: ");
ImGui.SameLine();
ImGui.Text($"{gameObjectInfo.Position.X} : {gameObjectInfo.Position.Y} : {gameObjectInfo.Position.Z}");
ImGui.Text("Вектор поворота: ");
ImGui.SameLine();
ImGui.Text($"{gameObjectInfo.Rotation.X} : {gameObjectInfo.Rotation.Y} : {gameObjectInfo.Rotation.Z}");
ImGui.Text("Вектор масштаба: ");
ImGui.SameLine();
ImGui.Text($"{gameObjectInfo.Scale.X} : {gameObjectInfo.Scale.Y} : {gameObjectInfo.Scale.Z}");
ImGui.Text("Неизвестная строка 2: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.UnknownString2);
ImGui.Text("Неизвестное число 4: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.UnknownInt4.ToString());
ImGui.Text("Неизвестное число 5: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.UnknownInt5.ToString());
ImGui.Text("Неизвестное число 6: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.UnknownInt6.ToString());
if (ImGui.TreeNodeEx("Настройки"))
{
ImGui.Text("Неиспользуемый заголовок: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.Settings.Unused.ToString());
ImGui.Text("Кол-во настроек: ");
ImGui.SameLine();
ImGui.Text(gameObjectInfo.Settings.SettingsCount.ToString());
ImGui.Text("0 - дробное число, 1 - целое число");
if (ImGui.BeginTable("Настройки", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{
ImGui.TableSetupColumn("Тип");
ImGui.TableSetupColumn("Число 1");
ImGui.TableSetupColumn("Число 2");
ImGui.TableSetupColumn("Число 3");
ImGui.TableSetupColumn("Название");
ImGui.TableHeadersRow();
for (var i1 = 0; i1 < gameObjectInfo.Settings.Settings.Count; i1++)
{
var setting = gameObjectInfo.Settings.Settings[i1];
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(setting.SettingType.ToString());
ImGui.TableNextColumn();
ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString());
ImGui.TableNextColumn();
ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString());
ImGui.TableNextColumn();
ImGui.Text(setting.SettingType == 0 ? setting.Unk1.AsFloat.ToString() : setting.Unk1.AsInt.ToString());
ImGui.TableNextColumn();
ImGui.Text(setting.Name);
}
ImGui.EndTable();
}
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.TreePop();
}
}
ImGui.Text("LAND строка: ");
Utils.ShowHint("Видимо это путь к настройкам поверхности");
ImGui.SameLine();
ImGui.Text(gameObjectsData.LandString);
ImGui.Text("Неизвестное число: ");
ImGui.SameLine();
ImGui.Text(gameObjectsData.UnknownInt.ToString());
ImGui.Text("Техническое описание: ");
ImGui.SameLine();
ImGui.Text(gameObjectsData.MissionTechDescription?.Replace((char)0xcd, '.') ?? "Отсутствует");
var lodeData = gameObjectsData.LodeData;
if (lodeData is not null)
{
ImGui.Text("Информация о LOD-ах");
ImGui.Text("Неиспользуемый заголовок: ");
ImGui.SameLine();
ImGui.Text(lodeData.Unused.ToString());
ImGui.Text("Кол-во LOD-ов: ");
ImGui.SameLine();
ImGui.Text(lodeData.LodeCount.ToString());
if (ImGui.BeginTable("Информация о лодах", 7, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{
ImGui.TableSetupColumn("X");
ImGui.TableSetupColumn("Y");
ImGui.TableSetupColumn("Z");
ImGui.TableSetupColumn("Число 1");
ImGui.TableSetupColumn("Флаги 2");
ImGui.TableSetupColumn("Число 3");
ImGui.TableSetupColumn("Число 4");
ImGui.TableHeadersRow();
for (var i1 = 0; i1 < lodeData.Lodes.Count; i1++)
{
var lode = lodeData.Lodes[i1];
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownVector.X.ToString());
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownVector.Y.ToString());
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownVector.Z.ToString());
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownInt1.ToString());
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownFlags2.ToString());
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownFloat.ToString());
ImGui.TableNextColumn();
ImGui.Text(lode.UnknownInt3.ToString());
}
ImGui.EndTable();
}
}
else
{
ImGui.Text("Информаия о LOD-ах отсутствует");
}
ImGui.TreePop();
}
ImGui.NextColumn();
ImGui.Text("Тут хочу сделать карту");
} }
else else
{ {

View File

@ -46,12 +46,13 @@ public class NResExplorerPanel : IImGuiPanel
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString()); ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
if (ImGui.BeginTable("content", 11)) if (ImGui.BeginTable("content", 12, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{ {
ImGui.TableSetupColumn("Тип файла"); ImGui.TableSetupColumn("Тип файла");
ImGui.TableSetupColumn("Кол-во элементов");
ImGui.TableSetupColumn("Magic1"); ImGui.TableSetupColumn("Magic1");
ImGui.TableSetupColumn("Длина файла в байтах"); ImGui.TableSetupColumn("Длина файла в байтах");
ImGui.TableSetupColumn("Magic2"); ImGui.TableSetupColumn("Размер элемента");
ImGui.TableSetupColumn("Имя файла"); ImGui.TableSetupColumn("Имя файла");
ImGui.TableSetupColumn("Magic3"); ImGui.TableSetupColumn("Magic3");
ImGui.TableSetupColumn("Magic4"); ImGui.TableSetupColumn("Magic4");
@ -68,6 +69,8 @@ public class NResExplorerPanel : IImGuiPanel
ImGui.TableNextColumn(); ImGui.TableNextColumn();
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.TableNextColumn();
ImGui.Text( ImGui.Text(
_viewModel.Archive.Files[i] _viewModel.Archive.Files[i]
.Magic1.ToString() .Magic1.ToString()
@ -80,7 +83,7 @@ public class NResExplorerPanel : IImGuiPanel
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text( ImGui.Text(
_viewModel.Archive.Files[i] _viewModel.Archive.Files[i]
.Magic2.ToString() .ElementSize.ToString()
); );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(_viewModel.Archive.Files[i].FileName); ImGui.Text(_viewModel.Archive.Files[i].FileName);

View File

@ -0,0 +1,144 @@
using ImGuiNET;
using NResUI.Abstractions;
using NResUI.Models;
using ScrLib;
namespace NResUI.ImGuiUI;
public class ScrExplorer : IImGuiPanel
{
private readonly ScrViewModel _viewModel;
public ScrExplorer(ScrViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnImGuiRender()
{
if (ImGui.Begin("SCR Explorer"))
{
var scr = _viewModel.Scr;
if (_viewModel.HasFile && scr is not null)
{
ImGui.Text("Магия: ");
Utils.ShowHint("тут всегда число 59 (0x3b) - это число известных игре скриптов");
ImGui.SameLine();
ImGui.Text(scr.Magic.ToString());
ImGui.Text("Кол-во секций: ");
ImGui.SameLine();
ImGui.Text(scr.EntryCount.ToString());
if (ImGui.TreeNodeEx("Секции"))
{
for (var i = 0; i < scr.Entries.Count; i++)
{
var entry = scr.Entries[i];
if (ImGui.TreeNodeEx($"Секция {i} - \"{entry.Title}\""))
{
ImGui.Text("Индекс: ");
ImGui.SameLine();
ImGui.Text(entry.Index.ToString());
ImGui.Text("Кол-во элементов: ");
ImGui.SameLine();
ImGui.Text(entry.InnerCount.ToString());
if (ImGui.BeginTable($"Элементы##{i:0000}", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{
ImGui.TableSetupColumn("Индекс встроенного скрипта");
ImGui.TableSetupColumn("UnkInner2");
ImGui.TableSetupColumn("UnkInner3");
ImGui.TableSetupColumn("Тип действия");
ImGui.TableSetupColumn("UnkInner5");
ImGui.TableSetupColumn("Кол-во аргументов");
ImGui.TableSetupColumn("Аргументы");
ImGui.TableSetupColumn("UnkInner7");
ImGui.TableHeadersRow();
for (int j = 0; j < entry.Inners.Count; j++)
{
var inner = entry.Inners[j];
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(inner.ScriptIndex.ToString());
if (inner.ScriptIndex == 2)
{
Utils.ShowHint("Первый аргумент - номер проблемы");
}
if (inner.ScriptIndex == 4)
{
Utils.ShowHint("Первый аргумент - номер проблемы");
}
if (inner.ScriptIndex == 8)
{
Utils.ShowHint("Установить dCurrentProblem стейт (VARSET:arg0)");
}
if (inner.ScriptIndex == 20)
{
Utils.ShowHint("Первый аргумент - номер проблемы");
}
ImGui.TableNextColumn();
ImGui.Text(inner.UnkInner2.ToString());
ImGui.TableNextColumn();
ImGui.Text(inner.UnkInner3.ToString());
ImGui.TableNextColumn();
ImGui.Text($"{(int) inner.Type}: {inner.Type:G}");
if (inner.Type == ScrEntryInnerType._0)
{
Utils.ShowHint("0 обязан иметь аргументы");
}
if (inner.Type == ScrEntryInnerType.CheckInternalState)
{
Utils.ShowHint("Для 5 вообще не нужны данные, игра проверяет внутренний стейт");
}
if (inner.Type == ScrEntryInnerType.SetVarsetValue)
{
Utils.ShowHint("В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3");
}
ImGui.TableNextColumn();
ImGui.Text(inner.UnkInner5.ToString());
ImGui.TableNextColumn();
ImGui.Text(inner.ArgumentsCount.ToString());
ImGui.TableNextColumn();
foreach (var argument in inner.Arguments)
{
if (ImGui.Button(argument.ToString()))
{
}
ImGui.SameLine();
}
ImGui.TableNextColumn();
ImGui.Text(inner.UnkInner7.ToString());
}
ImGui.EndTable();
}
ImGui.TreePop();
}
}
ImGui.TreePop();
}
}
else
{
ImGui.Text("SCR не открыт");
}
ImGui.End();
}
}
}

View File

@ -0,0 +1,57 @@
using ImGuiNET;
using NResUI.Abstractions;
using NResUI.Models;
namespace NResUI.ImGuiUI;
public class VarsetExplorerPanel : IImGuiPanel
{
private readonly VarsetViewModel _viewModel;
public VarsetExplorerPanel(VarsetViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnImGuiRender()
{
if (ImGui.Begin("VARSET Explorer"))
{
if (_viewModel.Items.Count == 0)
{
ImGui.Text("VARSET не загружен");
}
else
{
if (ImGui.BeginTable($"varset", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
{
ImGui.TableSetupColumn("Индекс");
ImGui.TableSetupColumn("Тип");
ImGui.TableSetupColumn("Имя");
ImGui.TableSetupColumn("Значение");
ImGui.TableHeadersRow();
for (int j = 0; j < _viewModel.Items.Count; j++)
{
var item = _viewModel.Items[j];
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(j.ToString());
ImGui.TableNextColumn();
ImGui.Text(item.Type);
ImGui.TableNextColumn();
ImGui.Text(item.Name);
ImGui.TableNextColumn();
ImGui.Text(item.Value);
}
ImGui.EndTable();
}
ImGui.TreePop();
}
ImGui.End();
}
}
}

View File

@ -0,0 +1,27 @@
using System.Numerics;
namespace NResUI.Models;
public class BinaryExplorerViewModel
{
public bool HasFile { get; set; }
public string? Error { get; set; }
public string Path { get; set; } = "";
public byte[] Data { get; set; } = [];
public List<Region> Regions { get; set; } = [];
public Vector4 NextColor;
}
public class Region
{
public int Begin { get; set; }
public int Length { get; set; }
public uint Color { get; set; }
public string? Value;
}

View File

@ -0,0 +1,20 @@
using ScrLib;
namespace NResUI.Models;
public class ScrViewModel
{
public bool HasFile { get; set; }
public string? Error { get; set; }
public ScrFile? Scr { get; set; }
public string? Path { get; set; }
public void SetParseResult(ScrFile scrFile, string path)
{
Scr = scrFile;
HasFile = true;
Path = path;
}
}

View File

@ -0,0 +1,8 @@
using VarsetLib;
namespace NResUI.Models;
public class VarsetViewModel
{
public List<VarsetItem> Items { get; set; } = [];
}

View File

@ -21,7 +21,9 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" /> <ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
<ProjectReference Include="..\NResLib\NResLib.csproj" /> <ProjectReference Include="..\NResLib\NResLib.csproj" />
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
<ProjectReference Include="..\TexmLib\TexmLib.csproj" /> <ProjectReference Include="..\TexmLib\TexmLib.csproj" />
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}"
EndProject EndProject
@ -23,6 +22,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{BAF212FE-A
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrLib", "ScrLib\ScrLib.csproj", "{C445359B-97D4-4432-9331-708B5A14887A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VarsetLib", "VarsetLib\VarsetLib.csproj", "{0EC800E2-1444-40D5-9EDD-93276F4D1FF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Visualisator", "Visualisator\Visualisator.csproj", "{667A7E03-5CAA-4591-9980-F6C722911A35}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X86Disassembler", "X86Disassembler\X86Disassembler.csproj", "{B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -65,5 +72,21 @@ Global
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU {773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU {773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.Build.0 = Release|Any CPU {773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.Build.0 = Release|Any CPU
{C445359B-97D4-4432-9331-708B5A14887A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C445359B-97D4-4432-9331-708B5A14887A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C445359B-97D4-4432-9331-708B5A14887A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C445359B-97D4-4432-9331-708B5A14887A}.Release|Any CPU.Build.0 = Release|Any CPU
{0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Release|Any CPU.Build.0 = Release|Any CPU
{667A7E03-5CAA-4591-9980-F6C722911A35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{667A7E03-5CAA-4591-9980-F6C722911A35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{667A7E03-5CAA-4591-9980-F6C722911A35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{667A7E03-5CAA-4591-9980-F6C722911A35}.Release|Any CPU.Build.0 = Release|Any CPU
{B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -0,0 +1,11 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAssemblyCodeArray_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa494e0aa381c41ff9484df33e5edb42535e00_003Fa1_003Fbc9d4e81_003FAssemblyCodeArray_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAssemblyCodeMemory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa494e0aa381c41ff9484df33e5edb42535e00_003F6e_003F09b667c6_003FAssemblyCodeMemory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADisassembler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa494e0aa381c41ff9484df33e5edb42535e00_003Fd4_003Fad0818f9_003FDisassembler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGL_002Egen_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F54e6df16dd99323ba9b0682ce5d5dac3648ccd10aafd29d5f3fad52b62bf3f75_003FGL_002Egen_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAssemblyCode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa494e0aa381c41ff9484df33e5edb42535e00_003F8c_003F9fe9bac2_003FIAssemblyCode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMatrix4x4_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fed6aa59cd75423c5b655901d6ec4fb4be48ab669fa6fb01b3a7a7f31be95_003FMatrix4x4_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMemory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FLocal_003FSymbols_003Fsrc_003Fdotnet_003Fruntime_003F5535e31a712343a63f5d7d796cd874e563e5ac14_003Fsrc_003Flibraries_003FSystem_002EPrivate_002ECoreLib_003Fsrc_003FSystem_003FMemory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASingle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc99a63bcf3d2a18c20ee19e58ac875ab1edf2a147c8b92ffeed185ab8a44b4_003FSingle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003Aud_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa494e0aa381c41ff9484df33e5edb42535e00_003F15_003F87bd9007_003Fud_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003Audis86_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa494e0aa381c41ff9484df33e5edb42535e00_003F95_003F953bbb0f_003Fudis86_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@ -9,6 +9,12 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NResLib\NResLib.csproj" /> <ProjectReference Include="..\NResLib\NResLib.csproj" />
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpDisasm" Version="1.1.11" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,41 +1,116 @@
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Numerics;
using NResLib; using System.Text.Json;
using ScrLib;
using SharpDisasm;
using VarsetLib;
var libFile = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\ui\\ui_back.lib";
var parseResult = NResParser.ReadFile(libFile); // 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
// );
if (parseResult.Error != null) // var items = VarsetParser.Parse(path);
// 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)
{ {
Console.WriteLine(parseResult.Error); var x = fs.ReadFloatLittleEndian();
return; 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;
} }
// var libFileName = new FileInfo(libFile).Name; 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));
// //
// if (Directory.Exists(libFileName)) // 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())
// { // {
// Directory.Delete(libFileName, true); // Console.WriteLine($"{instruction.PC - instruction.Offset}: {instruction}");
// }
// //
// var dir = Directory.CreateDirectory(libFileName); // new Instruction()
//
// byte[] copyBuffer = new byte[8192];
//
// foreach (var element in elements)
// { // {
// nResFs.Seek(element.OffsetInFile, SeekOrigin.Begin); // Action = instruction.Mnemonic.ToString(),
// using var destFs = new FileStream(Path.Combine(libFileName, element.FileName), FileMode.CreateNew); // Arguments = {instruction.Operands[0].ToString()}
// // };
// var totalCopiedBytes = 0;
// while (totalCopiedBytes < element.ItemLength)
// {
// var needReadBytes = Math.Min(element.ItemLength - totalCopiedBytes, copyBuffer.Length);
// var readBytes = nResFs.Read(copyBuffer, 0, needReadBytes);
//
// destFs.Write(copyBuffer, 0, readBytes);
//
// totalCopiedBytes += readBytes;
// }
// } // }
public class Instruction
{
public string Action { get; set; } = "";
public List<string> Arguments { get; set; } = [];
}

39
ScrLib/Extensions.cs Normal file
View File

@ -0,0 +1,39 @@
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);
}
}

View File

@ -0,0 +1,3 @@
namespace ScrLib;
public record ScrParseResult(ScrFile? Scr, string? Error);

60
ScrLib/ScrFile.cs Normal file
View File

@ -0,0 +1,60 @@
namespace ScrLib;
public class ScrFile
{
/// <summary>
/// тут всегда число 59 (0x3b) - это число известных игре скриптов
/// </summary>
public int Magic { get; set; }
public int EntryCount { get; set; }
public List<ScrEntry> Entries { get; set; }
}
public class ScrEntry
{
public string Title { get; set; }
public int Index { get; set; }
public int InnerCount { get; set; }
public List<ScrEntryInner> Inners { get; set; }
}
public class ScrEntryInner
{
/// <summary>
/// Номер скрипта в игре (это тех, которых 0x3b)
/// </summary>
public int ScriptIndex { get; set; }
public int UnkInner2 { get; set; }
public int UnkInner3 { get; set; }
public ScrEntryInnerType Type { get; set; }
public int UnkInner5 { get; set; }
public int ArgumentsCount { get; set; }
public List<int> Arguments { get; set; }
public int UnkInner7 { get; set; }
}
public enum ScrEntryInnerType
{
Unspecified = -1,
_0 = 0,
_1 = 1,
_2 = 2,
_3 = 3,
_4 = 4,
CheckInternalState = 5,
/// <summary>
/// В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3
/// </summary>
SetVarsetValue = 6,
}

9
ScrLib/ScrLib.csproj Normal file
View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

55
ScrLib/ScrParser.cs Normal file
View File

@ -0,0 +1,55 @@
namespace ScrLib;
public class ScrParser
{
public static ScrFile ReadFile(string filePath)
{
var fs = new FileStream(filePath, FileMode.Open);
var scrFile = new ScrFile();
scrFile.Magic = fs.ReadInt32LittleEndian();
scrFile.EntryCount = fs.ReadInt32LittleEndian();
scrFile.Entries = [];
for (var i = 0; i < scrFile.EntryCount; i++)
{
var entry = new ScrEntry();
entry.Title = fs.ReadLengthPrefixedString();
// тут игра дополнительно вычитывает ещё 1 байт, видимо как \0 для char*
fs.ReadByte();
entry.Index = fs.ReadInt32LittleEndian();
entry.InnerCount = fs.ReadInt32LittleEndian();
entry.Inners = [];
for (var i1 = 0; i1 < entry.InnerCount; i1++)
{
var entryInner = new ScrEntryInner();
entryInner.ScriptIndex = fs.ReadInt32LittleEndian();
entryInner.UnkInner2 = fs.ReadInt32LittleEndian();
entryInner.UnkInner3 = fs.ReadInt32LittleEndian();
entryInner.Type = (ScrEntryInnerType)fs.ReadInt32LittleEndian();
entryInner.UnkInner5 = fs.ReadInt32LittleEndian();
entryInner.ArgumentsCount = fs.ReadInt32LittleEndian();
entryInner.Arguments = [];
for (var i2 = 0; i2 < entryInner.ArgumentsCount; i2++)
{
entryInner.Arguments.Add(fs.ReadInt32LittleEndian());
}
entryInner.UnkInner7 = fs.ReadInt32LittleEndian();
entry.Inners.Add(entryInner);
}
scrFile.Entries.Add(entry);
}
return scrFile;
}
}

View File

@ -180,14 +180,20 @@ public class TexmFile
{ {
var rawPixel = span.Slice(i, 2); var rawPixel = span.Slice(i, 2);
var g = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31); // swap endianess
var b = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63); (rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
var r = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
result[i / 2 * 4 + 0] = r; var r = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
result[i / 2 * 4 + 1] = g; var g = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
result[i / 2 * 4 + 2] = b; var b = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
result[i / 2 * 4 + 3] = r;
result[i / 2 * 4 + 0] = (byte)(0xff - r);
result[i / 2 * 4 + 1] = (byte)(0xff - g);
result[i / 2 * 4 + 2] = (byte)(0xff - b);
result[i / 2 * 4 + 3] = 0xff;
// swap endianess back
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
} }
return result; return result;
@ -202,6 +208,9 @@ public class TexmFile
{ {
var rawPixel = span.Slice(i, 2); var rawPixel = span.Slice(i, 2);
// swap endianess
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17); var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17);
var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17); var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17);
var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17); var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17);
@ -211,6 +220,9 @@ public class TexmFile
result[i / 2 * 4 + 1] = g; result[i / 2 * 4 + 1] = g;
result[i / 2 * 4 + 2] = b; result[i / 2 * 4 + 2] = b;
result[i / 2 * 4 + 3] = a; result[i / 2 * 4 + 3] = a;
// swap endianess back
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
} }
return result; return result;
@ -247,16 +259,21 @@ public class TexmFile
for (var i = 0; i < span.Length; i += 4) for (var i = 0; i < span.Length; i += 4)
{ {
var rawPixel = span.Slice(i, 4); var rawPixel = span.Slice(i, 4);
// swap endianess back
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
var b = rawPixel[0]; var r = rawPixel[0];
var g = rawPixel[1]; var g = rawPixel[1];
var r = rawPixel[2]; var b = rawPixel[2];
var a = rawPixel[3]; var a = rawPixel[3];
result[i + 0] = r; result[i + 0] = r;
result[i + 1] = g; result[i + 1] = g;
result[i + 2] = b; result[i + 2] = b;
result[i + 3] = a; result[i + 3] = a;
// swap endianess back
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
} }
return result; return result;

3
VarsetLib/VarsetItem.cs Normal file
View File

@ -0,0 +1,3 @@
namespace VarsetLib;
public record VarsetItem(string Type, string Name, string Value);

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

68
VarsetLib/VarsetParser.cs Normal file
View File

@ -0,0 +1,68 @@
namespace VarsetLib;
public class VarsetParser
{
public static List<VarsetItem> Parse(string path)
{
FileStream fs = new FileStream(path, FileMode.Open);
var reader = new StreamReader(fs);
List<VarsetItem> varsetItems = [];
var lineIndex = 1;
while (!reader.EndOfStream)
{
var line = reader.ReadLine()!;
if (line.Length == 0)
{
lineIndex++;
continue;
}
if (line.StartsWith("//") || line.Trim().StartsWith("//"))
{
lineIndex++;
continue;
}
if (!line.StartsWith("VAR"))
{
Console.WriteLine($"Error on line: {lineIndex}! Not starting with VAR");
lineIndex++;
continue;
}
var openParenthesisIndex = line.IndexOf("(");
var closeParenthesisIndex = line.IndexOf(")");
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 parts = arguments.Trim()
.Split(',');
var type = parts[0]
.Trim();
var name = parts[1]
.Trim();
var value = parts[2]
.Trim();
var item = new VarsetItem(type, name, value);
varsetItems.Add(item);
lineIndex++;
}
return varsetItems;
}
}

180
Visualisator/Program.cs Normal file
View File

@ -0,0 +1,180 @@
// Configure window options
using System.Buffers.Binary;
using System.Numerics;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
public static class Program
{
private static string vertexShaderSource = @"
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 uMVP;
void main()
{
gl_Position = uMVP * vec4(aPos, 1.0);
gl_PointSize = 8.0;
}
";
private static string fragmentShaderSource = @"
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0, 1.0, 1.0, 1.0); // White points
}
";
private static IWindow? window;
private static GL? gl = null;
private static uint shaderProgram = uint.MaxValue;
private static uint vao = uint.MaxValue;
private static uint vbo = uint.MaxValue;
private static Matrix4x4 mvp = new Matrix4x4();
private static float[] points = [];
public static void Main(string[] args)
{
var path = "C:\\ParkanUnpacked\\Land.msh\\2_03 00 00 00_Land.bin";
var bytes = File.ReadAllBytes(path);
points = new float[bytes.Length / 4];
for (int i = 0; i < bytes.Length / 4; i++)
{
points[i] = BinaryPrimitives.ReadSingleBigEndian(bytes.AsSpan()[(i * 4)..]);
}
var options = WindowOptions.Default;
options.API = new GraphicsAPI(ContextAPI.OpenGL, new APIVersion(3, 3));
options.Title = "3D Points with Silk.NET";
window = Window.Create(options);
window.Load += OnLoad;
window.Render += OnRender;
window.Run();
}
unsafe static void OnLoad()
{
gl = window.CreateOpenGL();
// Compile shaders
uint vertexShader = gl.CreateShader(ShaderType.VertexShader);
gl.ShaderSource(vertexShader, vertexShaderSource);
gl.CompileShader(vertexShader);
CheckShaderCompile(vertexShader);
uint fragmentShader = gl.CreateShader(ShaderType.FragmentShader);
gl.ShaderSource(fragmentShader, fragmentShaderSource);
gl.CompileShader(fragmentShader);
CheckShaderCompile(fragmentShader);
// Create shader program
shaderProgram = gl.CreateProgram();
gl.AttachShader(shaderProgram, vertexShader);
gl.AttachShader(shaderProgram, fragmentShader);
gl.LinkProgram(shaderProgram);
CheckProgramLink(shaderProgram);
gl.DeleteShader(vertexShader);
gl.DeleteShader(fragmentShader);
// Create VAO and VBO
vao = gl.GenVertexArray();
gl.BindVertexArray(vao);
vbo = gl.GenBuffer();
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
unsafe
{
fixed (float* ptr = points)
{
gl.BufferData(
BufferTargetARB.ArrayBuffer,
(nuint) (points.Length * sizeof(float)),
ptr,
BufferUsageARB.StaticDraw
);
}
}
gl.VertexAttribPointer(
0,
3,
VertexAttribPointerType.Float,
false,
3 * sizeof(float),
(void*) 0
);
gl.EnableVertexAttribArray(0);
gl.BindVertexArray(0); // Unbind VAO
gl.Enable(EnableCap.DepthTest);
}
unsafe static void OnRender(double dt)
{
gl.ClearColor(
0.1f,
0.1f,
0.1f,
1.0f
);
gl.Clear((uint) (ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit));
// Set up MVP matrix
Matrix4x4 view = Matrix4x4.CreateLookAt(
new Vector3(100, 100, 40), // Camera position
Vector3.Zero, // Look at origin
Vector3.UnitY
); // Up direction
Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(
(float) Math.PI / 4f, // 45 degrees
(float) window.Size.X / window.Size.Y,
0.1f,
100f
);
mvp = view * projection;
gl.UseProgram(shaderProgram);
// Set MVP matrix (transpose=true for column-major format)
int mvpLocation = gl.GetUniformLocation(shaderProgram, "uMVP");
fixed (Matrix4x4* ptr = &mvp)
{
gl.UniformMatrix4(
mvpLocation,
1,
true,
(float*) ptr
);
}
gl.BindVertexArray(vao);
gl.DrawArrays(PrimitiveType.Points, 0, (uint) (points.Length / 3));
}
// Error checking methods
static void CheckShaderCompile(uint shader)
{
gl.GetShader(shader, ShaderParameterName.CompileStatus, out int success);
if (success == 0)
Console.WriteLine(gl.GetShaderInfoLog(shader));
}
static void CheckProgramLink(uint program)
{
gl.GetProgram(program, ProgramPropertyARB.LinkStatus, out int success);
if (success == 0)
Console.WriteLine(gl.GetProgramInfoLog(program));
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
<PackageReference Include="Silk.NET" Version="2.22.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
</ItemGroup>
</Project>

834
X86Disassembler/PEFormat.cs Normal file
View File

@ -0,0 +1,834 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace X86Disassembler
{
/// <summary>
/// Represents a Portable Executable (PE) file format parser
/// </summary>
public class PEFormat
{
// DOS Header constants
private const ushort DOS_SIGNATURE = 0x5A4D; // 'MZ'
private const uint PE_SIGNATURE = 0x00004550; // 'PE\0\0'
// Optional Header Magic values
private const ushort PE32_MAGIC = 0x10B; // 32-bit executable
private const ushort PE32PLUS_MAGIC = 0x20B; // 64-bit executable
// Section characteristics flags
private const uint IMAGE_SCN_CNT_CODE = 0x00000020; // Section contains code
private const uint IMAGE_SCN_MEM_EXECUTE = 0x20000000; // Section is executable
private const uint IMAGE_SCN_MEM_READ = 0x40000000; // Section is readable
private const uint IMAGE_SCN_MEM_WRITE = 0x80000000; // Section is writable
// Data directories
private const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0; // Export Directory
private const int IMAGE_DIRECTORY_ENTRY_IMPORT = 1; // Import Directory
private const int IMAGE_DIRECTORY_ENTRY_RESOURCE = 2; // Resource Directory
private const int IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3; // Exception Directory
private const int IMAGE_DIRECTORY_ENTRY_SECURITY = 4; // Security Directory
private const int IMAGE_DIRECTORY_ENTRY_BASERELOC = 5; // Base Relocation Table
private const int IMAGE_DIRECTORY_ENTRY_DEBUG = 6; // Debug Directory
private const int IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7; // Architecture Specific Data
private const int IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8; // RVA of GP
private const int IMAGE_DIRECTORY_ENTRY_TLS = 9; // TLS Directory
private const int IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10; // Load Configuration Directory
private const int IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11; // Bound Import Directory
private const int IMAGE_DIRECTORY_ENTRY_IAT = 12; // Import Address Table
private const int IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13; // Delay Load Import Descriptors
private const int IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14; // COM Runtime descriptor
// PE file data
private byte[] _fileData;
// Parsed headers
public DOSHeader DosHeader { get; private set; }
public FileHeader FileHeader { get; private set; }
public OptionalHeader OptionalHeader { get; private set; }
public List<SectionHeader> SectionHeaders { get; private set; }
public bool Is64Bit { get; private set; }
// Export and Import information
public ExportDirectory ExportDirectory { get; private set; }
public List<ExportedFunction> ExportedFunctions { get; private set; }
public List<ImportDescriptor> ImportDescriptors { get; private set; }
/// <summary>
/// Parses a PE file from the given byte array
/// </summary>
/// <param name="fileData">The raw file data</param>
public PEFormat(byte[] fileData)
{
_fileData = fileData;
SectionHeaders = new List<SectionHeader>();
ExportedFunctions = new List<ExportedFunction>();
ImportDescriptors = new List<ImportDescriptor>();
Parse();
}
/// <summary>
/// Parses the PE file structure
/// </summary>
private void Parse()
{
using (MemoryStream stream = new MemoryStream(_fileData))
using (BinaryReader reader = new BinaryReader(stream))
{
// Parse DOS header
DosHeader = ParseDOSHeader(reader);
// Move to PE header
reader.BaseStream.Seek(DosHeader.e_lfanew, SeekOrigin.Begin);
// Verify PE signature
uint peSignature = reader.ReadUInt32();
if (peSignature != PE_SIGNATURE)
{
throw new InvalidDataException("Invalid PE signature");
}
// Parse File Header
FileHeader = ParseFileHeader(reader);
// Parse Optional Header
OptionalHeader = ParseOptionalHeader(reader);
// Parse Section Headers
for (int i = 0; i < FileHeader.NumberOfSections; i++)
{
SectionHeaders.Add(ParseSectionHeader(reader));
}
// Parse Export Directory
if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_EXPORT &&
OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0)
{
ExportDirectory = ParseExportDirectory(reader, OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
ParseExportedFunctions(reader);
}
// Parse Import Descriptors
if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_IMPORT &&
OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress != 0)
{
ImportDescriptors = ParseImportDescriptors(reader, OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
}
}
}
/// <summary>
/// Parses the DOS header
/// </summary>
private DOSHeader ParseDOSHeader(BinaryReader reader)
{
DOSHeader header = new DOSHeader();
header.e_magic = reader.ReadUInt16();
if (header.e_magic != DOS_SIGNATURE)
{
throw new InvalidDataException("Invalid DOS signature (MZ)");
}
header.e_cblp = reader.ReadUInt16();
header.e_cp = reader.ReadUInt16();
header.e_crlc = reader.ReadUInt16();
header.e_cparhdr = reader.ReadUInt16();
header.e_minalloc = reader.ReadUInt16();
header.e_maxalloc = reader.ReadUInt16();
header.e_ss = reader.ReadUInt16();
header.e_sp = reader.ReadUInt16();
header.e_csum = reader.ReadUInt16();
header.e_ip = reader.ReadUInt16();
header.e_cs = reader.ReadUInt16();
header.e_lfarlc = reader.ReadUInt16();
header.e_ovno = reader.ReadUInt16();
header.e_res = new ushort[4];
for (int i = 0; i < 4; i++)
{
header.e_res[i] = reader.ReadUInt16();
}
header.e_oemid = reader.ReadUInt16();
header.e_oeminfo = reader.ReadUInt16();
header.e_res2 = new ushort[10];
for (int i = 0; i < 10; i++)
{
header.e_res2[i] = reader.ReadUInt16();
}
header.e_lfanew = reader.ReadUInt32();
return header;
}
/// <summary>
/// Parses the File header
/// </summary>
private FileHeader ParseFileHeader(BinaryReader reader)
{
FileHeader header = new FileHeader();
header.Machine = reader.ReadUInt16();
header.NumberOfSections = reader.ReadUInt16();
header.TimeDateStamp = reader.ReadUInt32();
header.PointerToSymbolTable = reader.ReadUInt32();
header.NumberOfSymbols = reader.ReadUInt32();
header.SizeOfOptionalHeader = reader.ReadUInt16();
header.Characteristics = reader.ReadUInt16();
return header;
}
/// <summary>
/// Parses the Optional header
/// </summary>
private OptionalHeader ParseOptionalHeader(BinaryReader reader)
{
OptionalHeader header = new OptionalHeader();
// Standard fields
header.Magic = reader.ReadUInt16();
// Determine if this is a PE32 or PE32+ file
Is64Bit = header.Magic == PE32PLUS_MAGIC;
header.MajorLinkerVersion = reader.ReadByte();
header.MinorLinkerVersion = reader.ReadByte();
header.SizeOfCode = reader.ReadUInt32();
header.SizeOfInitializedData = reader.ReadUInt32();
header.SizeOfUninitializedData = reader.ReadUInt32();
header.AddressOfEntryPoint = reader.ReadUInt32();
header.BaseOfCode = reader.ReadUInt32();
// PE32 has BaseOfData, PE32+ doesn't
if (!Is64Bit)
{
header.BaseOfData = reader.ReadUInt32();
}
// Windows-specific fields
if (Is64Bit)
{
header.ImageBase = reader.ReadUInt64();
}
else
{
header.ImageBase = reader.ReadUInt32();
}
header.SectionAlignment = reader.ReadUInt32();
header.FileAlignment = reader.ReadUInt32();
header.MajorOperatingSystemVersion = reader.ReadUInt16();
header.MinorOperatingSystemVersion = reader.ReadUInt16();
header.MajorImageVersion = reader.ReadUInt16();
header.MinorImageVersion = reader.ReadUInt16();
header.MajorSubsystemVersion = reader.ReadUInt16();
header.MinorSubsystemVersion = reader.ReadUInt16();
header.Win32VersionValue = reader.ReadUInt32();
header.SizeOfImage = reader.ReadUInt32();
header.SizeOfHeaders = reader.ReadUInt32();
header.CheckSum = reader.ReadUInt32();
header.Subsystem = reader.ReadUInt16();
header.DllCharacteristics = reader.ReadUInt16();
// Size fields differ between PE32 and PE32+
if (Is64Bit)
{
header.SizeOfStackReserve = reader.ReadUInt64();
header.SizeOfStackCommit = reader.ReadUInt64();
header.SizeOfHeapReserve = reader.ReadUInt64();
header.SizeOfHeapCommit = reader.ReadUInt64();
}
else
{
header.SizeOfStackReserve = reader.ReadUInt32();
header.SizeOfStackCommit = reader.ReadUInt32();
header.SizeOfHeapReserve = reader.ReadUInt32();
header.SizeOfHeapCommit = reader.ReadUInt32();
}
header.LoaderFlags = reader.ReadUInt32();
header.NumberOfRvaAndSizes = reader.ReadUInt32();
// Data directories
int numDirectories = (int)Math.Min(header.NumberOfRvaAndSizes, 16); // Maximum of 16 directories
header.DataDirectories = new DataDirectory[numDirectories];
for (int i = 0; i < numDirectories; i++)
{
DataDirectory dir = new DataDirectory();
dir.VirtualAddress = reader.ReadUInt32();
dir.Size = reader.ReadUInt32();
header.DataDirectories[i] = dir;
}
return header;
}
/// <summary>
/// Parses a section header
/// </summary>
private SectionHeader ParseSectionHeader(BinaryReader reader)
{
SectionHeader header = new SectionHeader();
// Read section name (8 bytes)
byte[] nameBytes = reader.ReadBytes(8);
// Convert to string, removing any null characters
header.Name = Encoding.ASCII.GetString(nameBytes).TrimEnd('\0');
header.VirtualSize = reader.ReadUInt32();
header.VirtualAddress = reader.ReadUInt32();
header.SizeOfRawData = reader.ReadUInt32();
header.PointerToRawData = reader.ReadUInt32();
header.PointerToRelocations = reader.ReadUInt32();
header.PointerToLinenumbers = reader.ReadUInt32();
header.NumberOfRelocations = reader.ReadUInt16();
header.NumberOfLinenumbers = reader.ReadUInt16();
header.Characteristics = reader.ReadUInt32();
return header;
}
/// <summary>
/// Parses the Export Directory
/// </summary>
private ExportDirectory ParseExportDirectory(BinaryReader reader, uint rva)
{
ExportDirectory directory = new ExportDirectory();
reader.BaseStream.Seek(RvaToOffset(rva), SeekOrigin.Begin);
directory.Characteristics = reader.ReadUInt32();
directory.TimeDateStamp = reader.ReadUInt32();
directory.MajorVersion = reader.ReadUInt16();
directory.MinorVersion = reader.ReadUInt16();
directory.Name = reader.ReadUInt32();
directory.Base = reader.ReadUInt32();
directory.NumberOfFunctions = reader.ReadUInt32();
directory.NumberOfNames = reader.ReadUInt32();
directory.AddressOfFunctions = reader.ReadUInt32();
directory.AddressOfNames = reader.ReadUInt32();
directory.AddressOfNameOrdinals = reader.ReadUInt32();
// Read the DLL name
uint dllNameRVA = directory.Name;
reader.BaseStream.Seek(RvaToOffset(dllNameRVA), SeekOrigin.Begin);
byte[] dllNameBytes = reader.ReadBytes(256);
directory.DllName = Encoding.ASCII.GetString(dllNameBytes).TrimEnd('\0');
return directory;
}
/// <summary>
/// Parses the Import Descriptors
/// </summary>
private List<ImportDescriptor> ParseImportDescriptors(BinaryReader reader, uint rva)
{
List<ImportDescriptor> descriptors = new List<ImportDescriptor>();
try
{
reader.BaseStream.Seek(RvaToOffset(rva), SeekOrigin.Begin);
while (true)
{
ImportDescriptor descriptor = new ImportDescriptor();
descriptor.OriginalFirstThunk = reader.ReadUInt32();
descriptor.TimeDateStamp = reader.ReadUInt32();
descriptor.ForwarderChain = reader.ReadUInt32();
descriptor.Name = reader.ReadUInt32();
descriptor.FirstThunk = reader.ReadUInt32();
// Check if we've reached the end of the import descriptors
if (descriptor.OriginalFirstThunk == 0 && descriptor.Name == 0 && descriptor.FirstThunk == 0)
{
break;
}
try
{
// Read the DLL name
uint dllNameOffset = RvaToOffset(descriptor.Name);
reader.BaseStream.Seek(dllNameOffset, SeekOrigin.Begin);
List<byte> nameBytes = new List<byte>();
byte b;
while ((b = reader.ReadByte()) != 0)
{
nameBytes.Add(b);
}
descriptor.DllName = Encoding.ASCII.GetString(nameBytes.ToArray());
// Read the imported functions (use FirstThunk if OriginalFirstThunk is 0)
uint thunkRVA = descriptor.OriginalFirstThunk != 0 ? descriptor.OriginalFirstThunk : descriptor.FirstThunk;
if (thunkRVA != 0)
{
try
{
uint thunkOffset = RvaToOffset(thunkRVA);
uint currentThunkOffset = thunkOffset;
while (true)
{
reader.BaseStream.Seek(currentThunkOffset, SeekOrigin.Begin);
uint thunk = reader.ReadUInt32();
if (thunk == 0)
{
break;
}
ImportedFunction function = new ImportedFunction();
function.ThunkRVA = thunkRVA + (currentThunkOffset - thunkOffset);
// Check if the function is imported by ordinal
if ((thunk & 0x80000000) != 0)
{
function.IsOrdinal = true;
function.Ordinal = (ushort)(thunk & 0xFFFF);
function.Name = $"Ordinal_{function.Ordinal}";
}
else
{
// Read the function name and hint
try
{
uint nameOffset = RvaToOffset(thunk);
reader.BaseStream.Seek(nameOffset, SeekOrigin.Begin);
function.Hint = reader.ReadUInt16();
List<byte> funcNameBytes = new List<byte>();
byte c;
while ((c = reader.ReadByte()) != 0)
{
funcNameBytes.Add(c);
}
function.Name = Encoding.ASCII.GetString(funcNameBytes.ToArray());
}
catch (Exception)
{
function.Name = $"Function_at_{thunk:X8}";
}
}
descriptor.Functions.Add(function);
currentThunkOffset += 4; // Move to the next thunk
}
}
catch (Exception)
{
// Skip this thunk table if there's an error
}
}
}
catch (Exception)
{
// If we can't read the DLL name, use a placeholder
descriptor.DllName = $"DLL_at_{descriptor.Name:X8}";
}
descriptors.Add(descriptor);
}
}
catch (Exception)
{
// Return whatever descriptors we've managed to parse
}
return descriptors;
}
/// <summary>
/// Parses the exported functions using the export directory information
/// </summary>
private void ParseExportedFunctions(BinaryReader reader)
{
if (ExportDirectory == null)
{
return;
}
// Read the array of function addresses (RVAs)
uint[] functionRVAs = new uint[ExportDirectory.NumberOfFunctions];
reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfFunctions), SeekOrigin.Begin);
for (int i = 0; i < ExportDirectory.NumberOfFunctions; i++)
{
functionRVAs[i] = reader.ReadUInt32();
}
// Read the array of name RVAs
uint[] nameRVAs = new uint[ExportDirectory.NumberOfNames];
reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfNames), SeekOrigin.Begin);
for (int i = 0; i < ExportDirectory.NumberOfNames; i++)
{
nameRVAs[i] = reader.ReadUInt32();
}
// Read the array of name ordinals
ushort[] nameOrdinals = new ushort[ExportDirectory.NumberOfNames];
reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfNameOrdinals), SeekOrigin.Begin);
for (int i = 0; i < ExportDirectory.NumberOfNames; i++)
{
nameOrdinals[i] = reader.ReadUInt16();
}
// Create a dictionary to map ordinals to names
Dictionary<ushort, string> ordinalToName = new Dictionary<ushort, string>();
for (int i = 0; i < ExportDirectory.NumberOfNames; i++)
{
// Read the function name
reader.BaseStream.Seek(RvaToOffset(nameRVAs[i]), SeekOrigin.Begin);
List<byte> nameBytes = new List<byte>();
byte b;
while ((b = reader.ReadByte()) != 0)
{
nameBytes.Add(b);
}
string name = Encoding.ASCII.GetString(nameBytes.ToArray());
// Map the ordinal to the name
ordinalToName[nameOrdinals[i]] = name;
}
// Create the exported functions
for (ushort i = 0; i < ExportDirectory.NumberOfFunctions; i++)
{
uint functionRVA = functionRVAs[i];
if (functionRVA == 0)
{
continue; // Skip empty entries
}
ExportedFunction function = new ExportedFunction();
function.Ordinal = (ushort)(i + ExportDirectory.Base);
function.Address = functionRVA;
// Check if this function has a name
if (ordinalToName.TryGetValue(i, out string name))
{
function.Name = name;
}
else
{
function.Name = $"Ordinal_{function.Ordinal}";
}
// Check if this is a forwarder
uint exportDirStart = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
uint exportDirEnd = exportDirStart + OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
if (functionRVA >= exportDirStart && functionRVA < exportDirEnd)
{
function.IsForwarder = true;
// Read the forwarder string
reader.BaseStream.Seek(RvaToOffset(functionRVA), SeekOrigin.Begin);
List<byte> forwarderBytes = new List<byte>();
byte b;
while ((b = reader.ReadByte()) != 0)
{
forwarderBytes.Add(b);
}
function.ForwarderName = Encoding.ASCII.GetString(forwarderBytes.ToArray());
}
ExportedFunctions.Add(function);
}
}
/// <summary>
/// Gets the raw data for a specific section
/// </summary>
/// <param name="sectionIndex">Index of the section</param>
/// <returns>Byte array containing the section data</returns>
public byte[] GetSectionData(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= SectionHeaders.Count)
{
throw new ArgumentOutOfRangeException(nameof(sectionIndex));
}
SectionHeader section = SectionHeaders[sectionIndex];
byte[] sectionData = new byte[section.SizeOfRawData];
Array.Copy(_fileData, section.PointerToRawData, sectionData, 0, section.SizeOfRawData);
return sectionData;
}
/// <summary>
/// Gets the raw data for a section by name
/// </summary>
/// <param name="sectionName">Name of the section</param>
/// <returns>Byte array containing the section data</returns>
public byte[] GetSectionData(string sectionName)
{
for (int i = 0; i < SectionHeaders.Count; i++)
{
if (SectionHeaders[i].Name == sectionName)
{
return GetSectionData(i);
}
}
throw new ArgumentException($"Section '{sectionName}' not found");
}
/// <summary>
/// Checks if a section contains code
/// </summary>
/// <param name="section">The section to check</param>
/// <returns>True if the section contains code, false otherwise</returns>
public bool IsSectionContainsCode(SectionHeader section)
{
return (section.Characteristics & IMAGE_SCN_CNT_CODE) != 0 ||
(section.Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
}
/// <summary>
/// Gets all code sections
/// </summary>
/// <returns>List of section indices that contain code</returns>
public List<int> GetCodeSections()
{
List<int> codeSections = new List<int>();
for (int i = 0; i < SectionHeaders.Count; i++)
{
if (IsSectionContainsCode(SectionHeaders[i]))
{
codeSections.Add(i);
}
}
return codeSections;
}
/// <summary>
/// Converts a Relative Virtual Address (RVA) to a file offset
/// </summary>
/// <param name="rva">The RVA to convert</param>
/// <returns>The corresponding file offset</returns>
public uint RvaToOffset(uint rva)
{
if (rva == 0)
{
return 0;
}
foreach (var section in SectionHeaders)
{
// Check if the RVA is within this section
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
{
// Calculate the offset within the section
uint offsetInSection = rva - section.VirtualAddress;
// Make sure we don't exceed the raw data size
if (offsetInSection < section.SizeOfRawData)
{
return section.PointerToRawData + offsetInSection;
}
}
}
// If the RVA is not within any section, it might be in the headers
if (rva < OptionalHeader.SizeOfHeaders)
{
return rva;
}
throw new ArgumentException($"RVA {rva:X8} is not within any section");
}
}
#region PE Format Structures
/// <summary>
/// DOS Header structure
/// </summary>
public class DOSHeader
{
public ushort e_magic; // Magic number ("MZ")
public ushort e_cblp; // Bytes on last page of file
public ushort e_cp; // Pages in file
public ushort e_crlc; // Relocations
public ushort e_cparhdr; // Size of header in paragraphs
public ushort e_minalloc; // Minimum extra paragraphs needed
public ushort e_maxalloc; // Maximum extra paragraphs needed
public ushort e_ss; // Initial (relative) SS value
public ushort e_sp; // Initial SP value
public ushort e_csum; // Checksum
public ushort e_ip; // Initial IP value
public ushort e_cs; // Initial (relative) CS value
public ushort e_lfarlc; // File address of relocation table
public ushort e_ovno; // Overlay number
public ushort[] e_res; // Reserved words
public ushort e_oemid; // OEM identifier
public ushort e_oeminfo; // OEM information
public ushort[] e_res2; // Reserved words
public uint e_lfanew; // File address of new exe header
}
/// <summary>
/// File Header structure
/// </summary>
public class FileHeader
{
public ushort Machine; // Target machine type
public ushort NumberOfSections; // Number of sections
public uint TimeDateStamp; // Time stamp
public uint PointerToSymbolTable; // File offset of symbol table
public uint NumberOfSymbols; // Number of symbols
public ushort SizeOfOptionalHeader; // Size of optional header
public ushort Characteristics; // Characteristics
}
/// <summary>
/// Optional Header structure
/// </summary>
public class OptionalHeader
{
// Standard fields
public ushort Magic; // Magic number (PE32 or PE32+)
public byte MajorLinkerVersion; // Major linker version
public byte MinorLinkerVersion; // Minor linker version
public uint SizeOfCode; // Size of code section
public uint SizeOfInitializedData; // Size of initialized data
public uint SizeOfUninitializedData; // Size of uninitialized data
public uint AddressOfEntryPoint; // Entry point RVA
public uint BaseOfCode; // Base of code section
public uint BaseOfData; // Base of data section (PE32 only)
// Windows-specific fields
public dynamic ImageBase; // Preferred image base (uint for PE32, ulong for PE32+)
public uint SectionAlignment; // Section alignment
public uint FileAlignment; // File alignment
public ushort MajorOperatingSystemVersion; // Major OS version
public ushort MinorOperatingSystemVersion; // Minor OS version
public ushort MajorImageVersion; // Major image version
public ushort MinorImageVersion; // Minor image version
public ushort MajorSubsystemVersion; // Major subsystem version
public ushort MinorSubsystemVersion; // Minor subsystem version
public uint Win32VersionValue; // Win32 version value
public uint SizeOfImage; // Size of image
public uint SizeOfHeaders; // Size of headers
public uint CheckSum; // Checksum
public ushort Subsystem; // Subsystem
public ushort DllCharacteristics; // DLL characteristics
public dynamic SizeOfStackReserve; // Size of stack reserve (uint for PE32, ulong for PE32+)
public dynamic SizeOfStackCommit; // Size of stack commit (uint for PE32, ulong for PE32+)
public dynamic SizeOfHeapReserve; // Size of heap reserve (uint for PE32, ulong for PE32+)
public dynamic SizeOfHeapCommit; // Size of heap commit (uint for PE32, ulong for PE32+)
public uint LoaderFlags; // Loader flags
public uint NumberOfRvaAndSizes; // Number of data directories
// Data directories
public DataDirectory[] DataDirectories; // Data directories
}
/// <summary>
/// Data Directory structure
/// </summary>
public class DataDirectory
{
public uint VirtualAddress; // RVA of the directory
public uint Size; // Size of the directory
}
/// <summary>
/// Section Header structure
/// </summary>
public class SectionHeader
{
public string Name; // Section name
public uint VirtualSize; // Virtual size
public uint VirtualAddress; // Virtual address (RVA)
public uint SizeOfRawData; // Size of raw data
public uint PointerToRawData; // File pointer to raw data
public uint PointerToRelocations; // File pointer to relocations
public uint PointerToLinenumbers; // File pointer to line numbers
public ushort NumberOfRelocations; // Number of relocations
public ushort NumberOfLinenumbers; // Number of line numbers
public uint Characteristics; // Characteristics
}
#endregion
#region Export and Import Structures
/// <summary>
/// Export Directory structure
/// </summary>
public class ExportDirectory
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public uint Name; // RVA to the DLL name
public string DllName; // Actual DLL name
public uint Base; // Ordinal base
public uint NumberOfFunctions; // Number of exported functions
public uint NumberOfNames; // Number of exported names
public uint AddressOfFunctions; // RVA to function addresses
public uint AddressOfNames; // RVA to function names
public uint AddressOfNameOrdinals; // RVA to ordinals
}
/// <summary>
/// Represents an exported function
/// </summary>
public class ExportedFunction
{
public string Name; // Function name
public uint Address; // Function RVA
public ushort Ordinal; // Function ordinal
public bool IsForwarder; // True if this is a forwarder
public string ForwarderName; // Name of the forwarded function (if IsForwarder is true)
}
/// <summary>
/// Import Descriptor structure
/// </summary>
public class ImportDescriptor
{
public uint OriginalFirstThunk; // RVA to Import Lookup Table
public uint TimeDateStamp;
public uint ForwarderChain;
public uint Name; // RVA to the DLL name
public string DllName; // Actual DLL name
public uint FirstThunk; // RVA to Import Address Table
public List<ImportedFunction> Functions; // List of imported functions
public ImportDescriptor()
{
Functions = new List<ImportedFunction>();
}
}
/// <summary>
/// Represents an imported function
/// </summary>
public class ImportedFunction
{
public bool IsOrdinal; // True if imported by ordinal
public ushort Ordinal; // Ordinal value (if IsOrdinal is true)
public string Name; // Function name (if IsOrdinal is false)
public ushort Hint; // Hint value (if IsOrdinal is false)
public uint ThunkRVA; // RVA in the Import Address Table
}
#endregion
}

153
X86Disassembler/Program.cs Normal file
View File

@ -0,0 +1,153 @@
using System;
using System.IO;
namespace X86Disassembler
{
internal class Program
{
// Path to the DLL file to disassemble
private const string DllPath = @"C:\Windows\SysWOW64\msvcrt.dll"; // Example path, replace with your target DLL
static void Main(string[] args)
{
Console.WriteLine("X86 Disassembler and Decompiler");
Console.WriteLine("--------------------------------");
Console.WriteLine($"Loading file: {DllPath}");
// Load the DLL file
byte[] binaryData = File.ReadAllBytes(DllPath);
Console.WriteLine($"Successfully loaded {DllPath}");
Console.WriteLine($"File size: {binaryData.Length} bytes");
// Parse the PE format
Console.WriteLine("\nParsing PE format...");
PEFormat peFile = new PEFormat(binaryData);
// Display basic PE information
DisplayPEInfo(peFile);
// Display exported functions
DisplayExportedFunctions(peFile);
// Display imported functions
DisplayImportedFunctions(peFile);
// Find code sections for disassembly
var codeSections = peFile.GetCodeSections();
Console.WriteLine($"\nFound {codeSections.Count} code section(s):");
foreach (int sectionIndex in codeSections)
{
var section = peFile.SectionHeaders[sectionIndex];
Console.WriteLine($" - {section.Name}: Size={section.SizeOfRawData} bytes, RVA=0x{section.VirtualAddress:X8}");
// Get the section data for disassembly
byte[] sectionData = peFile.GetSectionData(sectionIndex);
// TODO: Implement disassembling logic here
// This is where we would pass the section data to our disassembler
}
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
private static void DisplayPEInfo(PEFormat peFile)
{
Console.WriteLine("\nPE File Information:");
Console.WriteLine($"Architecture: {(peFile.Is64Bit ? "64-bit" : "32-bit")}");
Console.WriteLine($"Entry Point: 0x{peFile.OptionalHeader.AddressOfEntryPoint:X8}");
Console.WriteLine($"Image Base: 0x{peFile.OptionalHeader.ImageBase:X}");
Console.WriteLine($"Number of Sections: {peFile.FileHeader.NumberOfSections}");
// Display section information
Console.WriteLine("\nSections:");
for (int i = 0; i < peFile.SectionHeaders.Count; i++)
{
var section = peFile.SectionHeaders[i];
string flags = "";
if ((section.Characteristics & 0x00000020) != 0) flags += "Code "; // IMAGE_SCN_CNT_CODE
if ((section.Characteristics & 0x20000000) != 0) flags += "Exec "; // IMAGE_SCN_MEM_EXECUTE
if ((section.Characteristics & 0x40000000) != 0) flags += "Read "; // IMAGE_SCN_MEM_READ
if ((section.Characteristics & 0x80000000) != 0) flags += "Write"; // IMAGE_SCN_MEM_WRITE
Console.WriteLine($" {i}: {section.Name,-8} VA=0x{section.VirtualAddress:X8} Size={section.SizeOfRawData,-8} [{flags}]");
}
}
private static void DisplayExportedFunctions(PEFormat peFile)
{
if (peFile.ExportDirectory == null)
{
Console.WriteLine("\nNo exported functions found.");
return;
}
Console.WriteLine("\nExported Functions:");
Console.WriteLine($"DLL Name: {peFile.ExportDirectory.DllName}");
Console.WriteLine($"Number of Functions: {peFile.ExportDirectory.NumberOfFunctions}");
Console.WriteLine($"Number of Names: {peFile.ExportDirectory.NumberOfNames}");
// Display the first 10 exported functions (if any)
int count = Math.Min(10, peFile.ExportedFunctions.Count);
for (int i = 0; i < count; i++)
{
var function = peFile.ExportedFunctions[i];
Console.WriteLine($" {i}: {function.Name} (Ordinal={function.Ordinal}, RVA=0x{function.Address:X8})");
}
if (peFile.ExportedFunctions.Count > 10)
{
Console.WriteLine($" ... and {peFile.ExportedFunctions.Count - 10} more");
}
}
private static void DisplayImportedFunctions(PEFormat peFile)
{
if (peFile.ImportDescriptors.Count == 0)
{
Console.WriteLine("\nNo imported functions found.");
return;
}
Console.WriteLine("\nImported Functions:");
Console.WriteLine($"Number of Imported DLLs: {peFile.ImportDescriptors.Count}");
// Display the first 5 imported DLLs and their functions
int dllCount = Math.Min(5, peFile.ImportDescriptors.Count);
for (int i = 0; i < dllCount; i++)
{
var descriptor = peFile.ImportDescriptors[i];
Console.WriteLine($" DLL: {descriptor.DllName}");
// Display the first 5 functions from this DLL
int funcCount = Math.Min(5, descriptor.Functions.Count);
for (int j = 0; j < funcCount; j++)
{
var function = descriptor.Functions[j];
if (function.IsOrdinal)
{
Console.WriteLine($" {j}: Ordinal {function.Ordinal}");
}
else
{
Console.WriteLine($" {j}: {function.Name} (Hint={function.Hint})");
}
}
if (descriptor.Functions.Count > 5)
{
Console.WriteLine($" ... and {descriptor.Functions.Count - 5} more");
}
}
if (peFile.ImportDescriptors.Count > 5)
{
Console.WriteLine($" ... and {peFile.ImportDescriptors.Count - 5} more DLLs");
}
}
}
}

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>