mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-09-13 18:20:30 +03:00
Compare commits
15 Commits
x86-disass
...
windows-re
Author | SHA1 | Date | |
---|---|---|---|
![]() |
be60d8d72f | ||
![]() |
f2bed4b141 | ||
![]() |
7f0246f996 | ||
![]() |
055694a4b4 | ||
![]() |
fca052365f | ||
![]() |
5c52ab2b2b | ||
![]() |
77e7f7652c | ||
![]() |
35af4da326 | ||
![]() |
4b1c4bf3aa | ||
![]() |
4ea756a1a4 | ||
![]() |
b9e15541c5 | ||
![]() |
67c9020b96 | ||
![]() |
476017e9c1 | ||
![]() |
ee77738713 | ||
![]() |
8e31f43abf |
@@ -1,23 +0,0 @@
|
|||||||
when creating or edditing code, adjust namespace declaration style to oneliner, e.g. "namespace MyNamespace;".
|
|
||||||
|
|
||||||
always separate usings, namespaces, type declarations, methods and properties with empty line.
|
|
||||||
|
|
||||||
always add comments to the code, when the code is not trivial.
|
|
||||||
|
|
||||||
always put classes into separate files.
|
|
||||||
|
|
||||||
always try to build the project you've edited.
|
|
||||||
|
|
||||||
always summarize the changes you've made.
|
|
||||||
|
|
||||||
always add changes to git with descriptive comment, but be concise.
|
|
||||||
|
|
||||||
never use terminal commands to edit code. In case of a failure, write it to user and stop execution.
|
|
||||||
|
|
||||||
never address compiler warnings yourself. If you see a warning, suggest to address it.
|
|
||||||
|
|
||||||
when working with RVA variables, always add that to variable name, e.g. "nameRVA".
|
|
||||||
|
|
||||||
always build only affected project, not full solution.
|
|
||||||
|
|
||||||
never introduce special cases in general solutions.
|
|
3
Common/Common.csproj
Normal file
3
Common/Common.csproj
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
</Project>
|
@@ -1,7 +1,7 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace ScrLib;
|
namespace Common;
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
@@ -13,6 +13,14 @@ public static class Extensions
|
|||||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static uint ReadUInt32LittleEndian(this FileStream fs)
|
||||||
|
{
|
||||||
|
Span<byte> buf = stackalloc byte[4];
|
||||||
|
fs.ReadExactly(buf);
|
||||||
|
|
||||||
|
return BinaryPrimitives.ReadUInt32LittleEndian(buf);
|
||||||
|
}
|
||||||
|
|
||||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||||
{
|
{
|
||||||
Span<byte> buf = stackalloc byte[4];
|
Span<byte> buf = stackalloc byte[4];
|
||||||
@@ -21,6 +29,24 @@ public static class Extensions
|
|||||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ReadNullTerminatedString(this FileStream fs)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var b = fs.ReadByte();
|
||||||
|
if (b == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append((char)b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||||
{
|
{
|
||||||
var len = fs.ReadInt32LittleEndian();
|
var len = fs.ReadInt32LittleEndian();
|
3
Common/IndexedEdge.cs
Normal file
3
Common/IndexedEdge.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Common;
|
||||||
|
|
||||||
|
public record IndexedEdge(ushort Index1, ushort Index2);
|
@@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MissionTmaLib;
|
namespace Common;
|
||||||
|
|
||||||
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
||||||
public class IntFloatValue(Span<byte> span)
|
public class IntFloatValue(Span<byte> span)
|
3
Common/Vector3.cs
Normal file
3
Common/Vector3.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Common;
|
||||||
|
|
||||||
|
public record Vector3(float X, float Y, float Z);
|
12
CpDatLib/CpDatEntry.cs
Normal file
12
CpDatLib/CpDatEntry.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CpDatLib;
|
||||||
|
|
||||||
|
public record CpDatEntry(
|
||||||
|
string ArchiveFile,
|
||||||
|
string ArchiveEntryName,
|
||||||
|
int Magic1,
|
||||||
|
int Magic2,
|
||||||
|
string Description,
|
||||||
|
int Magic3,
|
||||||
|
int ChildCount, // игра не хранит это число в объекте, но оно есть в файле
|
||||||
|
List<CpDatEntry> Children
|
||||||
|
);
|
5
CpDatLib/CpDatLib.csproj
Normal file
5
CpDatLib/CpDatLib.csproj
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
3
CpDatLib/CpDatParseResult.cs
Normal file
3
CpDatLib/CpDatParseResult.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace CpDatLib;
|
||||||
|
|
||||||
|
public record CpDatParseResult(CpDatScheme? Scheme, string? Error);
|
68
CpDatLib/CpDatParser.cs
Normal file
68
CpDatLib/CpDatParser.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Common;
|
||||||
|
|
||||||
|
namespace CpDatLib;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (fs.Length < 8)
|
||||||
|
return new CpDatParseResult(null, "File too small to be a valid \"cp\" .dat file.");
|
||||||
|
|
||||||
|
fs.ReadExactly(f0f1);
|
||||||
|
|
||||||
|
if (f0f1[0] != 0xf1 || f0f1[1] != 0xf0)
|
||||||
|
{
|
||||||
|
return new CpDatParseResult(null, "File does not start with expected header bytes f1_f0");
|
||||||
|
}
|
||||||
|
|
||||||
|
var schemeType = (SchemeType)fs.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CpDatEntry root = ReadEntryRecursive(fs);
|
||||||
|
|
||||||
|
var scheme = new CpDatScheme(schemeType, root);
|
||||||
|
|
||||||
|
return new CpDatParseResult(scheme, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CpDatEntry ReadEntryRecursive(FileStream fs)
|
||||||
|
{
|
||||||
|
var str1 = fs.ReadNullTerminatedString();
|
||||||
|
|
||||||
|
fs.Seek(32 - str1.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||||
|
|
||||||
|
var str2 = fs.ReadNullTerminatedString();
|
||||||
|
|
||||||
|
fs.Seek(32 - str2.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||||
|
var magic1 = fs.ReadInt32LittleEndian();
|
||||||
|
var magic2 = fs.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
var descriptionString = fs.ReadNullTerminatedString();
|
||||||
|
|
||||||
|
fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||||
|
var magic3 = fs.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
// игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов
|
||||||
|
var childCount = fs.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
List<CpDatEntry> children = new List<CpDatEntry>(childCount);
|
||||||
|
|
||||||
|
for (var i = 0; i < childCount; i++)
|
||||||
|
{
|
||||||
|
var child = ReadEntryRecursive(fs);
|
||||||
|
children.Add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CpDatEntry(str1, str2, magic1, magic2, descriptionString, magic3, childCount, Children: children);
|
||||||
|
}
|
||||||
|
}
|
3
CpDatLib/CpDatScheme.cs
Normal file
3
CpDatLib/CpDatScheme.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace CpDatLib;
|
||||||
|
|
||||||
|
public record CpDatScheme(SchemeType Type, CpDatEntry Root);
|
29
CpDatLib/CpEntryType.cs
Normal file
29
CpDatLib/CpEntryType.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
namespace CpDatLib;
|
||||||
|
|
||||||
|
public enum SchemeType : uint
|
||||||
|
{
|
||||||
|
ClassBuilding = 0x80000000,
|
||||||
|
ClassRobot = 0x01000000,
|
||||||
|
ClassAnimal = 0x20000000,
|
||||||
|
|
||||||
|
BunkerSmall = 0x80010000,
|
||||||
|
BunkerMedium = 0x80020000,
|
||||||
|
BunkerLarge = 0x80040000,
|
||||||
|
Generator = 0x80000002,
|
||||||
|
Mine = 0x80000004,
|
||||||
|
Storage = 0x80000008,
|
||||||
|
Plant = 0x80000010,
|
||||||
|
Hangar = 0x80000040,
|
||||||
|
TowerMedium = 0x80100000,
|
||||||
|
TowerLarge = 0x80200000,
|
||||||
|
MainTeleport = 0x80000200,
|
||||||
|
Institute = 0x80000400,
|
||||||
|
Bridge = 0x80001000,
|
||||||
|
Ruine = 0x80002000,
|
||||||
|
|
||||||
|
RobotTransport = 0x01002000,
|
||||||
|
RobotBuilder = 0x01004000,
|
||||||
|
RobotBattleunit = 0x01008000,
|
||||||
|
RobotHq = 0x01010000,
|
||||||
|
RobotHero = 0x01020000,
|
||||||
|
}
|
19
Directory.Build.props
Normal file
19
Directory.Build.props
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|
||||||
|
<!-- Enable Central Package Management -->
|
||||||
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||||
|
|
||||||
|
<!-- Enforce package version consistency -->
|
||||||
|
<EnablePackageVersionOverride>false</EnablePackageVersionOverride>
|
||||||
|
|
||||||
|
<!-- Suppress package version warnings -->
|
||||||
|
<NoWarn>$(NoWarn);NU1507;CS1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
16
Directory.Packages.props
Normal file
16
Directory.Packages.props
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- Package versions used across the solution -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||||
|
|
||||||
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
|
<PackageVersion Include="NativeFileDialogSharp" Version="0.5.0" />
|
||||||
|
<PackageVersion Include="Silk.NET" Version="2.22.0" />
|
||||||
|
<PackageVersion Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
||||||
|
|
||||||
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@@ -1,14 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@@ -1,332 +0,0 @@
|
|||||||
using System.Buffers.Binary;
|
|
||||||
using NResLib;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LandscapeExplorer;
|
|
||||||
|
|
||||||
public static class Program
|
|
||||||
{
|
|
||||||
private const string MapsDirectory = @"C:\Program Files (x86)\Nikita\Iron Strategy\DATA\MAPS\SC_3";
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
Console.OutputEncoding = Encoding.UTF8;
|
|
||||||
Console.WriteLine("Parkan 1 Landscape Explorer\n");
|
|
||||||
|
|
||||||
// Get all .map and .msh files in the directory
|
|
||||||
var mapFiles = Directory.GetFiles(MapsDirectory, "*.map");
|
|
||||||
var mshFiles = Directory.GetFiles(MapsDirectory, "*.msh");
|
|
||||||
|
|
||||||
Console.WriteLine($"Found {mapFiles.Length} .map files and {mshFiles.Length} .msh files in {MapsDirectory}\n");
|
|
||||||
|
|
||||||
// Process .map files
|
|
||||||
Console.WriteLine("=== MAP Files Analysis ===\n");
|
|
||||||
foreach (var mapFile in mapFiles)
|
|
||||||
{
|
|
||||||
AnalyzeNResFile(mapFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process .msh files
|
|
||||||
Console.WriteLine("\n=== MSH Files Analysis ===\n");
|
|
||||||
foreach (var mshFile in mshFiles)
|
|
||||||
{
|
|
||||||
AnalyzeNResFile(mshFile);
|
|
||||||
|
|
||||||
// Perform detailed landscape analysis on MSH files
|
|
||||||
AnalyzeLandscapeMeshFile(mshFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("\nAnalysis complete.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Analyzes an NRes file and displays its structure
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">Path to the NRes file</param>
|
|
||||||
private static void AnalyzeNResFile(string filePath)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Analyzing file: {Path.GetFileName(filePath)}");
|
|
||||||
|
|
||||||
var parseResult = NResParser.ReadFile(filePath);
|
|
||||||
|
|
||||||
if (parseResult.Error != null)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Error: {parseResult.Error}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var archive = parseResult.Archive!;
|
|
||||||
|
|
||||||
Console.WriteLine($" Header: {archive.Header.NRes}, Version: {archive.Header.Version:X}, Files: {archive.Header.FileCount}, Size: {archive.Header.TotalFileLengthBytes} bytes");
|
|
||||||
|
|
||||||
// Group files by type for better analysis
|
|
||||||
var filesByType = archive.Files.GroupBy(f => f.FileType);
|
|
||||||
|
|
||||||
foreach (var group in filesByType)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" File Type: {group.Key}, Count: {group.Count()}");
|
|
||||||
|
|
||||||
// Display details of the first file of each type as an example
|
|
||||||
var example = group.First();
|
|
||||||
Console.WriteLine($" Example: {example.FileName}");
|
|
||||||
Console.WriteLine($" Elements: {example.ElementCount}, Element Size: {example.ElementSize} bytes");
|
|
||||||
Console.WriteLine($" File Length: {example.FileLength} bytes, Offset: {example.OffsetInFile}");
|
|
||||||
|
|
||||||
// If this is a landscape-related file, provide more detailed analysis
|
|
||||||
if (IsLandscapeRelatedType(group.Key))
|
|
||||||
{
|
|
||||||
AnalyzeLandscapeData(example, filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if a file type is related to landscape data
|
|
||||||
/// </summary>
|
|
||||||
private static bool IsLandscapeRelatedType(string fileType)
|
|
||||||
{
|
|
||||||
// Based on the Landscape constructor analysis, these types might be related to landscape
|
|
||||||
return fileType == "LAND" || fileType == "TERR" || fileType == "MSH0" ||
|
|
||||||
fileType == "MESH" || fileType == "MATR" || fileType == "TEXT";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Analyzes landscape-specific data in a file
|
|
||||||
/// </summary>
|
|
||||||
private static void AnalyzeLandscapeData(ListMetadataItem item, string filePath)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" [Landscape Data Analysis]:");
|
|
||||||
|
|
||||||
// Read the file data for this specific item
|
|
||||||
using var fs = new FileStream(filePath, FileMode.Open);
|
|
||||||
fs.Seek(item.OffsetInFile, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
var buffer = new byte[Math.Min(item.FileLength, 256)]; // Read at most 256 bytes for analysis
|
|
||||||
fs.Read(buffer, 0, buffer.Length);
|
|
||||||
|
|
||||||
// Display some basic statistics based on the file type
|
|
||||||
if (item.FileType == "LAND" || item.FileType == "TERR")
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Terrain data with {item.ElementCount} elements");
|
|
||||||
// If element size is known, we can calculate grid dimensions
|
|
||||||
if (item.ElementCount > 0 && item.ElementSize > 0)
|
|
||||||
{
|
|
||||||
// Assuming square terrain, which is common in games from this era
|
|
||||||
var gridSize = Math.Sqrt(item.ElementCount);
|
|
||||||
if (Math.Abs(gridSize - Math.Round(gridSize)) < 0.001) // If it's close to a whole number
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Terrain grid size: {Math.Round(gridSize)} x {Math.Round(gridSize)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (item.FileType == "MSH0" || item.FileType == "MESH")
|
|
||||||
{
|
|
||||||
// For mesh data, try to estimate vertex/face counts
|
|
||||||
Console.WriteLine($" Mesh data, possibly with vertices and faces");
|
|
||||||
|
|
||||||
// Common sizes: vertices are often 12 bytes (3 floats), faces are often 12 bytes (3 indices)
|
|
||||||
if (item.ElementSize == 12)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Possibly {item.ElementCount} vertices or faces");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display first few bytes as hex for debugging
|
|
||||||
var hexPreview = BitConverter.ToString(
|
|
||||||
buffer.Take(32)
|
|
||||||
.ToArray()
|
|
||||||
)
|
|
||||||
.Replace("-", " ");
|
|
||||||
Console.WriteLine($" Data preview (hex): {hexPreview}...");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs a detailed analysis of a landscape mesh file
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">Path to the MSH file</param>
|
|
||||||
private static void AnalyzeLandscapeMeshFile(string filePath)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"\nDetailed Landscape Analysis for: {Path.GetFileName(filePath)}\n");
|
|
||||||
|
|
||||||
var parseResult = NResParser.ReadFile(filePath);
|
|
||||||
if (parseResult.Error != null || parseResult.Archive == null)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Error analyzing file: {parseResult.Error}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var archive = parseResult.Archive;
|
|
||||||
|
|
||||||
// Based on the Landscape constructor and the file analysis, we can identify specific sections
|
|
||||||
// File types in MSH files appear to be numeric values (01, 02, 03, etc.)
|
|
||||||
|
|
||||||
// First, let's extract all the different data sections
|
|
||||||
var sections = new Dictionary<string, (ListMetadataItem Meta, byte[] Data)>();
|
|
||||||
|
|
||||||
foreach (var item in archive.Files)
|
|
||||||
{
|
|
||||||
using var fs = new FileStream(filePath, FileMode.Open);
|
|
||||||
fs.Seek(item.OffsetInFile, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
var buffer = new byte[item.FileLength];
|
|
||||||
fs.Read(buffer, 0, buffer.Length);
|
|
||||||
|
|
||||||
sections[item.FileType] = (item, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now analyze each section based on what we know from the Landscape constructor
|
|
||||||
Console.WriteLine(" Landscape Structure Analysis:");
|
|
||||||
|
|
||||||
// Type 01 appears to be basic landscape information (possibly header/metadata)
|
|
||||||
if (sections.TryGetValue("01 00 00 00", out var section01))
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Section 01: Basic Landscape Info");
|
|
||||||
Console.WriteLine($" Elements: {section01.Meta.ElementCount}, Element Size: {section01.Meta.ElementSize} bytes");
|
|
||||||
Console.WriteLine($" Total Size: {section01.Meta.FileLength} bytes");
|
|
||||||
|
|
||||||
// Try to extract some basic info if the format is as expected
|
|
||||||
if (section01.Meta.ElementSize == 38 && section01.Data.Length >= 38)
|
|
||||||
{
|
|
||||||
// This is speculative based on common terrain formats
|
|
||||||
var width = BitConverter.ToInt32(section01.Data, 0);
|
|
||||||
var height = BitConverter.ToInt32(section01.Data, 4);
|
|
||||||
Console.WriteLine($" Possible Dimensions: {width} x {height}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type 03 appears to be vertex data (based on element size of 12 bytes which is typical for 3D vertices)
|
|
||||||
if (sections.TryGetValue("03 00 00 00", out var section03))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"\n Section 03: Vertex Data");
|
|
||||||
Console.WriteLine($" Vertex Count: {section03.Meta.ElementCount}");
|
|
||||||
Console.WriteLine($" Vertex Size: {section03.Meta.ElementSize} bytes");
|
|
||||||
|
|
||||||
// If we have vertex data in expected format (3 floats per vertex)
|
|
||||||
if (section03.Meta.ElementSize == 12 && section03.Data.Length >= 36)
|
|
||||||
{
|
|
||||||
// Display first 3 vertices as example
|
|
||||||
Console.WriteLine(" Sample Vertices:");
|
|
||||||
for (int i = 0; i < Math.Min(3, section03.Meta.ElementCount); i++)
|
|
||||||
{
|
|
||||||
var offset = i * 12;
|
|
||||||
var x = BitConverter.ToSingle(section03.Data, offset);
|
|
||||||
var y = BitConverter.ToSingle(section03.Data, offset + 4);
|
|
||||||
var z = BitConverter.ToSingle(section03.Data, offset + 8);
|
|
||||||
Console.WriteLine($" Vertex {i}: ({x}, {y}, {z})");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate terrain bounds
|
|
||||||
var minX = float.MaxValue;
|
|
||||||
var minY = float.MaxValue;
|
|
||||||
var minZ = float.MaxValue;
|
|
||||||
var maxX = float.MinValue;
|
|
||||||
var maxY = float.MinValue;
|
|
||||||
var maxZ = float.MinValue;
|
|
||||||
|
|
||||||
for (int i = 0; i < section03.Meta.ElementCount; i++)
|
|
||||||
{
|
|
||||||
var offset = i * 12;
|
|
||||||
if (offset + 12 <= section03.Data.Length)
|
|
||||||
{
|
|
||||||
var x = BitConverter.ToSingle(section03.Data, offset);
|
|
||||||
var y = BitConverter.ToSingle(section03.Data, offset + 4);
|
|
||||||
var z = BitConverter.ToSingle(section03.Data, offset + 8);
|
|
||||||
|
|
||||||
minX = Math.Min(minX, x);
|
|
||||||
minY = Math.Min(minY, y);
|
|
||||||
minZ = Math.Min(minZ, z);
|
|
||||||
maxX = Math.Max(maxX, x);
|
|
||||||
maxY = Math.Max(maxY, y);
|
|
||||||
maxZ = Math.Max(maxZ, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine(" Terrain Bounds:");
|
|
||||||
Console.WriteLine($" Min: ({minX}, {minY}, {minZ})");
|
|
||||||
Console.WriteLine($" Max: ({maxX}, {maxY}, {maxZ})");
|
|
||||||
Console.WriteLine($" Dimensions: {maxX - minX} x {maxY - minY} x {maxZ - minZ}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type 02 might be face/index data for the mesh
|
|
||||||
if (sections.TryGetValue("02 00 00 00", out var section02))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"\n Section 02: Possible Face/Index Data");
|
|
||||||
Console.WriteLine($" Elements: {section02.Meta.ElementCount}");
|
|
||||||
Console.WriteLine($" Element Size: {section02.Meta.ElementSize} bytes");
|
|
||||||
|
|
||||||
// If element size is divisible by 4 (common for index data)
|
|
||||||
if (section02.Meta.ElementSize % 4 == 0 && section02.Data.Length >= 12)
|
|
||||||
{
|
|
||||||
// Display first triangle as example (assuming 3 indices per triangle)
|
|
||||||
Console.WriteLine(" Sample Indices (if this is index data):");
|
|
||||||
var indicesPerElement = section02.Meta.ElementSize / 4;
|
|
||||||
for (int i = 0; i < Math.Min(1, section02.Meta.ElementCount); i++)
|
|
||||||
{
|
|
||||||
Console.Write($" Element {i}: ");
|
|
||||||
for (int j = 0; j < indicesPerElement; j++)
|
|
||||||
{
|
|
||||||
var offset = i * section02.Meta.ElementSize + j * 4;
|
|
||||||
if (offset + 4 <= section02.Data.Length)
|
|
||||||
{
|
|
||||||
var index = BitConverter.ToInt32(section02.Data, offset);
|
|
||||||
Console.Write($"{index} ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types 04, 05, 12, 0E, 0B might be texture coordinates, normals, colors, etc.
|
|
||||||
var otherSections = new[] {"04 00 00 00", "05 00 00 00", "12 00 00 00", "0E 00 00 00", "0B 00 00 00"};
|
|
||||||
foreach (var sectionType in otherSections)
|
|
||||||
{
|
|
||||||
if (sections.TryGetValue(sectionType, out var section))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"\n Section {sectionType.Substring(0, 2)}: Additional Mesh Data");
|
|
||||||
Console.WriteLine($" Elements: {section.Meta.ElementCount}");
|
|
||||||
Console.WriteLine($" Element Size: {section.Meta.ElementSize} bytes");
|
|
||||||
|
|
||||||
// If element size is 4 bytes, it could be color data, texture indices, etc.
|
|
||||||
if (section.Meta.ElementSize == 4 && section.Data.Length >= 12)
|
|
||||||
{
|
|
||||||
Console.WriteLine(" Sample Data (as integers):");
|
|
||||||
for (int i = 0; i < Math.Min(3, section.Meta.ElementCount); i++)
|
|
||||||
{
|
|
||||||
var offset = i * 4;
|
|
||||||
var value = BitConverter.ToInt32(section.Data, offset);
|
|
||||||
Console.WriteLine($" Element {i}: {value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type 15 might be material or special data (Msh_15 in the decompiled code)
|
|
||||||
if (sections.TryGetValue("15 00 00 00", out var section15) && sections.TryGetValue("03 00 00 00", out var vertexSection))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"\n Section 15: Special Data (Msh_15 type in decompiled code)");
|
|
||||||
Console.WriteLine($" Elements: {section15.Meta.ElementCount}");
|
|
||||||
Console.WriteLine($" Element Size: {section15.Meta.ElementSize} bytes");
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
for (var i = 0; i < section15.Data.Length; i += 28)
|
|
||||||
{
|
|
||||||
var first = BinaryPrimitives.ReadUInt32LittleEndian(section15.Data.AsSpan(i));
|
|
||||||
|
|
||||||
if ((first & 0x20000) != 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Found {first}/0x{first:X8} 0x20000 at index {i / 28}. &0x20000={first&0x20000}/0x{first&0x20000:X8} offset: {i:X8}");
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"Total found: {count}");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
@@ -1,4 +1,6 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public class GameObjectInfo
|
public class GameObjectInfo
|
||||||
{
|
{
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);
|
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);
|
@@ -1,9 +1,5 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
<PropertyGroup>
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
</ItemGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
using System.Buffers.Binary;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MissionTmaLib.Parsing;
|
|
||||||
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
public static int ReadInt32LittleEndian(this FileStream fs)
|
|
||||||
{
|
|
||||||
Span<byte> buf = stackalloc byte[4];
|
|
||||||
fs.ReadExactly(buf);
|
|
||||||
|
|
||||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
|
||||||
{
|
|
||||||
Span<byte> buf = stackalloc byte[4];
|
|
||||||
fs.ReadExactly(buf);
|
|
||||||
|
|
||||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
|
||||||
{
|
|
||||||
var len = fs.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
if (len == 0)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = new byte[len];
|
|
||||||
|
|
||||||
fs.ReadExactly(buffer, 0, len);
|
|
||||||
|
|
||||||
return Encoding.ASCII.GetString(buffer, 0, len);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,12 @@
|
|||||||
namespace MissionTmaLib.Parsing;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib.Parsing;
|
||||||
|
|
||||||
public class MissionTmaParser
|
public class MissionTmaParser
|
||||||
{
|
{
|
||||||
public static MissionTmaParseResult ReadFile(string filePath)
|
public static MissionTmaParseResult ReadFile(string filePath)
|
||||||
{
|
{
|
||||||
var fs = new FileStream(filePath, FileMode.Open);
|
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
|
||||||
var arealData = LoadAreals(fs);
|
var arealData = LoadAreals(fs);
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
namespace MissionTmaLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
@@ -1,3 +0,0 @@
|
|||||||
namespace MissionTmaLib;
|
|
||||||
|
|
||||||
public record Vector3(float X, float Y, float Z);
|
|
@@ -1,11 +1,11 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,7 +1,40 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont.rlb");
|
var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont-1.rlb");
|
||||||
|
|
||||||
|
var header = fileBytes.AsSpan().Slice(0, 32);
|
||||||
|
|
||||||
|
var nlHeaderBytes = header.Slice(0, 2);
|
||||||
|
var mustBeZero = header[2];
|
||||||
|
var mustBeOne = header[3];
|
||||||
|
var numberOfEntriesBytes = header.Slice(4, 2);
|
||||||
|
var sortingFlagBytes = header.Slice(14, 2);
|
||||||
|
var decryptionKeyBytes = header.Slice(20, 2);
|
||||||
|
|
||||||
|
var numberOfEntries = BinaryPrimitives.ReadInt16LittleEndian(numberOfEntriesBytes);
|
||||||
|
var sortingFlag = BinaryPrimitives.ReadInt16LittleEndian(sortingFlagBytes);
|
||||||
|
var decryptionKey = BinaryPrimitives.ReadInt16LittleEndian(decryptionKeyBytes);
|
||||||
|
|
||||||
|
var headerSize = numberOfEntries * 32;
|
||||||
|
|
||||||
|
var decryptedHeader = new byte[headerSize];
|
||||||
|
|
||||||
|
var keyLow = decryptionKeyBytes[0];
|
||||||
|
var keyHigh = decryptionKeyBytes[1];
|
||||||
|
for (var i = 0; i < headerSize; i++)
|
||||||
|
{
|
||||||
|
byte tmp = (byte)((keyLow << 1) ^ keyHigh);
|
||||||
|
keyLow = tmp;
|
||||||
|
keyHigh = (byte)((keyHigh >> 1) ^ tmp);
|
||||||
|
decryptedHeader[i] = (byte)(fileBytes[32 + i] ^ tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
var decryptedHeaderString = Encoding.ASCII.GetString(decryptedHeader, 0, headerSize);
|
||||||
|
var entries = decryptedHeader.Chunk(32).ToArray();
|
||||||
|
var entriesStrings = entries.Select(x => Encoding.ASCII.GetString(x, 0, x.Length)).ToArray();
|
||||||
|
|
||||||
|
File.WriteAllBytes("export.nl", decryptedHeader);
|
||||||
var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2));
|
var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2));
|
||||||
|
|
||||||
var decodedHeader = new byte[fileCount * 32];
|
var decodedHeader = new byte[fileCount * 32];
|
||||||
|
@@ -1,9 +1,3 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,151 +0,0 @@
|
|||||||
using System.Buffers.Binary;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NResLib;
|
|
||||||
|
|
||||||
public class NResPacker
|
|
||||||
{
|
|
||||||
public static string Pack(NResArchive archive, string srcNresPath, string contentDirectoryPath, string targetFileDirectoryPath)
|
|
||||||
{
|
|
||||||
var diskFiles = Directory.GetFiles(contentDirectoryPath)
|
|
||||||
.Select(Path.GetFileName)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var fileOffset = 16; // 16 по умолчанию, т.к. есть заголовок в 16 байт.
|
|
||||||
|
|
||||||
var metadataItems = new List<ListMetadataItem>();
|
|
||||||
|
|
||||||
foreach (var archiveFile in archive.Files)
|
|
||||||
{
|
|
||||||
var extension = Path.GetExtension(archiveFile.FileName);
|
|
||||||
var fileName = Path.GetFileNameWithoutExtension(archiveFile.FileName);
|
|
||||||
|
|
||||||
if (extension == "")
|
|
||||||
{
|
|
||||||
extension = ".bin";
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetFileName = $"{archiveFile.Index}_{archiveFile.FileType}_{fileName}{extension}";
|
|
||||||
|
|
||||||
if (diskFiles.All(x => x != targetFileName))
|
|
||||||
{
|
|
||||||
return $"Не найдён файл {targetFileName}";
|
|
||||||
}
|
|
||||||
|
|
||||||
var filePath = Path.Combine(contentDirectoryPath, targetFileName);
|
|
||||||
|
|
||||||
var fileInfo = new FileInfo(filePath);
|
|
||||||
|
|
||||||
if (!fileInfo.Exists)
|
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
var newFileLength = (int)fileInfo.Length;
|
|
||||||
|
|
||||||
var listItem = new ListMetadataItem(
|
|
||||||
archiveFile.FileType,
|
|
||||||
archiveFile.ElementCount,
|
|
||||||
archiveFile.Magic1,
|
|
||||||
newFileLength,
|
|
||||||
archiveFile.ElementSize,
|
|
||||||
archiveFile.FileName,
|
|
||||||
archiveFile.Magic3,
|
|
||||||
archiveFile.Magic4,
|
|
||||||
archiveFile.Magic5,
|
|
||||||
archiveFile.Magic6,
|
|
||||||
fileOffset,
|
|
||||||
archiveFile.Index
|
|
||||||
);
|
|
||||||
|
|
||||||
fileOffset += newFileLength;
|
|
||||||
|
|
||||||
metadataItems.Add(listItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalFileLength =
|
|
||||||
16 + // заголовок
|
|
||||||
metadataItems.Sum(x => x.FileLength) + // сумма длин всех файлов
|
|
||||||
metadataItems.Count * 64; // длина всех метаданных
|
|
||||||
|
|
||||||
var header = new NResArchiveHeader(archive.Header.NRes, archive.Header.Version, archive.Header.FileCount, totalFileLength);
|
|
||||||
|
|
||||||
var targetArchive = new NResArchive(header, metadataItems);
|
|
||||||
|
|
||||||
// имя архива = имени папки в которую архив распаковывали
|
|
||||||
string targetArchiveFileName = Path.GetFileName(srcNresPath)!;
|
|
||||||
|
|
||||||
var targetArchivePath = Path.Combine(targetFileDirectoryPath, targetArchiveFileName);
|
|
||||||
|
|
||||||
using var fs = new FileStream(targetArchivePath, FileMode.CreateNew);
|
|
||||||
|
|
||||||
Span<byte> span = stackalloc byte[4];
|
|
||||||
|
|
||||||
span.Clear();
|
|
||||||
Encoding.ASCII.GetBytes(header.NRes, span);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, header.Version);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, header.FileCount);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, header.TotalFileLengthBytes);
|
|
||||||
fs.Write(span);
|
|
||||||
|
|
||||||
foreach (var archiveFile in targetArchive.Files)
|
|
||||||
{
|
|
||||||
var extension = Path.GetExtension(archiveFile.FileName);
|
|
||||||
var fileName = Path.GetFileNameWithoutExtension(archiveFile.FileName);
|
|
||||||
|
|
||||||
if (extension == "")
|
|
||||||
{
|
|
||||||
extension = ".bin";
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetFileName = $"{archiveFile.Index}_{archiveFile.FileType}_{fileName}{extension}";
|
|
||||||
|
|
||||||
var filePath = Path.Combine(contentDirectoryPath, targetFileName);
|
|
||||||
using var srcFs = new FileStream(filePath, FileMode.Open);
|
|
||||||
|
|
||||||
srcFs.CopyTo(fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<byte> fileNameSpan = stackalloc byte[20];
|
|
||||||
|
|
||||||
foreach (var archiveFile in targetArchive.Files)
|
|
||||||
{
|
|
||||||
span.Clear();
|
|
||||||
Encoding.ASCII.GetBytes(archiveFile.FileType, span);
|
|
||||||
fs.Write(span);
|
|
||||||
|
|
||||||
BinaryPrimitives.WriteUInt32LittleEndian(span, archiveFile.ElementCount);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.Magic1);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.FileLength);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.ElementSize);
|
|
||||||
fs.Write(span);
|
|
||||||
|
|
||||||
fileNameSpan.Clear();
|
|
||||||
Encoding.ASCII.GetBytes(archiveFile.FileName, fileNameSpan);
|
|
||||||
fs.Write(fileNameSpan);
|
|
||||||
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.Magic3);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.Magic4);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.Magic5);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.Magic6);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.OffsetInFile);
|
|
||||||
fs.Write(span);
|
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(span, archiveFile.Index);
|
|
||||||
fs.Write(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.Flush();
|
|
||||||
|
|
||||||
return "Запакован архив";
|
|
||||||
}
|
|
||||||
}
|
|
@@ -7,7 +7,7 @@ public static class NResParser
|
|||||||
{
|
{
|
||||||
public static NResParseResult ReadFile(string path)
|
public static NResParseResult ReadFile(string path)
|
||||||
{
|
{
|
||||||
using FileStream nResFs = new FileStream(path, FileMode.Open);
|
using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
|
||||||
if (nResFs.Length < 16)
|
if (nResFs.Length < 16)
|
||||||
{
|
{
|
||||||
|
@@ -56,6 +56,7 @@ public class App
|
|||||||
serviceCollection.AddSingleton(new BinaryExplorerViewModel());
|
serviceCollection.AddSingleton(new BinaryExplorerViewModel());
|
||||||
serviceCollection.AddSingleton(new ScrViewModel());
|
serviceCollection.AddSingleton(new ScrViewModel());
|
||||||
serviceCollection.AddSingleton(new VarsetViewModel());
|
serviceCollection.AddSingleton(new VarsetViewModel());
|
||||||
|
serviceCollection.AddSingleton(new CpDatSchemeViewModel());
|
||||||
|
|
||||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
112
NResUI/ImGuiUI/CpDatSchemeExplorer.cs
Normal file
112
NResUI/ImGuiUI/CpDatSchemeExplorer.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using CpDatLib;
|
||||||
|
using ImGuiNET;
|
||||||
|
using NResUI.Abstractions;
|
||||||
|
using NResUI.Models;
|
||||||
|
|
||||||
|
namespace NResUI.ImGuiUI;
|
||||||
|
|
||||||
|
public class CpDatSchemeExplorer : IImGuiPanel
|
||||||
|
{
|
||||||
|
private readonly CpDatSchemeViewModel _viewModel;
|
||||||
|
|
||||||
|
public CpDatSchemeExplorer(CpDatSchemeViewModel viewModel)
|
||||||
|
{
|
||||||
|
_viewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnImGuiRender()
|
||||||
|
{
|
||||||
|
if (ImGui.Begin("cp .dat Scheme Explorer"))
|
||||||
|
{
|
||||||
|
ImGui.Text("cp .dat - это файл схема здания или робота. Их можно найти в папке UNITS");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
var cpDat = _viewModel.CpDatScheme;
|
||||||
|
if (_viewModel.HasFile && cpDat is not null)
|
||||||
|
{
|
||||||
|
ImGui.Text("Тип объекта в схеме: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(cpDat.Type.ToString("G"));
|
||||||
|
|
||||||
|
var root = cpDat.Root;
|
||||||
|
|
||||||
|
DrawEntry(root, 0);
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (ImGui.BeginTable("content", 7, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Уровень вложенности");
|
||||||
|
ImGui.TableSetupColumn("Архив");
|
||||||
|
ImGui.TableSetupColumn("Элемент");
|
||||||
|
ImGui.TableSetupColumn("Magic1");
|
||||||
|
ImGui.TableSetupColumn("Magic2");
|
||||||
|
ImGui.TableSetupColumn("Описание");
|
||||||
|
ImGui.TableSetupColumn("Magic3");
|
||||||
|
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (int i = 0; i < _viewModel.FlatList.Count; i++)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Level.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveFile);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveEntryName);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Entry.Magic1.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Entry.Magic2.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Entry.Description);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(_viewModel.FlatList[i].Entry.Magic3.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawEntry(CpDatEntry entry, int index)
|
||||||
|
{
|
||||||
|
if (ImGui.TreeNodeEx($"Элемент: \"{entry.ArchiveFile}/{entry.ArchiveEntryName}\" - {entry.Description}##entry_{index}"))
|
||||||
|
{
|
||||||
|
ImGui.Text("Magic1: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(entry.Magic1.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Magic2: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(entry.Magic2.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Magic3: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(entry.Magic3.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Кол-во дочерних элементов: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(entry.ChildCount.ToString());
|
||||||
|
|
||||||
|
foreach (var child in entry.Children)
|
||||||
|
{
|
||||||
|
DrawEntry(child, ++index);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_viewModel.Error is not null)
|
||||||
|
{
|
||||||
|
ImGui.Text(_viewModel.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("cp .dat не открыт");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using CpDatLib;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using MissionTmaLib;
|
using MissionTmaLib;
|
||||||
using MissionTmaLib.Parsing;
|
using MissionTmaLib.Parsing;
|
||||||
@@ -18,6 +19,7 @@ namespace NResUI.ImGuiUI
|
|||||||
ScrViewModel scrViewModel,
|
ScrViewModel scrViewModel,
|
||||||
MissionTmaViewModel missionTmaViewModel,
|
MissionTmaViewModel missionTmaViewModel,
|
||||||
VarsetViewModel varsetViewModel,
|
VarsetViewModel varsetViewModel,
|
||||||
|
CpDatSchemeViewModel cpDatSchemeViewModel,
|
||||||
MessageBoxModalPanel messageBox)
|
MessageBoxModalPanel messageBox)
|
||||||
: IImGuiPanel
|
: IImGuiPanel
|
||||||
{
|
{
|
||||||
@@ -121,6 +123,21 @@ namespace NResUI.ImGuiUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Open cp .dat Scheme File"))
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("dat");
|
||||||
|
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
var path = result.Path;
|
||||||
|
var parseResult = CpDatParser.Parse(path);
|
||||||
|
|
||||||
|
cpDatSchemeViewModel.SetParseResult(parseResult, path);
|
||||||
|
|
||||||
|
Console.WriteLine("Read cp .dat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (nResExplorerViewModel.HasFile)
|
if (nResExplorerViewModel.HasFile)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Экспортировать NRes"))
|
if (ImGui.MenuItem("Экспортировать NRes"))
|
||||||
@@ -138,34 +155,6 @@ namespace NResUI.ImGuiUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nResExplorerViewModel.HasFile)
|
|
||||||
{
|
|
||||||
if (ImGui.MenuItem("Запаковать NRes"))
|
|
||||||
{
|
|
||||||
messageBox.Show("Выберите папку с контентом NRES");
|
|
||||||
var contentDirectoryPicker = Dialog.FolderPicker();
|
|
||||||
|
|
||||||
if (contentDirectoryPicker.IsOk)
|
|
||||||
{
|
|
||||||
var contentDirectoryPath = contentDirectoryPicker.Path;
|
|
||||||
|
|
||||||
var targetFileDirectoryPicker = Dialog.FolderPicker();
|
|
||||||
|
|
||||||
if (targetFileDirectoryPicker.IsOk)
|
|
||||||
{
|
|
||||||
var targetFileDirectory = targetFileDirectoryPicker.Path;
|
|
||||||
|
|
||||||
var packResult = NResPacker.Pack(
|
|
||||||
nResExplorerViewModel.Archive!,
|
|
||||||
nResExplorerViewModel.Path!,
|
|
||||||
contentDirectoryPath, targetFileDirectory);
|
|
||||||
|
|
||||||
messageBox.Show(packResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,9 @@ public class MissionTmaExplorer : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("Mission TMA Explorer"))
|
if (ImGui.Begin("Mission TMA Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("data.tma - это файл миссии. Его можно найти в папке MISSIONS");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
var mission = _viewModel.Mission;
|
var mission = _viewModel.Mission;
|
||||||
if (_viewModel.HasFile && mission is not null)
|
if (_viewModel.HasFile && mission is not null)
|
||||||
{
|
{
|
||||||
|
@@ -17,6 +17,9 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("NRes Explorer"))
|
if (ImGui.Begin("NRes Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
if (!_viewModel.HasFile)
|
if (!_viewModel.HasFile)
|
||||||
{
|
{
|
||||||
ImGui.Text("No NRes is opened");
|
ImGui.Text("No NRes is opened");
|
||||||
@@ -82,8 +85,8 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
);
|
);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(
|
ImGui.Text(
|
||||||
"0x" + _viewModel.Archive.Files[i]
|
_viewModel.Archive.Files[i]
|
||||||
.ElementSize.ToString("X2")
|
.ElementSize.ToString()
|
||||||
);
|
);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text(_viewModel.Archive.Files[i].FileName);
|
ImGui.Text(_viewModel.Archive.Files[i].FileName);
|
||||||
|
@@ -18,6 +18,9 @@ public class ScrExplorer : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("SCR Explorer"))
|
if (ImGui.Begin("SCR Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("scr - это файл AI скриптов. Их можно найти в папке MISSIONS/SCRIPTS");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
var scr = _viewModel.Scr;
|
var scr = _viewModel.Scr;
|
||||||
if (_viewModel.HasFile && scr is not null)
|
if (_viewModel.HasFile && scr is not null)
|
||||||
{
|
{
|
||||||
|
@@ -21,6 +21,9 @@ public class TexmExplorer : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("TEXM Explorer"))
|
if (ImGui.Begin("TEXM Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text("TEXM - это файл текстуры. Их можно найти внутри NRes архивов, например Textures.lib");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
if (!_viewModel.HasFile)
|
if (!_viewModel.HasFile)
|
||||||
{
|
{
|
||||||
ImGui.Text("No TEXM opened");
|
ImGui.Text("No TEXM opened");
|
||||||
|
@@ -17,6 +17,9 @@ public class VarsetExplorerPanel : IImGuiPanel
|
|||||||
{
|
{
|
||||||
if (ImGui.Begin("VARSET Explorer"))
|
if (ImGui.Begin("VARSET Explorer"))
|
||||||
{
|
{
|
||||||
|
ImGui.Text(".var - это файл динамических настроек. Можно найти в MISSIONS/SCRIPTS/varset.var, а также внутри behpsp.res");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
if (_viewModel.Items.Count == 0)
|
if (_viewModel.Items.Count == 0)
|
||||||
{
|
{
|
||||||
ImGui.Text("VARSET не загружен");
|
ImGui.Text("VARSET не загружен");
|
||||||
|
40
NResUI/Models/CpDatSchemeViewModel.cs
Normal file
40
NResUI/Models/CpDatSchemeViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using CpDatLib;
|
||||||
|
using ScrLib;
|
||||||
|
|
||||||
|
namespace NResUI.Models;
|
||||||
|
|
||||||
|
public class CpDatSchemeViewModel
|
||||||
|
{
|
||||||
|
public bool HasFile { get; set; }
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
public CpDatScheme? CpDatScheme { get; set; }
|
||||||
|
|
||||||
|
public List<(int Level, CpDatEntry Entry)> FlatList { get; set; }
|
||||||
|
|
||||||
|
public string? Path { get; set; }
|
||||||
|
|
||||||
|
public void SetParseResult(CpDatParseResult parseResult, string path)
|
||||||
|
{
|
||||||
|
CpDatScheme = parseResult.Scheme;
|
||||||
|
Error = parseResult.Error;
|
||||||
|
HasFile = true;
|
||||||
|
Path = path;
|
||||||
|
|
||||||
|
if (CpDatScheme is not null)
|
||||||
|
{
|
||||||
|
FlatList = [];
|
||||||
|
|
||||||
|
CollectEntries(CpDatScheme.Root, 0);
|
||||||
|
|
||||||
|
void CollectEntries(CpDatEntry entry, int level)
|
||||||
|
{
|
||||||
|
FlatList.Add((level, entry));
|
||||||
|
foreach (var child in entry.Children)
|
||||||
|
{
|
||||||
|
CollectEntries(child, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -12,13 +9,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
|
<PackageReference Include="NativeFileDialogSharp" />
|
||||||
<PackageReference Include="Silk.NET" Version="2.22.0" />
|
<PackageReference Include="Silk.NET" />
|
||||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CpDatLib\CpDatLib.csproj" />
|
||||||
<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="..\ScrLib\ScrLib.csproj" />
|
||||||
|
@@ -137,9 +137,9 @@ namespace NResUI
|
|||||||
|
|
||||||
public void SetLod(int @base, int min, int max)
|
public void SetLod(int @base, int min, int max)
|
||||||
{
|
{
|
||||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base);
|
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, in @base);
|
||||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min);
|
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, in min);
|
||||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max);
|
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, in max);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetWrap(TextureCoordinate coord, TextureWrapMode mode)
|
public void SetWrap(TextureCoordinate coord, TextureWrapMode mode)
|
||||||
|
@@ -1,104 +0,0 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextureDecoder", "TextureDecoder\TextureDecoder.csproj", "{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLUnpacker", "NLUnpacker\NLUnpacker.csproj", "{50C83E6C-23ED-4A8E-B948-89686A742CF0}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResUI", "NResUI\NResUI.csproj", "{7456A089-0701-416C-8668-1F740BF4B72C}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResLib", "NResLib\NResLib.csproj", "{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshUnpacker", "MeshUnpacker\MeshUnpacker.csproj", "{F1465FFE-0D66-4A3C-90D7-153A14E226E6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TexmLib", "TexmLib\TexmLib.csproj", "{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionDataUnpacker", "MissionDataUnpacker\MissionDataUnpacker.csproj", "{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{BAF212FE-A0FD-41A2-A1A9-B406FDDFBAF3}"
|
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
README.md = README.md
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}"
|
|
||||||
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
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X86DisassemblerTests", "X86DisassemblerTests\X86DisassemblerTests.csproj", "{D6A1F5A9-0C7A-4F8F-B8C5-83E9D3F3A1D5}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LandscapeExplorer", "LandscapeExplorer\LandscapeExplorer.csproj", "{2700BD3F-DC67-4B58-8F73-F790AA68E4FE}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.ActiveCfg = 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.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
|
|
||||||
{D6A1F5A9-0C7A-4F8F-B8C5-83E9D3F3A1D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D6A1F5A9-0C7A-4F8F-B8C5-83E9D3F3A1D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D6A1F5A9-0C7A-4F8F-B8C5-83E9D3F3A1D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D6A1F5A9-0C7A-4F8F-B8C5-83E9D3F3A1D5}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2700BD3F-DC67-4B58-8F73-F790AA68E4FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2700BD3F-DC67-4B58-8F73-F790AA68E4FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2700BD3F-DC67-4B58-8F73-F790AA68E4FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2700BD3F-DC67-4B58-8F73-F790AA68E4FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
@@ -1,31 +0,0 @@
|
|||||||
<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_003ACollectionAsserts_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F435f965090c5b89f0c5efb49ac3c5a72367d90599314191af25a832d0942f_003FCollectionAsserts_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACsvReader_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff7b87edd534764eebf2388a77d49e5cd9c6d49eb6788dca9b1c07d4545412715_003FCsvReader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultTypeConverter_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F64b864a5d465bc24fc4b55e1026aba213beb1733ef631abeca5a9f25357eda_003FDefaultTypeConverter_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_003AEncoding_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F1675dc7b710feeeb3e0bc8728be8a947537155c199480fb23b776e81d459_003FEncoding_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFailAsserts_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F807c15a7d8383b1548dff1ae33270e637836659d9caecd676ea6f2c59f1c71a_003FFailAsserts_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_003ASafeFileHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa03380083db34a2faee436e29e06a72ae8e910_003Fb6_003F67cd826c_003FSafeFileHandle_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_003AStringAsserts_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F999ae9cc4ab7b7cfbc5080803e994426e97fd9d87c5b1f44544a799bc114_003FStringAsserts_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>
|
|
||||||
|
|
||||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=47ebaefe_002Da806_002D4565_002Dabe7_002D4f14ac675135/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;X86DisassemblerTests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
|
||||||
<Project Location="C:\Projects\CSharp\ParkanPlayground\X86DisassemblerTests" Presentation="&lt;X86DisassemblerTests&gt;" />
|
|
||||||
</SessionState></s:String>
|
|
||||||
|
|
||||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=87a33e46_002D2816_002D434f_002D972a_002D703eb7a78476/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
|
||||||
<And>
|
|
||||||
<Namespace>X86DisassemblerTests</Namespace>
|
|
||||||
<Project Location="C:\Projects\CSharp\ParkanPlayground\X86DisassemblerTests" Presentation="&lt;X86DisassemblerTests&gt;" />
|
|
||||||
</And>
|
|
||||||
</SessionState></s:String>
|
|
||||||
|
|
||||||
</wpf:ResourceDictionary>
|
|
21
ParkanPlayground.slnx
Normal file
21
ParkanPlayground.slnx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/meta/">
|
||||||
|
<File Path="Directory.Build.props" />
|
||||||
|
<File Path="Directory.Packages.props" />
|
||||||
|
<File Path="README.md" />
|
||||||
|
</Folder>
|
||||||
|
<Project Path="Common\Common.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="CpDatLib\CpDatLib.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="MeshUnpacker/MeshUnpacker.csproj" />
|
||||||
|
<Project Path="MissionDataUnpacker/MissionDataUnpacker.csproj" />
|
||||||
|
<Project Path="MissionTmaLib/MissionTmaLib.csproj" />
|
||||||
|
<Project Path="NLUnpacker/NLUnpacker.csproj" />
|
||||||
|
<Project Path="NResLib/NResLib.csproj" />
|
||||||
|
<Project Path="NResUI/NResUI.csproj" />
|
||||||
|
<Project Path="ParkanPlayground/ParkanPlayground.csproj" />
|
||||||
|
<Project Path="ScrLib/ScrLib.csproj" />
|
||||||
|
<Project Path="TexmLib/TexmLib.csproj" />
|
||||||
|
<Project Path="TextureDecoder/TextureDecoder.csproj" />
|
||||||
|
<Project Path="VarsetLib/VarsetLib.csproj" />
|
||||||
|
<Project Path="Visualisator/Visualisator.csproj" />
|
||||||
|
</Solution>
|
77
ParkanPlayground/Msh01.cs
Normal file
77
ParkanPlayground/Msh01.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public static class Msh01
|
||||||
|
{
|
||||||
|
public static Msh01Component ReadComponent(FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var headerFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "01 00 00 00");
|
||||||
|
|
||||||
|
if (headerFileEntry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain header file (01)");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[headerFileEntry.ElementCount * headerFileEntry.ElementSize];
|
||||||
|
mshFs.Seek(headerFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
var dataSpan = data.AsSpan();
|
||||||
|
|
||||||
|
var elements = new List<SubMesh>((int)headerFileEntry.ElementCount);
|
||||||
|
for (var i = 0; i < headerFileEntry.ElementCount; i++)
|
||||||
|
{
|
||||||
|
var element = new SubMesh()
|
||||||
|
{
|
||||||
|
Type1 = dataSpan[i * headerFileEntry.ElementSize + 0],
|
||||||
|
Type2 = dataSpan[i * headerFileEntry.ElementSize + 1],
|
||||||
|
ParentIndex =
|
||||||
|
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 2)),
|
||||||
|
OffsetIntoFile13 =
|
||||||
|
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 4)),
|
||||||
|
IndexInFile08 =
|
||||||
|
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 6))
|
||||||
|
};
|
||||||
|
|
||||||
|
element.Lod[0] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 8));
|
||||||
|
element.Lod[1] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 10));
|
||||||
|
element.Lod[2] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 12));
|
||||||
|
element.Lod[3] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 14));
|
||||||
|
element.Lod[4] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 16));
|
||||||
|
element.Lod[5] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 18));
|
||||||
|
element.Lod[6] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 20));
|
||||||
|
element.Lod[7] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 22));
|
||||||
|
element.Lod[8] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 24));
|
||||||
|
element.Lod[9] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 26));
|
||||||
|
element.Lod[10] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 28));
|
||||||
|
element.Lod[11] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 30));
|
||||||
|
element.Lod[12] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 32));
|
||||||
|
element.Lod[13] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 34));
|
||||||
|
element.Lod[14] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 36));
|
||||||
|
elements.Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Msh01Component()
|
||||||
|
{
|
||||||
|
Elements = elements
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Msh01Component
|
||||||
|
{
|
||||||
|
public List<SubMesh> Elements { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubMesh
|
||||||
|
{
|
||||||
|
public byte Type1 { get; set; }
|
||||||
|
public byte Type2 { get; set; }
|
||||||
|
public short ParentIndex { get; set; }
|
||||||
|
public short OffsetIntoFile13 { get; set; }
|
||||||
|
public short IndexInFile08 { get; set; }
|
||||||
|
public ushort[] Lod { get; set; } = new ushort[15];
|
||||||
|
}
|
||||||
|
}
|
186
ParkanPlayground/Msh02.cs
Normal file
186
ParkanPlayground/Msh02.cs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using Common;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public static class Msh02
|
||||||
|
{
|
||||||
|
public static Msh02Component ReadComponent(FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var fileEntry = archive.Files.FirstOrDefault(x => x.FileType == "02 00 00 00");
|
||||||
|
|
||||||
|
if (fileEntry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain 02 component");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[fileEntry.FileLength];
|
||||||
|
mshFs.Seek(fileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
var header = data.AsSpan(0, 0x8c); // 140 bytes header
|
||||||
|
|
||||||
|
var center = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x60)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x64)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x68))
|
||||||
|
);
|
||||||
|
var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c));
|
||||||
|
|
||||||
|
var bb = new BoundingBox();
|
||||||
|
bb.Vec1 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8))
|
||||||
|
);
|
||||||
|
bb.Vec2 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20))
|
||||||
|
);
|
||||||
|
bb.Vec3 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32))
|
||||||
|
);
|
||||||
|
bb.Vec4 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44))
|
||||||
|
);
|
||||||
|
bb.Vec5 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56))
|
||||||
|
);
|
||||||
|
bb.Vec6 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68))
|
||||||
|
);
|
||||||
|
bb.Vec7 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80))
|
||||||
|
);
|
||||||
|
bb.Vec8 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92))
|
||||||
|
);
|
||||||
|
|
||||||
|
var bottom = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(112)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(116)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(120))
|
||||||
|
);
|
||||||
|
|
||||||
|
var top = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(124)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(128)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(132))
|
||||||
|
);
|
||||||
|
|
||||||
|
var xyRadius = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(136));
|
||||||
|
|
||||||
|
|
||||||
|
List<Msh02Element> elements = new List<Msh02Element>();
|
||||||
|
var skippedHeader = data.AsSpan(0x8c); // skip header
|
||||||
|
for (var i = 0; i < fileEntry.ElementCount; i++)
|
||||||
|
{
|
||||||
|
var element = new Msh02Element();
|
||||||
|
element.StartIndexIn07 =
|
||||||
|
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 0));
|
||||||
|
element.CountIn07 =
|
||||||
|
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 2));
|
||||||
|
element.StartOffsetIn0d =
|
||||||
|
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 4));
|
||||||
|
element.ByteLengthIn0D =
|
||||||
|
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 6));
|
||||||
|
element.LocalMinimum = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 8)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 12)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 16))
|
||||||
|
);
|
||||||
|
element.LocalMaximum = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 20)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 24)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 28))
|
||||||
|
);
|
||||||
|
element.Center = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 32)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 36)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 40))
|
||||||
|
);
|
||||||
|
element.Vector4 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 44)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 48)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 52))
|
||||||
|
);
|
||||||
|
element.Vector5 = new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 56)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 60)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 64))
|
||||||
|
);
|
||||||
|
elements.Add(element);
|
||||||
|
|
||||||
|
_ = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Msh02Component()
|
||||||
|
{
|
||||||
|
Header = new Msh02Header()
|
||||||
|
{
|
||||||
|
BoundingBox = bb,
|
||||||
|
Center = center,
|
||||||
|
CenterW = centerW,
|
||||||
|
Bottom = bottom,
|
||||||
|
Top = top,
|
||||||
|
XYRadius = xyRadius
|
||||||
|
},
|
||||||
|
Elements = elements
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Msh02Component
|
||||||
|
{
|
||||||
|
public Msh02Header Header { get; set; }
|
||||||
|
public List<Msh02Element> Elements { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Msh02Header
|
||||||
|
{
|
||||||
|
public BoundingBox BoundingBox { get; set; }
|
||||||
|
public Vector3 Center { get; set; }
|
||||||
|
public float CenterW { get; set; }
|
||||||
|
public Vector3 Bottom { get; set; }
|
||||||
|
public Vector3 Top { get; set; }
|
||||||
|
public float XYRadius { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Msh02Element
|
||||||
|
{
|
||||||
|
public ushort StartIndexIn07 { get; set; }
|
||||||
|
public ushort CountIn07 { get; set; }
|
||||||
|
public ushort StartOffsetIn0d { get; set; }
|
||||||
|
public ushort ByteLengthIn0D { get; set; }
|
||||||
|
public Vector3 LocalMinimum { get; set; }
|
||||||
|
public Vector3 LocalMaximum { get; set; }
|
||||||
|
public Vector3 Center { get; set; }
|
||||||
|
public Vector3 Vector4 { get; set; }
|
||||||
|
public Vector3 Vector5 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
35
ParkanPlayground/Msh03.cs
Normal file
35
ParkanPlayground/Msh03.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using Common;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public class Msh03
|
||||||
|
{
|
||||||
|
public static List<Vector3> ReadComponent(FileStream mshFs, NResArchive mshNres)
|
||||||
|
{
|
||||||
|
var verticesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "03 00 00 00");
|
||||||
|
|
||||||
|
if (verticesFileEntry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain vertices file (03)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verticesFileEntry.ElementSize != 12)
|
||||||
|
{
|
||||||
|
throw new Exception("Vertices file (03) element size is not 12");
|
||||||
|
}
|
||||||
|
|
||||||
|
var verticesFile = new byte[verticesFileEntry.ElementCount * verticesFileEntry.ElementSize];
|
||||||
|
mshFs.Seek(verticesFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(verticesFile, 0, verticesFile.Length);
|
||||||
|
|
||||||
|
var vertices = verticesFile.Chunk(12).Select(x => new Vector3(
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(0)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(4)),
|
||||||
|
BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(8))
|
||||||
|
)
|
||||||
|
).ToList();
|
||||||
|
return vertices;
|
||||||
|
}
|
||||||
|
}
|
32
ParkanPlayground/Msh06.cs
Normal file
32
ParkanPlayground/Msh06.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public static class Msh06
|
||||||
|
{
|
||||||
|
public static List<ushort> ReadComponent(
|
||||||
|
FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var entry = archive.Files.FirstOrDefault(x => x.FileType == "06 00 00 00");
|
||||||
|
|
||||||
|
if (entry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain file (06)");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||||
|
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
var elements = new List<ushort>((int)entry.ElementCount);
|
||||||
|
for (var i = 0; i < entry.ElementCount; i++)
|
||||||
|
{
|
||||||
|
elements.Add(
|
||||||
|
BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(i * 2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
}
|
53
ParkanPlayground/Msh07.cs
Normal file
53
ParkanPlayground/Msh07.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public static class Msh07
|
||||||
|
{
|
||||||
|
public static List<Msh07Element> ReadComponent(
|
||||||
|
FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var entry = archive.Files.FirstOrDefault(x => x.FileType == "07 00 00 00");
|
||||||
|
|
||||||
|
if (entry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain file (07)");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||||
|
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
var elementBytes = data.Chunk(16);
|
||||||
|
|
||||||
|
var elements = elementBytes.Select(x => new Msh07Element()
|
||||||
|
{
|
||||||
|
Flags = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)),
|
||||||
|
Magic02 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(2)),
|
||||||
|
Magic04 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(4)),
|
||||||
|
Magic06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)),
|
||||||
|
OffsetX = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(8)),
|
||||||
|
OffsetY = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(10)),
|
||||||
|
OffsetZ = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(12)),
|
||||||
|
Magic14 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(14)),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Msh07Element
|
||||||
|
{
|
||||||
|
public ushort Flags { get; set; }
|
||||||
|
public ushort Magic02 { get; set; }
|
||||||
|
public ushort Magic04 { get; set; }
|
||||||
|
public ushort Magic06 { get; set; }
|
||||||
|
// normalized vector X, need to divide by 32767 to get float in range -1..1
|
||||||
|
public short OffsetX { get; set; }
|
||||||
|
// normalized vector Y, need to divide by 32767 to get float in range -1..1
|
||||||
|
public short OffsetY { get; set; }
|
||||||
|
// normalized vector Z, need to divide by 32767 to get float in range -1..1
|
||||||
|
public short OffsetZ { get; set; }
|
||||||
|
public ushort Magic14 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
49
ParkanPlayground/Msh0A.cs
Normal file
49
ParkanPlayground/Msh0A.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Text;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public class Msh0A
|
||||||
|
{
|
||||||
|
public static List<string> ReadComponent(FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var aFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "0A 00 00 00");
|
||||||
|
|
||||||
|
if (aFileEntry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain 0A component");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[aFileEntry.FileLength];
|
||||||
|
mshFs.Seek(aFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
var strings = new List<string>();
|
||||||
|
while (pos < data.Length)
|
||||||
|
{
|
||||||
|
var len = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(pos));
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
pos += 4; // empty entry, no string attached
|
||||||
|
strings.Add(""); // add empty string
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// len is not 0, we need to read it
|
||||||
|
var strBytes = data.AsSpan(pos + 4, len);
|
||||||
|
var str = Encoding.UTF8.GetString(strBytes);
|
||||||
|
strings.Add(str);
|
||||||
|
pos += len + 4 + 1; // skip length prefix and string itself, +1, because it's null-terminated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strings.Count != aFileEntry.ElementCount)
|
||||||
|
{
|
||||||
|
throw new Exception("String count mismatch in 0A component");
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
}
|
55
ParkanPlayground/Msh0D.cs
Normal file
55
ParkanPlayground/Msh0D.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public static class Msh0D
|
||||||
|
{
|
||||||
|
public const int ElementSize = 20;
|
||||||
|
|
||||||
|
public static List<Msh0DElement> ReadComponent(
|
||||||
|
FileStream mshFs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var entry = archive.Files.FirstOrDefault(x => x.FileType == "0D 00 00 00");
|
||||||
|
|
||||||
|
if (entry is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Archive doesn't contain file (0D)");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||||
|
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
mshFs.ReadExactly(data, 0, data.Length);
|
||||||
|
|
||||||
|
var elementBytes = data.Chunk(ElementSize);
|
||||||
|
|
||||||
|
var elements = elementBytes.Select(x => new Msh0DElement()
|
||||||
|
{
|
||||||
|
Flags = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)),
|
||||||
|
Magic04 = x.AsSpan(4)[0],
|
||||||
|
Magic05 = x.AsSpan(5)[0],
|
||||||
|
Magic06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)),
|
||||||
|
CountOf06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)),
|
||||||
|
IndexInto06 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0xA)),
|
||||||
|
CountOf03 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xE)),
|
||||||
|
IndexInto03 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x10)),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Msh0DElement
|
||||||
|
{
|
||||||
|
public uint Flags { get; set; }
|
||||||
|
|
||||||
|
// Magic04 и Magic06 обрабатываются вместе
|
||||||
|
|
||||||
|
public byte Magic04 { get; set; }
|
||||||
|
public byte Magic05 { get; set; }
|
||||||
|
public ushort Magic06 { get; set; }
|
||||||
|
public ushort CountOf06 { get; set; }
|
||||||
|
public int IndexInto06 { get; set; }
|
||||||
|
public ushort CountOf03 { get; set; }
|
||||||
|
public int IndexInto03 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
229
ParkanPlayground/MshConverter.cs
Normal file
229
ParkanPlayground/MshConverter.cs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Common;
|
||||||
|
using NResLib;
|
||||||
|
|
||||||
|
namespace ParkanPlayground;
|
||||||
|
|
||||||
|
public class MshConverter
|
||||||
|
{
|
||||||
|
public void Convert(string mshPath)
|
||||||
|
{
|
||||||
|
var mshNresResult = NResParser.ReadFile(mshPath);
|
||||||
|
var mshNres = mshNresResult.Archive!;
|
||||||
|
|
||||||
|
using var mshFs = new FileStream(mshPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
|
||||||
|
var component01 = Msh01.ReadComponent(mshFs, mshNres);
|
||||||
|
var component02 = Msh02.ReadComponent(mshFs, mshNres);
|
||||||
|
var component0A = Msh0A.ReadComponent(mshFs, mshNres);
|
||||||
|
var component07 = Msh07.ReadComponent(mshFs, mshNres);
|
||||||
|
var component0D = Msh0D.ReadComponent(mshFs, mshNres);
|
||||||
|
|
||||||
|
// Triangle Vertex Indices
|
||||||
|
var component06 = Msh06.ReadComponent(mshFs, mshNres);
|
||||||
|
|
||||||
|
// vertices
|
||||||
|
var component03 = Msh03.ReadComponent(mshFs, mshNres);
|
||||||
|
|
||||||
|
_ = 5;
|
||||||
|
|
||||||
|
// --- Write OBJ ---
|
||||||
|
using var sw = new StreamWriter("test.obj", false, new UTF8Encoding(false));
|
||||||
|
|
||||||
|
foreach (var v in component03)
|
||||||
|
sw.WriteLine($"v {v.X:F8} {v.Y:F8} {v.Z:F8}");
|
||||||
|
|
||||||
|
var vertices = new List<Vector3>();
|
||||||
|
var faces = new List<(int, int, int)>(); // store indices into vertices list
|
||||||
|
|
||||||
|
// 01 - это части меша (Piece)
|
||||||
|
for (var pieceIndex = 0; pieceIndex < component01.Elements.Count; pieceIndex++)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Piece {pieceIndex}");
|
||||||
|
var piece01 = component01.Elements[pieceIndex];
|
||||||
|
// var state = (piece.State00 == 0xffff) ? 0 : piece.State00;
|
||||||
|
|
||||||
|
for (var lodIndex = 0; lodIndex < piece01.Lod.Length; lodIndex++)
|
||||||
|
{
|
||||||
|
var lod = piece01.Lod[lodIndex];
|
||||||
|
if (lod == 0xffff)
|
||||||
|
{
|
||||||
|
// Console.WriteLine($"Piece {pieceIndex} has lod -1 at {lodIndex}. Skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.WriteLine($"o piece_{pieceIndex}_lod_{lodIndex}");
|
||||||
|
// 02 - Submesh
|
||||||
|
var part02 = component02.Elements[lod];
|
||||||
|
|
||||||
|
int indexInto07 = part02.StartIndexIn07;
|
||||||
|
var comp07 = component07[indexInto07];
|
||||||
|
Console.WriteLine($"Lod {lodIndex}");
|
||||||
|
Console.WriteLine($"Comp07: {comp07.OffsetX}, {comp07.OffsetY}, {comp07.OffsetZ}");
|
||||||
|
|
||||||
|
var element0Dstart = part02.StartOffsetIn0d;
|
||||||
|
var element0Dcount = part02.ByteLengthIn0D;
|
||||||
|
|
||||||
|
// Console.WriteLine($"Started piece {pieceIndex}. LOD={lod}. 0D start={element0Dstart}, count={element0Dcount}");
|
||||||
|
|
||||||
|
for (var comp0Dindex = 0; comp0Dindex < element0Dcount; comp0Dindex++)
|
||||||
|
{
|
||||||
|
var element0D = component0D[element0Dstart + comp0Dindex];
|
||||||
|
|
||||||
|
var indexInto03 = element0D.IndexInto03;
|
||||||
|
var indexInto06 = element0D.IndexInto06; // indices
|
||||||
|
|
||||||
|
uint maxIndex = element0D.CountOf03;
|
||||||
|
uint indicesCount = element0D.CountOf06;
|
||||||
|
|
||||||
|
|
||||||
|
// Convert IndexInto06 to ushort array index (3 ushorts per triangle)
|
||||||
|
// Console.WriteLine($"Processing 0D element[{element0Dstart + comp0Dindex}]. IndexInto03={indexInto03}, IndexInto06={indexInto06}. Number of triangles={indicesCount}");
|
||||||
|
|
||||||
|
if (indicesCount != 0)
|
||||||
|
{
|
||||||
|
// sw.WriteLine($"o piece_{pieceIndex}_of_mesh_{comp0Dindex}");
|
||||||
|
|
||||||
|
for (int ind = 0; ind < indicesCount; ind += 3)
|
||||||
|
{
|
||||||
|
// Each triangle uses 3 consecutive ushorts in component06
|
||||||
|
|
||||||
|
// sw.WriteLine($"o piece_{pieceIndex}_of_mesh_{comp0Dindex}_tri_{ind}");
|
||||||
|
|
||||||
|
|
||||||
|
var i1 = indexInto03 + component06[indexInto06];
|
||||||
|
var i2 = indexInto03 + component06[indexInto06 + 1];
|
||||||
|
var i3 = indexInto03 + component06[indexInto06 + 2];
|
||||||
|
|
||||||
|
var v1 = component03[i1];
|
||||||
|
var v2 = component03[i2];
|
||||||
|
var v3 = component03[i3];
|
||||||
|
|
||||||
|
sw.WriteLine($"f {i1 + 1} {i2 + 1} {i3 + 1}");
|
||||||
|
|
||||||
|
// push vertices to global list
|
||||||
|
vertices.Add(v1);
|
||||||
|
vertices.Add(v2);
|
||||||
|
vertices.Add(v3);
|
||||||
|
|
||||||
|
int baseIndex = vertices.Count;
|
||||||
|
// record face (OBJ is 1-based indexing!)
|
||||||
|
faces.Add((baseIndex - 2, baseIndex - 1, baseIndex));
|
||||||
|
|
||||||
|
indexInto07++;
|
||||||
|
indexInto06 += 3; // step by 3 since each triangle uses 3 ushorts
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Face(Vector3 P1, Vector3 P2, Vector3 P3);
|
||||||
|
|
||||||
|
public static void ExportCube(string filePath, Vector3[] points)
|
||||||
|
{
|
||||||
|
if (points.Length != 8)
|
||||||
|
throw new ArgumentException("Cube must have exactly 8 points.");
|
||||||
|
|
||||||
|
using (StreamWriter writer = new StreamWriter(filePath))
|
||||||
|
{
|
||||||
|
// Write vertices
|
||||||
|
foreach (var p in points)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"v {p.X} {p.Y} {p.Z}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write faces (each face defined by 4 vertices, using 1-based indices)
|
||||||
|
int[][] faces = new int[][]
|
||||||
|
{
|
||||||
|
new int[] { 1, 2, 3, 4 }, // bottom
|
||||||
|
new int[] { 5, 6, 7, 8 }, // top
|
||||||
|
new int[] { 1, 2, 6, 5 }, // front
|
||||||
|
new int[] { 2, 3, 7, 6 }, // right
|
||||||
|
new int[] { 3, 4, 8, 7 }, // back
|
||||||
|
new int[] { 4, 1, 5, 8 } // left
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var f in faces)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"f {f[0]} {f[1]} {f[2]} {f[3]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ExportCubesAtPositions(string filePath, List<Vector3> centers, float size = 2f)
|
||||||
|
{
|
||||||
|
float half = size / 2f;
|
||||||
|
using (StreamWriter writer = new StreamWriter(filePath))
|
||||||
|
{
|
||||||
|
int vertexOffset = 0;
|
||||||
|
|
||||||
|
foreach (var c in centers)
|
||||||
|
{
|
||||||
|
// Generate 8 vertices for this cube
|
||||||
|
Vector3[] vertices = new Vector3[]
|
||||||
|
{
|
||||||
|
new Vector3(c.X - half, c.Y - half, c.Z - half),
|
||||||
|
new Vector3(c.X + half, c.Y - half, c.Z - half),
|
||||||
|
new Vector3(c.X + half, c.Y - half, c.Z + half),
|
||||||
|
new Vector3(c.X - half, c.Y - half, c.Z + half),
|
||||||
|
|
||||||
|
new Vector3(c.X - half, c.Y + half, c.Z - half),
|
||||||
|
new Vector3(c.X + half, c.Y + half, c.Z - half),
|
||||||
|
new Vector3(c.X + half, c.Y + half, c.Z + half),
|
||||||
|
new Vector3(c.X - half, c.Y + half, c.Z + half)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write vertices
|
||||||
|
foreach (var v in vertices)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"v {v.X} {v.Y} {v.Z}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define faces (1-based indices, counter-clockwise)
|
||||||
|
int[][] faces = new int[][]
|
||||||
|
{
|
||||||
|
new int[] { 1, 2, 3, 4 }, // bottom
|
||||||
|
new int[] { 5, 6, 7, 8 }, // top
|
||||||
|
new int[] { 1, 2, 6, 5 }, // front
|
||||||
|
new int[] { 2, 3, 7, 6 }, // right
|
||||||
|
new int[] { 3, 4, 8, 7 }, // back
|
||||||
|
new int[] { 4, 1, 5, 8 } // left
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write faces with offset
|
||||||
|
foreach (var f in faces)
|
||||||
|
{
|
||||||
|
writer.WriteLine(
|
||||||
|
$"f {f[0] + vertexOffset} {f[1] + vertexOffset} {f[2] + vertexOffset} {f[3] + vertexOffset}");
|
||||||
|
}
|
||||||
|
|
||||||
|
vertexOffset += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Export(string filePath, IEnumerable<Vector3> vertices, List<IndexedEdge> edges)
|
||||||
|
{
|
||||||
|
using (var writer = new StreamWriter(filePath))
|
||||||
|
{
|
||||||
|
writer.WriteLine("# Exported OBJ file");
|
||||||
|
|
||||||
|
// Write vertices
|
||||||
|
foreach (var v in vertices)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"v {v.X:F2} {v.Y:F2} {v.Z:F2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write edges as lines ("l" elements in .obj format)
|
||||||
|
foreach (var e in edges)
|
||||||
|
{
|
||||||
|
// OBJ uses 1-based indexing
|
||||||
|
writer.WriteLine($"l {e.Index1 + 1} {e.Index2 + 1}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,19 +2,13 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="SharpDisasm" Version="1.1.11" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,116 +1,17 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Numerics;
|
using Common;
|
||||||
using System.Text.Json;
|
using MissionTmaLib.Parsing;
|
||||||
using ScrLib;
|
using NResLib;
|
||||||
using SharpDisasm;
|
using ParkanPlayground;
|
||||||
using VarsetLib;
|
|
||||||
|
|
||||||
|
// var cpDatEntryConverter = new CpDatEntryConverter();
|
||||||
|
// cpDatEntryConverter.Convert();
|
||||||
|
|
||||||
// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\default.scr";
|
var converter = new MshConverter();
|
||||||
// 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
|
|
||||||
// );
|
|
||||||
|
|
||||||
// var items = VarsetParser.Parse(path);
|
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");
|
||||||
// Console.WriteLine(items.Count);
|
// converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\73_fr_m_brige.msh");
|
||||||
|
// converter.Convert("E:\\ParkanUnpacked\\intsys.rlb\\277_MESH_o_pws_l_01.msh");
|
||||||
// Span<byte> flt = stackalloc byte[4];
|
// converter.Convert("E:\\ParkanUnpacked\\static.rlb\\2_MESH_s_stn_0_01.msh");
|
||||||
// flt[0] = 0x7f;
|
// converter.Convert("E:\\ParkanUnpacked\\bases.rlb\\25_MESH_R_H_02.msh");
|
||||||
// flt[1] = 0x7f;
|
|
||||||
// flt[2] = 0xff;
|
|
||||||
// flt[3] = 0xff;
|
|
||||||
// var f = BinaryPrimitives.ReadSingleBigEndian(flt);
|
|
||||||
//
|
|
||||||
// Console.WriteLine(f);
|
|
||||||
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MisLoad.dll";
|
|
||||||
var path = "C:\\ParkanUnpacked\\Land.msh\\2_03 00 00 00_Land.bin";
|
|
||||||
|
|
||||||
var fs = new FileStream(path, FileMode.Open);
|
|
||||||
var outputFs = new FileStream("Land.obj", FileMode.Create);
|
|
||||||
var sw = new StreamWriter(outputFs);
|
|
||||||
|
|
||||||
List<Vector3D> points = [];
|
|
||||||
var count = 0;
|
|
||||||
while (fs.Position < fs.Length)
|
|
||||||
{
|
|
||||||
var x = fs.ReadFloatLittleEndian();
|
|
||||||
var y = fs.ReadFloatLittleEndian();
|
|
||||||
var z = fs.ReadFloatLittleEndian();
|
|
||||||
|
|
||||||
var vertex = new Vector3D(x, y, z);
|
|
||||||
sw.WriteLine($"v {x} {y} {z}");
|
|
||||||
|
|
||||||
var seenIndex = points.FindIndex(vec => vec == vertex);
|
|
||||||
if (seenIndex != -1)
|
|
||||||
{
|
|
||||||
vertex.Duplicates = seenIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
points.Add(vertex);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllText("human-readable.json", JsonSerializer.Serialize(points, new JsonSerializerOptions()
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
Console.WriteLine($"Total vertices: {count}");
|
|
||||||
|
|
||||||
|
|
||||||
// for (int i = 0; i < count / 4; i++)
|
|
||||||
|
|
||||||
public record Vector3D(float X, float Y, float Z)
|
|
||||||
{
|
|
||||||
public int Duplicates { get; set; }
|
|
||||||
}
|
|
||||||
// var indices = string.Join(" ", Enumerable.Range(1, count));
|
|
||||||
//
|
|
||||||
// sw.WriteLine($"l {indices}");
|
|
||||||
|
|
||||||
//
|
|
||||||
// fs.Seek(0x1000, SeekOrigin.Begin);
|
|
||||||
//
|
|
||||||
// byte[] buf = new byte[34];
|
|
||||||
// fs.ReadExactly(buf);
|
|
||||||
//
|
|
||||||
// var disassembler = new SharpDisasm.Disassembler(buf, ArchitectureMode.x86_32);
|
|
||||||
// foreach (var instruction in disassembler.Disassemble())
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"{instruction.PC - instruction.Offset}: {instruction}");
|
|
||||||
//
|
|
||||||
// new Instruction()
|
|
||||||
// {
|
|
||||||
// Action = instruction.Mnemonic.ToString(),
|
|
||||||
// Arguments = {instruction.Operands[0].ToString()}
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
public class Instruction
|
|
||||||
{
|
|
||||||
public string Action { get; set; } = "";
|
|
||||||
|
|
||||||
public List<string> Arguments { get; set; } = [];
|
|
||||||
}
|
|
388
README.md
388
README.md
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" />
|
<img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" />
|
||||||
|
<img width="817" height="376" alt="Image" src="https://github.com/user-attachments/assets/c4959106-9da4-4c78-a2b7-6c94e360a89e" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## Сборка проекта
|
## Сборка проекта
|
||||||
|
|
||||||
Проект написан на C# под `.NET 8`
|
Проект написан на C# под `.NET 9`
|
||||||
|
|
||||||
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
|
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
|
||||||
|
|
||||||
@@ -14,13 +16,13 @@
|
|||||||
|
|
||||||
### Состояние проекта
|
### Состояние проекта
|
||||||
|
|
||||||
- Распаковка всех `NRes` файлов
|
- Поддержка всех `NRes` файлов - звуки, музыка, текстуры, карты и другие файлы. Есть документация.
|
||||||
- Распаковка всех `TEXM` текстур
|
- Поддержка всех `TEXM` текстур. Есть документация.
|
||||||
+ формат 565 работает некорректно
|
- Поддержка файлов миссий `.tma`.
|
||||||
+ не понятно назначение двух магических чисел в заголовке
|
- Поддержка шрифтов TFNT.
|
||||||
- Распаковка данных миссии `.tma`. Пока работает чтение ареалов и кланов.
|
- Поддержка файлов скриптов `.scr`.
|
||||||
- Распаковка файла NL. Есть только декодирование заголовка. Формат кажется не используется игрой, а реверс бинарника игры то ещё занятие.
|
- Поддержка файлов параметров `.var`.
|
||||||
- Распаковка текстуры шрифта формата TFNT. Встроен прямо в UI. По сути шрифт это 4116 байт заголовка и текстура TEXM сразу после.
|
- Поддержка файлов схем объектов `.dat`.
|
||||||
|
|
||||||
|
|
||||||
### Структура проекта
|
### Структура проекта
|
||||||
@@ -34,47 +36,6 @@
|
|||||||
|
|
||||||
Я конечно стараюсь, но ничего не обещаю.
|
Я конечно стараюсь, но ничего не обещаю.
|
||||||
|
|
||||||
#### NResUI
|
|
||||||
|
|
||||||
UI приложение на OpenGL + ImGui.
|
|
||||||
|
|
||||||
Туда постепенно добавляю логику.
|
|
||||||
|
|
||||||
#### NResLib
|
|
||||||
|
|
||||||
Библиотека распаковки формата NRes и всех файлов, которые им запакованы.
|
|
||||||
|
|
||||||
Есть логика импорта и экспорта. Работа не завершена, но уже сейчас можно читать любые архивы такого формата.
|
|
||||||
|
|
||||||
#### TexmLib
|
|
||||||
|
|
||||||
Библиотека распаковки текстур TEXM.
|
|
||||||
|
|
||||||
Есть логика импорта и экспорта, хотя к UI последняя не подключена.
|
|
||||||
|
|
||||||
#### NLUnpacker
|
|
||||||
|
|
||||||
Приложение распаковки NL.
|
|
||||||
|
|
||||||
Работа приостановлена, т.к. кажется игра не использует эти файлы.
|
|
||||||
|
|
||||||
#### MissionDataUnpacker
|
|
||||||
|
|
||||||
Приложение распаковки миссий `.tma`.
|
|
||||||
|
|
||||||
Готово чтение ареалов и кланов. Пока в процессе.
|
|
||||||
|
|
||||||
#### ParkanPlayground
|
|
||||||
|
|
||||||
Пустой проект, использую для локальных тестов.
|
|
||||||
|
|
||||||
#### TextureDecoder
|
|
||||||
|
|
||||||
Приложение для экспорта текстур TEXM.
|
|
||||||
|
|
||||||
Изначально тут игрался с текстурами.
|
|
||||||
|
|
||||||
|
|
||||||
## Для Reverse Engineering-а использую Ghidra
|
## Для Reverse Engineering-а использую Ghidra
|
||||||
|
|
||||||
### Наблюдения
|
### Наблюдения
|
||||||
@@ -86,6 +47,335 @@ UI приложение на OpenGL + ImGui.
|
|||||||
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
|
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
|
||||||
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`.
|
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`.
|
||||||
|
|
||||||
|
## Как быстро найти текст среди всех файлов игры
|
||||||
|
|
||||||
|
```shell
|
||||||
|
grep -rl --include="*" "s_tree_05" .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как быстро найти байты среди всех файлов игры
|
||||||
|
|
||||||
|
```shell
|
||||||
|
grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как работает игра
|
||||||
|
|
||||||
|
Главное меню:
|
||||||
|
|
||||||
|
Игра сканирует хардкод папку `missions` на наличие файлов миссий. (буквально 01, 02, 03 и т.д.)
|
||||||
|
|
||||||
|
Сначала игра читает название миссии из файла `descr` - тут название для меню.
|
||||||
|
|
||||||
|
- Одиночные игры - `missions/single.{index}/descr`
|
||||||
|
- Тренировочные миссии - `missions/tutorial.{index}/descr`
|
||||||
|
- Кампания - `missions/campaign/campaign.{index1}/descr`
|
||||||
|
* Далее используются подпапки - `missions/campaign/campaign.{index1}/mission.{index2}/descr`
|
||||||
|
|
||||||
|
Как только игра не находит файл `descr`, заканчивается итерация по папкам (понял, т.к. пробуется файл 05 - он не существует).
|
||||||
|
|
||||||
|
Загрузка миссии:
|
||||||
|
|
||||||
|
Читается файл `ui/game_resources.cfg`
|
||||||
|
Из этого файла загружаются ресурсы
|
||||||
|
- `library = "ui\\ui.lib"` - загружается файл `ui.lib`
|
||||||
|
- `library = "ui\\font.lib"` - загружается файл `font.lib`
|
||||||
|
- `library = "sounds.lib"` - загружается файл `sounds.lib`
|
||||||
|
- `library = "voices.lib"` - загружается файл `voices.lib`
|
||||||
|
|
||||||
|
Затем игра читает `save/saveslots.cfg` - тут слоты сохранения
|
||||||
|
|
||||||
|
Затем `Comp.ini` - тут системные функции, которые используются для загрузки объектов.
|
||||||
|
|
||||||
|
```
|
||||||
|
IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Host.url` - этого файла нет
|
||||||
|
- `palettes.lib` - тут палитры, но этот NRes пустой
|
||||||
|
- `system.rlb` - не понятно что
|
||||||
|
- `Textures.lib` - тут текстуры
|
||||||
|
- `Material.lib` - тут какие-то материалы - не понятно
|
||||||
|
- `LightMap.lib` - видимо это карты освещения - не понятно
|
||||||
|
- `sys.lib` - не понятно
|
||||||
|
|
||||||
|
|
||||||
|
- `ScanCode.dsc` - текстовый файл с мапом клавиш
|
||||||
|
- `command.dsc` - текстовый файл с мапом клавиш
|
||||||
|
|
||||||
|
Тут видимо идёт конфигурация ввода
|
||||||
|
|
||||||
|
- `table_1.man` - текстовый файл
|
||||||
|
- `table_2.man` - текстовый файл
|
||||||
|
- `hero.man` - текстовый файл
|
||||||
|
- `addition.man` - текстовый файл
|
||||||
|
- Снова `table_1.man`
|
||||||
|
- Снова `table_1.man`
|
||||||
|
- `M1.tbl` - текстовый файл
|
||||||
|
- Снова `table_2.man`
|
||||||
|
- Снова `table_2.man`
|
||||||
|
- `M2.tbl` - текстовый файл
|
||||||
|
- Снова `hero.man`
|
||||||
|
- Снова `hero.man`
|
||||||
|
- `HERO.TBL`
|
||||||
|
- Снова `addition.man`
|
||||||
|
|
||||||
|
|
||||||
|
- `ui/hq.cfg`
|
||||||
|
- Снова `ui/hq.cfg`
|
||||||
|
|
||||||
|
|
||||||
|
Дальше непосредственно читается миссия
|
||||||
|
|
||||||
|
- `mission.cfg` - метадата миссии
|
||||||
|
- `units\\units\\prebld\\scr_pre1.dat` из метаданных `object prebuild` - `cp` файл (грузятся подряд все)
|
||||||
|
- Опять `ui/hq.cfg`
|
||||||
|
- `mistips.mis` - описание для игрока (экран F1)
|
||||||
|
- `scancode.dsc` - хз
|
||||||
|
- `command.dsc` - хз
|
||||||
|
- `ui_hero.man` - хз
|
||||||
|
- `ui_bots.man` - хз
|
||||||
|
- `ui_hq.man` - хз
|
||||||
|
- `ui_other.man` - хз
|
||||||
|
- Цикл чтения курсоров
|
||||||
|
* `ui/cursor.cfg` - тут настройки курсора.
|
||||||
|
* `ui/{name}` - курсор
|
||||||
|
- Снова `mission.cfg` - метадата миссии
|
||||||
|
- `descr` - название
|
||||||
|
- `data/textres.cfg` - конфиг текстов
|
||||||
|
- Снова `mission.cfg` - метадата миссии
|
||||||
|
- Ещё раз `mission.cfg` - метадата миссии
|
||||||
|
- `ui/minimap.lib` - NRes с текстурами миникарты.
|
||||||
|
- `messages.cfg` - Tutorial messages
|
||||||
|
|
||||||
|
УРА НАКОНЕЦ-ТО `data.tma`
|
||||||
|
|
||||||
|
- Из `.tma` берётся LAND строка (я её так назвал)
|
||||||
|
- `DATA\\MAPS\\SC_3\\land1.wea`
|
||||||
|
- `DATA\\MAPS\\SC_3\\land2.wea`
|
||||||
|
- `BuildDat.lst` - Behaviour will use these schemes to Build Fortification
|
||||||
|
- `DATA\\MAPS\\SC_3\\land.map`
|
||||||
|
- `DATA\\MAPS\\SC_3\\land.msh`
|
||||||
|
|
||||||
|
|
||||||
|
- `effects.rlb`
|
||||||
|
|
||||||
|
Цикл по кланам из `.tma`
|
||||||
|
- `MISSIONS\\SCRIPTS\\screampl.scr`
|
||||||
|
- `varset.var`
|
||||||
|
- `MISSIONS\\SCRIPTS\\varset.var`
|
||||||
|
- `MISSIONS\\SCRIPTS\\screampl.fml`
|
||||||
|
|
||||||
|
|
||||||
|
- `missions/single.01/sky.ske`
|
||||||
|
- `missions/single.01/sky.wea`
|
||||||
|
|
||||||
|
Дальше начинаются объекты игры
|
||||||
|
- `"UNITS\\BUILDS\\BUNKER\\mbunk01.dat"` - cp файл
|
||||||
|
|
||||||
|
## Загрузка `cp` файлов
|
||||||
|
|
||||||
|
`cp` файл - схема. Он содержит дерево частей объекта.
|
||||||
|
|
||||||
|
`cp` файл читается в `ArealMap.dll/CreateObjectFromScheme`
|
||||||
|
|
||||||
|
В зависимости от типа объекта внутри схемы (байты 4..8) выбирается функция, с помощью которой загружается схема.
|
||||||
|
|
||||||
|
Функция выбирается на основе файла `Comp.ini`.
|
||||||
|
- Для ClassBuilding (0x80000000) - вызывается функция c классом 3 (по таблице ниже Building).
|
||||||
|
- Для всех остальных - функция с классом 4 (по таблице ниже Agent).
|
||||||
|
|
||||||
|
На основе файла `Comp.ini` и первом вызове внутри функции `World3D.dll/CreateObject` ремаппинг id:
|
||||||
|
|
||||||
|
| Class ID | ClassName | Function |
|
||||||
|
|:----------:|:-------------:|--------------------------------|
|
||||||
|
| 1 | Landscape | `terrain.dll LoadLandscape` |
|
||||||
|
| 2 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 3 | Building | `terrain.dll LoadBuilding` |
|
||||||
|
| 4 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 5 | Camera | `terrain.dll LoadCamera` |
|
||||||
|
| 7 | Atmospehere | `terrain.dll CreateAtmosphere` |
|
||||||
|
| 9 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 10 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
| 11 | Research | `misload.dll LoadResearch` |
|
||||||
|
| 12 | Agent | `animesh.dll LoadAgent` |
|
||||||
|
|
||||||
|
Будет дополняться по мере реверса.
|
||||||
|
|
||||||
|
Всем этим функциям передаётся `nres_file_name, nres_entry_name, 0, player_id`
|
||||||
|
|
||||||
|
## `fr FORT` файл
|
||||||
|
|
||||||
|
Всегда 0x80 байт
|
||||||
|
Содержит 2 ссылки на файлы:
|
||||||
|
- `.bas`
|
||||||
|
- `.ctl` - вызывается `LoadAgent`
|
||||||
|
|
||||||
|
## `.msh`
|
||||||
|
|
||||||
|
### Описание ниже валидно только для моделей роботов и зданий.
|
||||||
|
##### Land.msh использует другой формат, хотя 03 файл это всё ещё точки.
|
||||||
|
|
||||||
|
Загружается в `AniMesh.dll/LoadAniMesh`
|
||||||
|
|
||||||
|
- Тип 01 - заголовок. Он хранит список деталей (submesh) в разных LOD
|
||||||
|
```
|
||||||
|
нулевому элементу добавляется флаг 0x1000000
|
||||||
|
Содержит 2 ссылки на файлы анимаций (короткие - файл 13, длинные - файл 08)
|
||||||
|
Если интерполируется анимация -0.5s короче чем magic1 у файла 13
|
||||||
|
И у файла есть OffsetIntoFile13
|
||||||
|
И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда)
|
||||||
|
Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт)
|
||||||
|
```
|
||||||
|
- Тип 02 - описание одного LOD Submesh
|
||||||
|
```
|
||||||
|
Вначале идёт заголовок 0x8C (140) байт
|
||||||
|
В заголовке:
|
||||||
|
8 Vector3 (x,y,z) - bounding box
|
||||||
|
1 Vector4 - center
|
||||||
|
1 Vector3 - bottom
|
||||||
|
1 Vector3 - top
|
||||||
|
1 float - xy_radius
|
||||||
|
Далее инфа про куски меша
|
||||||
|
```
|
||||||
|
- Тип 03 - это вершины (vertex)
|
||||||
|
- Тип 06 - индексы треугольников в файле 03
|
||||||
|
- Тип 04 - скорее всего какие-то цвета RGBA или типа того
|
||||||
|
- Тип 08 - меш-анимации (см файл 01)
|
||||||
|
```
|
||||||
|
Индексируется по IndexInFile08 из файла 01 либо по файлу 13 через OffsetIntoFile13
|
||||||
|
Структура:
|
||||||
|
Vector3 position;
|
||||||
|
float time; // содержит только целые секунды
|
||||||
|
short rotation_x; // делится на 32767
|
||||||
|
short rotation_y; // делится на 32767
|
||||||
|
short rotation_z; // делится на 32767
|
||||||
|
short rotation_w; // делится на 32767
|
||||||
|
---
|
||||||
|
Игра интерполирует анимацию между текущим стейтом и следующим по time.
|
||||||
|
Если время интерполяции совпадает с исходным time, жёстко берётся первый стейт из 0x13.
|
||||||
|
Если время интерполяции совпадает с конечным time, жёстко берётся второй стейт из 0x13.
|
||||||
|
Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time)
|
||||||
|
```
|
||||||
|
- Тип 12 - microtexture mapping
|
||||||
|
- Тип 13 - короткие меш-анимации (почему я это не дописал?)
|
||||||
|
```
|
||||||
|
Буквально (hex)
|
||||||
|
00 01 01 02 ...
|
||||||
|
```
|
||||||
|
- Тип 0A - ссылка на части меша, не упакованные в текущий меш (например у бункера 4 и 5 части хранятся в parts.rlb)
|
||||||
|
```
|
||||||
|
Не имеет фиксированной длины. Хранит строки в следующем формате.
|
||||||
|
Игра обращается по индексу, пропуская суммарную длину и пропуская 4 байта на каждую строку (длина).
|
||||||
|
т.е. буквально файл выглядит так
|
||||||
|
00 00 00 00 - пустая строка
|
||||||
|
03 00 00 00 - длина строки 1
|
||||||
|
73 74 72 00 - строка "str" + null terminator
|
||||||
|
.. и повторяется до конца файла
|
||||||
|
Кол-во элементов из файла 01 должно быть равно кол-ву строк в этом файле, хотя игра это не проверяет.
|
||||||
|
Если у элемента эта строка равна "central", ему выставляется флаг (flag |= 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `.wea`
|
||||||
|
|
||||||
|
Загружается в `World3D.dll/LoadMatManager`
|
||||||
|
|
||||||
|
По сути это текстовый файл состоящий из 2 частей:
|
||||||
|
- Материалы
|
||||||
|
```
|
||||||
|
{count}
|
||||||
|
{id} {name}
|
||||||
|
```
|
||||||
|
- Карты освещения
|
||||||
|
```
|
||||||
|
LIGHTMAPS
|
||||||
|
{count}
|
||||||
|
{id} {name}
|
||||||
|
```
|
||||||
|
|
||||||
|
Может как-то анимироваться. Как - пока не понятно.
|
||||||
|
|
||||||
|
|
||||||
|
# Внутренняя система ID
|
||||||
|
|
||||||
|
- `1` -
|
||||||
|
- `4` - IShader
|
||||||
|
- `5` - ITerrain
|
||||||
|
- `6` - IGameObject (0x138)
|
||||||
|
- `7` - IShadeConfig (у меня в папке с игрой его не оказалось)
|
||||||
|
- `8` - ICamera
|
||||||
|
- `9` - IQueue
|
||||||
|
- `10` - IControl
|
||||||
|
- `0xb` - IAnimation
|
||||||
|
- `0xd` - IMatManager
|
||||||
|
- `0xe` - ILightManager
|
||||||
|
- `0x10` - IBehavior
|
||||||
|
- `0x11` - IBasement
|
||||||
|
- `0x12` - ICamera2 или IBufferingCamera
|
||||||
|
- `0x13` - IEffectManager
|
||||||
|
- `0x14` - IPosition
|
||||||
|
- `0x15` - IAgent
|
||||||
|
- `0x16` - ILifeSystem
|
||||||
|
- `0x17` - IBuilding - точно он, т.к. ArealMap.CreateObject на него проверяет
|
||||||
|
- `0x18` - IMesh2
|
||||||
|
- `0x19` - IManManager
|
||||||
|
- `0x20` - IJointMesh
|
||||||
|
- `0x21` - IShade
|
||||||
|
- `0x23` - IGameSettings
|
||||||
|
- `0x24` - IGameObject2
|
||||||
|
- `0x25` - unknown (implemented by AniMesh)
|
||||||
|
- `0x26` - unknown (implemented by AniMesh)
|
||||||
|
- `0x28` - ICollObject
|
||||||
|
- `0x101` - 3DRender
|
||||||
|
- `0x105` - NResFile
|
||||||
|
- `0x106` - NResFileMetadata
|
||||||
|
- `0x201` - IWizard
|
||||||
|
- `0x202` - IItemManager
|
||||||
|
- `0x203` - ICollManager
|
||||||
|
- `0x301` - IArealMap
|
||||||
|
- `0x302` - ISystemArealMap
|
||||||
|
- `0x303` - IHallway
|
||||||
|
- `0x304` - Distributor
|
||||||
|
- `0x401` - ISuperAI
|
||||||
|
- `0x501` - MissionData
|
||||||
|
- `0x502` - ResTree
|
||||||
|
- `0x700` - NetWatcher
|
||||||
|
- `0x701` - INetworkInterface
|
||||||
|
- `0x10d` - CreateVertexBufferData
|
||||||
|
|
||||||
|
|
||||||
|
## Опции
|
||||||
|
|
||||||
|
World3D.dll содержит функцию CreateGameSettings.
|
||||||
|
Она создаёт объект настроек и далее вызывает методы в соседних библиотеках.
|
||||||
|
- Terrain.dll - InitializeSettings
|
||||||
|
- Effect.dll - InitializeSettings
|
||||||
|
- Control.dll - InitializeSettings
|
||||||
|
|
||||||
|
Остальные наверное не трогают настройки.
|
||||||
|
|
||||||
|
| Resource ID | wOptionID | Name | Default | Description |
|
||||||
|
|:-----------:|:---------------:|:--------------------------:|:-------:|--------------------|
|
||||||
|
| 1 | 100 (0x64) | "Texture detail" | | |
|
||||||
|
| 2 | 101 (0x65) | "3D Sound" | | |
|
||||||
|
| 3 | 102 (0x66) | "Mouse sensitivity" | | |
|
||||||
|
| 4 | 103 (0x67) | "Joystick sensitivity" | | |
|
||||||
|
| 5 | !not a setting! | "Illegal wOptionID" | | |
|
||||||
|
| 6 | 104 (0x68) | "Wait for retrace" | | |
|
||||||
|
| 7 | 105 (0x69) | "Inverse mouse X" | | |
|
||||||
|
| 8 | 106 (0x6a) | "Inverse mouse Y" | | |
|
||||||
|
| 9 | 107 (0x6b) | "Inverse joystick X" | | |
|
||||||
|
| 10 | 108 (0x6c) | "Inverse joystick Y" | | |
|
||||||
|
| 11 | 109 (0x6d) | "Use BumpMapping" | | |
|
||||||
|
| 12 | 110 (0x6e) | "3D Sound quality" | | |
|
||||||
|
| 13 | 90 (0x5a) | "Reverse sound" | | |
|
||||||
|
| 14 | 91 (0x5b) | "Sound buffer frequency" | | |
|
||||||
|
| 15 | 92 (0x5c) | "Play sound buffer always" | | |
|
||||||
|
| 16 | 93 (0x5d) | "Select best sound device" | | |
|
||||||
|
| ---- | 30 (0x1e) | ShadeConfig | | из файла shade.cfg |
|
||||||
|
| ---- | (0x8001e) | | | добавляет AniMesh |
|
||||||
|
|
||||||
|
|
||||||
## Контакты
|
## Контакты
|
||||||
|
|
||||||
Вы можете связаться со мной в [Telegram](https://t.me/bird_egop).
|
Вы можете связаться со мной в [Telegram](https://t.me/bird_egop).
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
<PropertyGroup>
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
</ItemGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
namespace ScrLib;
|
using Common;
|
||||||
|
|
||||||
|
namespace ScrLib;
|
||||||
|
|
||||||
public class ScrParser
|
public class ScrParser
|
||||||
{
|
{
|
||||||
|
@@ -1,44 +0,0 @@
|
|||||||
using X86Disassembler.X86;
|
|
||||||
|
|
||||||
namespace TestDisassembler;
|
|
||||||
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
// Test the specific byte sequence that's causing issues
|
|
||||||
byte[] codeBytes = HexStringToByteArray("816B1078563412");
|
|
||||||
|
|
||||||
// Create a disassembler with the code
|
|
||||||
Disassembler disassembler = new Disassembler(codeBytes, 0x1000);
|
|
||||||
|
|
||||||
// Disassemble the code
|
|
||||||
var instructions = disassembler.Disassemble();
|
|
||||||
|
|
||||||
// Print the number of instructions
|
|
||||||
Console.WriteLine($"Number of instructions: {instructions.Count}");
|
|
||||||
|
|
||||||
// Print each instruction
|
|
||||||
for (int i = 0; i < instructions.Count; i++)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Instruction {i+1}: {instructions[i].Mnemonic} {instructions[i].Operands}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] HexStringToByteArray(string hex)
|
|
||||||
{
|
|
||||||
// Remove any non-hex characters
|
|
||||||
hex = hex.Replace(" ", "").Replace("-", "");
|
|
||||||
|
|
||||||
// Create a byte array
|
|
||||||
byte[] bytes = new byte[hex.Length / 2];
|
|
||||||
|
|
||||||
// Convert each pair of hex characters to a byte
|
|
||||||
for (int i = 0; i < hex.Length; i += 2)
|
|
||||||
{
|
|
||||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
using X86Disassembler.X86;
|
|
||||||
|
|
||||||
namespace TestDisassembler;
|
|
||||||
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
// Test the specific byte sequence with segment override prefix that's causing issues
|
|
||||||
byte[] codeBytes = HexStringToByteArray("26FF7510");
|
|
||||||
|
|
||||||
// Create a disassembler with the code
|
|
||||||
Disassembler disassembler = new Disassembler(codeBytes, 0x1000);
|
|
||||||
|
|
||||||
// Disassemble the code
|
|
||||||
var instructions = disassembler.Disassemble();
|
|
||||||
|
|
||||||
// Print the number of instructions
|
|
||||||
Console.WriteLine($"Number of instructions: {instructions.Count}");
|
|
||||||
|
|
||||||
// Print each instruction
|
|
||||||
for (int i = 0; i < instructions.Count; i++)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Instruction {i+1}: {instructions[i].Mnemonic} {instructions[i].Operands}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] HexStringToByteArray(string hex)
|
|
||||||
{
|
|
||||||
// Remove any non-hex characters
|
|
||||||
hex = hex.Replace(" ", "").Replace("-", "");
|
|
||||||
|
|
||||||
// Create a byte array
|
|
||||||
byte[] bytes = new byte[hex.Length / 2];
|
|
||||||
|
|
||||||
// Convert each pair of hex characters to a byte
|
|
||||||
for (int i = 0; i < hex.Length; i += 2)
|
|
||||||
{
|
|
||||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\X86Disassembler\X86Disassembler.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@@ -1,13 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
<PackageReference Include="SixLabors.ImageSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -2,17 +2,10 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
<PackageReference Include="SixLabors.ImageSharp" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -1,9 +1,3 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -2,17 +2,14 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
|
<PackageReference Include="NativeFileDialogSharp" />
|
||||||
<PackageReference Include="Silk.NET" Version="2.22.0" />
|
<PackageReference Include="Silk.NET" />
|
||||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
namespace X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a disassembled function with its control flow graph
|
|
||||||
/// </summary>
|
|
||||||
public class AsmFunction
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The starting address of the function
|
|
||||||
/// </summary>
|
|
||||||
public ulong Address { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The list of basic blocks that make up the function
|
|
||||||
/// </summary>
|
|
||||||
public List<InstructionBlock> Blocks { get; set; } = [];
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Address:X8}\n{string.Join("\n", Blocks)}";
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,554 +0,0 @@
|
|||||||
using X86Disassembler.X86;
|
|
||||||
using X86Disassembler.X86.Operands;
|
|
||||||
|
|
||||||
namespace X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disassembles code into basic blocks by following control flow instructions.
|
|
||||||
/// A basic block is a sequence of instructions with a single entry point (the first instruction)
|
|
||||||
/// and a single exit point (the last instruction, typically a jump or return).
|
|
||||||
/// </summary>
|
|
||||||
public class BlockDisassembler
|
|
||||||
{
|
|
||||||
// The buffer containing the code to disassemble
|
|
||||||
private readonly byte[] _codeBuffer;
|
|
||||||
|
|
||||||
// The length of the buffer
|
|
||||||
private readonly int _length;
|
|
||||||
|
|
||||||
// The base address of the code
|
|
||||||
private readonly ulong _baseAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the BlockDisassembler class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="codeBuffer">The raw code bytes to be disassembled</param>
|
|
||||||
/// <param name="baseAddress">The base RVA (Relative Virtual Address) of the code section</param>
|
|
||||||
public BlockDisassembler(byte[] codeBuffer, ulong baseAddress)
|
|
||||||
{
|
|
||||||
_codeBuffer = codeBuffer;
|
|
||||||
_length = codeBuffer.Length;
|
|
||||||
|
|
||||||
_baseAddress = baseAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disassembles code starting from the specified RVA address by following control flow.
|
|
||||||
/// Creates blocks of instructions separated by jumps, branches, and returns.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rvaAddress">The RVA (Relative Virtual Address) to start disassembly from</param>
|
|
||||||
/// <returns>A list of instruction blocks representing the control flow of the code</returns>
|
|
||||||
public AsmFunction DisassembleFromAddress(uint rvaAddress)
|
|
||||||
{
|
|
||||||
// Create instruction decoder for parsing the code buffer
|
|
||||||
InstructionDecoder decoder = new InstructionDecoder(_codeBuffer, _length);
|
|
||||||
|
|
||||||
// Track visited addresses to prevent infinite loops
|
|
||||||
HashSet<ulong> visitedAddresses = [];
|
|
||||||
|
|
||||||
// Queue of addresses to process (breadth-first approach)
|
|
||||||
Queue<ulong> addressQueue = [];
|
|
||||||
|
|
||||||
// Calculate the file offset from the RVA by subtracting the base address
|
|
||||||
// Store the file offset for processing, but we'll convert back to RVA when creating blocks
|
|
||||||
ulong fileOffset = rvaAddress - _baseAddress;
|
|
||||||
addressQueue.Enqueue(fileOffset);
|
|
||||||
|
|
||||||
// Keep track of the original entry point RVA for the function
|
|
||||||
ulong entryPointRVA = rvaAddress;
|
|
||||||
|
|
||||||
// List to store discovered basic blocks
|
|
||||||
List<InstructionBlock> blocks = [];
|
|
||||||
|
|
||||||
// Dictionary to track blocks by address for quick lookup
|
|
||||||
Dictionary<ulong, InstructionBlock> blocksByAddress = new Dictionary<ulong, InstructionBlock>();
|
|
||||||
|
|
||||||
while (addressQueue.Count > 0)
|
|
||||||
{
|
|
||||||
// Get the next address to process
|
|
||||||
var address = addressQueue.Dequeue();
|
|
||||||
|
|
||||||
// Skip if we've already visited this address
|
|
||||||
if (!visitedAddresses.Add(address))
|
|
||||||
{
|
|
||||||
// Skip addresses we've already processed
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position the decoder at the current address
|
|
||||||
decoder.SetPosition((int) address);
|
|
||||||
|
|
||||||
// Collect instructions for this block
|
|
||||||
List<Instruction> instructions = [];
|
|
||||||
|
|
||||||
// Get the current block if it exists (for tracking predecessors)
|
|
||||||
InstructionBlock? currentBlock = null;
|
|
||||||
if (blocksByAddress.TryGetValue(address, out var existingBlock))
|
|
||||||
{
|
|
||||||
currentBlock = existingBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process instructions until we hit a control flow change
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Get the current position
|
|
||||||
ulong currentPosition = (ulong)decoder.GetPosition();
|
|
||||||
|
|
||||||
// If we've stepped onto an existing block, create a new block up to this point
|
|
||||||
// and stop processing this path (to avoid duplicating instructions)
|
|
||||||
if (blocksByAddress.TryGetValue(currentPosition, out var targetBlock) && currentPosition != address)
|
|
||||||
{
|
|
||||||
// We've stepped onto an existing block, create a new one up to this point
|
|
||||||
|
|
||||||
// Register this block and establish the relationship with the target block
|
|
||||||
var newBlock = RegisterBlock(blocks, address, instructions, null, false, false);
|
|
||||||
blocksByAddress[address] = newBlock;
|
|
||||||
|
|
||||||
// Add the target block as a successor to the new block
|
|
||||||
newBlock.Successors.Add(targetBlock);
|
|
||||||
|
|
||||||
// Add the new block as a predecessor to the target block
|
|
||||||
targetBlock.Predecessors.Add(newBlock);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the next instruction
|
|
||||||
var instruction = decoder.DecodeInstruction();
|
|
||||||
|
|
||||||
// Handle decoding failures
|
|
||||||
if (instruction is null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpectedly failed to decode instruction at {address}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the instruction to the current block
|
|
||||||
instructions.Add(instruction);
|
|
||||||
|
|
||||||
// Check for conditional jump (e.g., JZ, JNZ, JLE)
|
|
||||||
// For conditional jumps, we need to follow both the jump target and the fall-through path
|
|
||||||
if (instruction.Type.IsConditionalJump())
|
|
||||||
{
|
|
||||||
// Get the jump target address
|
|
||||||
uint jumpTargetAddress = instruction.StructuredOperands[0].GetValue();
|
|
||||||
|
|
||||||
// Get the fall-through address (next instruction after this jump)
|
|
||||||
uint fallThroughAddress = (uint)decoder.GetPosition();
|
|
||||||
|
|
||||||
// Register this block (it ends with a conditional jump)
|
|
||||||
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
|
||||||
blocksByAddress[address] = newBlock;
|
|
||||||
|
|
||||||
// Register the target block if it doesn't exist yet
|
|
||||||
InstructionBlock? jumpTargetBlock = null;
|
|
||||||
if (blocksByAddress.TryGetValue(jumpTargetAddress, out var existingTargetBlock))
|
|
||||||
{
|
|
||||||
jumpTargetBlock = existingTargetBlock;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We'll create this block later when we process the queue
|
|
||||||
// For now, just queue it for processing
|
|
||||||
addressQueue.Enqueue(jumpTargetAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the fall-through block if it doesn't exist yet
|
|
||||||
InstructionBlock? fallThroughBlock = null;
|
|
||||||
if (blocksByAddress.TryGetValue(fallThroughAddress, out var existingFallThroughBlock))
|
|
||||||
{
|
|
||||||
fallThroughBlock = existingFallThroughBlock;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We'll create this block later when we process the queue
|
|
||||||
// For now, just queue it for processing
|
|
||||||
addressQueue.Enqueue(fallThroughAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the jump target block exists, add it as a successor to the current block
|
|
||||||
if (jumpTargetBlock != null)
|
|
||||||
{
|
|
||||||
newBlock.Successors.Add(jumpTargetBlock);
|
|
||||||
jumpTargetBlock.Predecessors.Add(newBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the fall-through block exists, add it as a successor to the current block
|
|
||||||
if (fallThroughBlock != null)
|
|
||||||
{
|
|
||||||
newBlock.Successors.Add(fallThroughBlock);
|
|
||||||
fallThroughBlock.Predecessors.Add(newBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for unconditional jump (e.g., JMP)
|
|
||||||
// For unconditional jumps, we only follow the jump target
|
|
||||||
if (instruction.Type.IsRegularJump())
|
|
||||||
{
|
|
||||||
// Get the jump target address
|
|
||||||
uint jumpTargetAddress = instruction.StructuredOperands[0].GetValue();
|
|
||||||
|
|
||||||
// Register this block (it ends with an unconditional jump)
|
|
||||||
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
|
||||||
blocksByAddress[address] = newBlock;
|
|
||||||
|
|
||||||
// Register the target block if it doesn't exist yet
|
|
||||||
InstructionBlock? jumpTargetBlock = null;
|
|
||||||
if (blocksByAddress.TryGetValue(jumpTargetAddress, out var existingTargetBlock))
|
|
||||||
{
|
|
||||||
jumpTargetBlock = existingTargetBlock;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We'll create this block later when we process the queue
|
|
||||||
// For now, just queue it for processing
|
|
||||||
addressQueue.Enqueue(jumpTargetAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the jump target block exists, add it as a successor to the current block
|
|
||||||
if (jumpTargetBlock != null)
|
|
||||||
{
|
|
||||||
newBlock.Successors.Add(jumpTargetBlock);
|
|
||||||
jumpTargetBlock.Predecessors.Add(newBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for return instruction (e.g., RET, RETF)
|
|
||||||
// Returns end a block without any successors
|
|
||||||
if (instruction.Type.IsRet())
|
|
||||||
{
|
|
||||||
// Register this block (it ends with a return)
|
|
||||||
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
|
||||||
blocksByAddress[address] = newBlock;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since blocks aren't necessarily ordered (ASM can jump anywhere it likes)
|
|
||||||
// we need to sort the blocks ourselves
|
|
||||||
blocks.Sort((b1, b2) => b1.Address.CompareTo(b2.Address));
|
|
||||||
|
|
||||||
// First, establish the successor and predecessor relationships based on file offsets
|
|
||||||
// This is done by analyzing the last instruction of each block
|
|
||||||
foreach (var block in blocks)
|
|
||||||
{
|
|
||||||
if (block.Instructions.Count == 0) continue;
|
|
||||||
|
|
||||||
var lastInstruction = block.Instructions[^1];
|
|
||||||
|
|
||||||
// Check if the last instruction is a conditional jump
|
|
||||||
if (lastInstruction.Type.IsConditionalJump())
|
|
||||||
{
|
|
||||||
// Get the jump target address (file offset)
|
|
||||||
ulong targetAddress = 0;
|
|
||||||
if (lastInstruction.StructuredOperands.Count > 0 && lastInstruction.StructuredOperands[0] is RelativeOffsetOperand relOp)
|
|
||||||
{
|
|
||||||
targetAddress = relOp.TargetAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the target block
|
|
||||||
var targetBlock = blocks.FirstOrDefault(b => b.Address == targetAddress);
|
|
||||||
if (targetBlock != null)
|
|
||||||
{
|
|
||||||
// Add the target block as a successor to this block
|
|
||||||
if (!block.Successors.Contains(targetBlock))
|
|
||||||
{
|
|
||||||
block.Successors.Add(targetBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this block as a predecessor to the target block
|
|
||||||
if (!targetBlock.Predecessors.Contains(block))
|
|
||||||
{
|
|
||||||
targetBlock.Predecessors.Add(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For conditional jumps, also add the fall-through block as a successor
|
|
||||||
// The fall-through block is the one that immediately follows this block in memory
|
|
||||||
// Find the next block in address order
|
|
||||||
var nextBlock = blocks.OrderBy(b => b.Address).FirstOrDefault(b => b.Address > block.Address);
|
|
||||||
if (nextBlock != null)
|
|
||||||
{
|
|
||||||
// The fall-through block is the one that immediately follows this block in memory
|
|
||||||
var fallThroughBlock = nextBlock;
|
|
||||||
|
|
||||||
// Add the fall-through block as a successor to this block
|
|
||||||
if (!block.Successors.Contains(fallThroughBlock))
|
|
||||||
{
|
|
||||||
block.Successors.Add(fallThroughBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this block as a predecessor to the fall-through block
|
|
||||||
if (!fallThroughBlock.Predecessors.Contains(block))
|
|
||||||
{
|
|
||||||
fallThroughBlock.Predecessors.Add(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if the last instruction is an unconditional jump
|
|
||||||
else if (lastInstruction.Type == InstructionType.Jmp)
|
|
||||||
{
|
|
||||||
// Get the jump target address (file offset)
|
|
||||||
ulong targetAddress = 0;
|
|
||||||
if (lastInstruction.StructuredOperands.Count > 0 && lastInstruction.StructuredOperands[0] is RelativeOffsetOperand relOp)
|
|
||||||
{
|
|
||||||
targetAddress = relOp.TargetAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the target block
|
|
||||||
var targetBlock = blocks.FirstOrDefault(b => b.Address == targetAddress);
|
|
||||||
if (targetBlock != null)
|
|
||||||
{
|
|
||||||
// Add the target block as a successor to this block
|
|
||||||
if (!block.Successors.Contains(targetBlock))
|
|
||||||
{
|
|
||||||
block.Successors.Add(targetBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this block as a predecessor to the target block
|
|
||||||
if (!targetBlock.Predecessors.Contains(block))
|
|
||||||
{
|
|
||||||
targetBlock.Predecessors.Add(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// For non-jump instructions that don't end the function (like Ret), add the fall-through block
|
|
||||||
else if (!lastInstruction.Type.IsRet())
|
|
||||||
{
|
|
||||||
// The fall-through block is the one that immediately follows this block in memory
|
|
||||||
// Find the next block in address order
|
|
||||||
var nextBlock = blocks.OrderBy(b => b.Address).FirstOrDefault(b => b.Address > block.Address);
|
|
||||||
if (nextBlock != null)
|
|
||||||
{
|
|
||||||
// The fall-through block is the one that immediately follows this block in memory
|
|
||||||
var fallThroughBlock = nextBlock;
|
|
||||||
|
|
||||||
// Add the fall-through block as a successor to this block
|
|
||||||
if (!block.Successors.Contains(fallThroughBlock))
|
|
||||||
{
|
|
||||||
block.Successors.Add(fallThroughBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this block as a predecessor to the fall-through block
|
|
||||||
if (!fallThroughBlock.Predecessors.Contains(block))
|
|
||||||
{
|
|
||||||
fallThroughBlock.Predecessors.Add(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the original file offset for each block in a dictionary
|
|
||||||
Dictionary<InstructionBlock, ulong> blockToFileOffset = new Dictionary<InstructionBlock, ulong>();
|
|
||||||
foreach (var block in blocks)
|
|
||||||
{
|
|
||||||
blockToFileOffset[block] = block.Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert all block addresses from file offsets to RVA
|
|
||||||
// and update the block dictionary for quick lookup
|
|
||||||
Dictionary<ulong, InstructionBlock> rvaBlocksByAddress = new Dictionary<ulong, InstructionBlock>();
|
|
||||||
Dictionary<ulong, ulong> fileOffsetToRvaMap = new Dictionary<ulong, ulong>();
|
|
||||||
|
|
||||||
// First pass: create a mapping from file offset to RVA for each block
|
|
||||||
foreach (var block in blocks)
|
|
||||||
{
|
|
||||||
// Get the original file offset address
|
|
||||||
ulong blockFileOffset = block.Address;
|
|
||||||
|
|
||||||
// Calculate the RVA address
|
|
||||||
ulong blockRvaAddress = blockFileOffset + _baseAddress;
|
|
||||||
|
|
||||||
// Store the mapping
|
|
||||||
fileOffsetToRvaMap[blockFileOffset] = blockRvaAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second pass: update all blocks to use RVA addresses
|
|
||||||
foreach (var block in blocks)
|
|
||||||
{
|
|
||||||
// Get the original file offset address
|
|
||||||
ulong blockFileOffset = block.Address;
|
|
||||||
|
|
||||||
// Update the block's address to RVA
|
|
||||||
ulong blockRvaAddress = fileOffsetToRvaMap[blockFileOffset];
|
|
||||||
block.Address = blockRvaAddress;
|
|
||||||
|
|
||||||
// Add to the dictionary for quick lookup
|
|
||||||
rvaBlocksByAddress[blockRvaAddress] = block;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now update all successors and predecessors to use the correct RVA addresses
|
|
||||||
foreach (var block in blocks)
|
|
||||||
{
|
|
||||||
// Create new lists for successors and predecessors with the correct RVA addresses
|
|
||||||
List<InstructionBlock> updatedSuccessors = new List<InstructionBlock>();
|
|
||||||
List<InstructionBlock> updatedPredecessors = new List<InstructionBlock>();
|
|
||||||
|
|
||||||
// Update successors
|
|
||||||
foreach (var successor in block.Successors)
|
|
||||||
{
|
|
||||||
// Get the original file offset of the successor
|
|
||||||
if (blockToFileOffset.TryGetValue(successor, out ulong successorFileOffset))
|
|
||||||
{
|
|
||||||
// Look up the RVA address in our mapping
|
|
||||||
if (fileOffsetToRvaMap.TryGetValue(successorFileOffset, out ulong successorRvaAddress))
|
|
||||||
{
|
|
||||||
// Find the block with this RVA address
|
|
||||||
if (rvaBlocksByAddress.TryGetValue(successorRvaAddress, out var rvaSuccessor))
|
|
||||||
{
|
|
||||||
updatedSuccessors.Add(rvaSuccessor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update predecessors
|
|
||||||
foreach (var predecessor in block.Predecessors)
|
|
||||||
{
|
|
||||||
// Get the original file offset of the predecessor
|
|
||||||
if (blockToFileOffset.TryGetValue(predecessor, out ulong predecessorFileOffset))
|
|
||||||
{
|
|
||||||
// Look up the RVA address in our mapping
|
|
||||||
if (fileOffsetToRvaMap.TryGetValue(predecessorFileOffset, out ulong predecessorRvaAddress))
|
|
||||||
{
|
|
||||||
// Find the block with this RVA address
|
|
||||||
if (rvaBlocksByAddress.TryGetValue(predecessorRvaAddress, out var rvaPredecessor))
|
|
||||||
{
|
|
||||||
updatedPredecessors.Add(rvaPredecessor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the old lists with the updated ones
|
|
||||||
block.Successors = updatedSuccessors;
|
|
||||||
block.Predecessors = updatedPredecessors;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new AsmFunction with the RVA address
|
|
||||||
var asmFunction = new AsmFunction()
|
|
||||||
{
|
|
||||||
Address = entryPointRVA,
|
|
||||||
Blocks = blocks,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Verify that the entry block exists (no need to log this information)
|
|
||||||
|
|
||||||
return asmFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates and registers a new instruction block in the blocks collection
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="blocks">The list of blocks to add to</param>
|
|
||||||
/// <param name="address">The starting address of the block</param>
|
|
||||||
/// <param name="instructions">The instructions contained in the block</param>
|
|
||||||
/// <param name="currentBlock">The current block being processed (null if this is the first block)</param>
|
|
||||||
/// <param name="isJumpTarget">Whether this block is a jump target</param>
|
|
||||||
/// <param name="isFallThrough">Whether this block is a fall-through from another block</param>
|
|
||||||
/// <returns>The newly created block</returns>
|
|
||||||
public InstructionBlock RegisterBlock(
|
|
||||||
List<InstructionBlock> blocks,
|
|
||||||
ulong address,
|
|
||||||
List<Instruction> instructions,
|
|
||||||
InstructionBlock? currentBlock = null,
|
|
||||||
bool isJumpTarget = false,
|
|
||||||
bool isFallThrough = false)
|
|
||||||
{
|
|
||||||
// Check if a block already exists at this address
|
|
||||||
var existingBlock = blocks.FirstOrDefault(b => b.Address == address);
|
|
||||||
|
|
||||||
if (existingBlock != null)
|
|
||||||
{
|
|
||||||
// If the current block is not null, update the relationships
|
|
||||||
if (currentBlock != null)
|
|
||||||
{
|
|
||||||
// Add the existing block as a successor to the current block if not already present
|
|
||||||
if (!currentBlock.Successors.Contains(existingBlock))
|
|
||||||
{
|
|
||||||
currentBlock.Successors.Add(existingBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the current block as a predecessor to the existing block if not already present
|
|
||||||
if (!existingBlock.Predecessors.Contains(currentBlock))
|
|
||||||
{
|
|
||||||
existingBlock.Predecessors.Add(currentBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new block with the provided address and instructions
|
|
||||||
var block = new InstructionBlock()
|
|
||||||
{
|
|
||||||
Address = address,
|
|
||||||
Instructions = new List<Instruction>(instructions) // Create a copy of the instructions list
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the block to the collection
|
|
||||||
blocks.Add(block);
|
|
||||||
|
|
||||||
// If the current block is not null, update the relationships
|
|
||||||
if (currentBlock != null)
|
|
||||||
{
|
|
||||||
// Add the new block as a successor to the current block
|
|
||||||
currentBlock.Successors.Add(block);
|
|
||||||
|
|
||||||
// Add the current block as a predecessor to the new block
|
|
||||||
block.Predecessors.Add(currentBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a basic block of instructions with a single entry and exit point
|
|
||||||
/// </summary>
|
|
||||||
public class InstructionBlock
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The starting address of the block
|
|
||||||
/// </summary>
|
|
||||||
public ulong Address { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The list of instructions contained in this block
|
|
||||||
/// </summary>
|
|
||||||
public List<Instruction> Instructions { get; set; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The blocks that can transfer control to this block
|
|
||||||
/// </summary>
|
|
||||||
public List<InstructionBlock> Predecessors { get; set; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The blocks that this block can transfer control to
|
|
||||||
/// </summary>
|
|
||||||
public List<InstructionBlock> Successors { get; set; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the block, including its address, instructions, and control flow information
|
|
||||||
/// </summary>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
// Create a string for predecessors
|
|
||||||
string predecessorsStr = Predecessors.Count > 0
|
|
||||||
? $"Predecessors: {string.Join(", ", Predecessors.Select(p => $"0x{p.Address:X8}"))}"
|
|
||||||
: "No predecessors";
|
|
||||||
|
|
||||||
// Create a string for successors
|
|
||||||
string successorsStr = Successors.Count > 0
|
|
||||||
? $"Successors: {string.Join(", ", Successors.Select(s => $"0x{s.Address:X8}"))}"
|
|
||||||
: "No successors";
|
|
||||||
|
|
||||||
// Return the complete string representation
|
|
||||||
return $"Address: 0x{Address:X8}\n{predecessorsStr}\n{successorsStr}\n{string.Join("\n", Instructions)}";
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
namespace X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
public abstract class Address(ulong value, ulong imageBase)
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The actual value of the address, not specifically typed.
|
|
||||||
/// </summary>
|
|
||||||
protected readonly ulong Value = value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// PE.ImageBase from which this address is constructed
|
|
||||||
/// </summary>
|
|
||||||
protected readonly ulong ImageBase = imageBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Absolute address in the PE file
|
|
||||||
/// </summary>
|
|
||||||
public class FileAbsoluteAddress(ulong value, ulong imageBase) : Address(value, imageBase)
|
|
||||||
{
|
|
||||||
public ulong GetValue()
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual VirtualAddress AsImageBaseAddress()
|
|
||||||
{
|
|
||||||
return new VirtualAddress(Value + ImageBase, ImageBase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual FileAbsoluteAddress AsFileAbsolute()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address from PE.ImageBase
|
|
||||||
/// </summary>
|
|
||||||
public class VirtualAddress : FileAbsoluteAddress
|
|
||||||
{
|
|
||||||
public VirtualAddress(ulong value, ulong imageBase) : base(value, imageBase)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override VirtualAddress AsImageBaseAddress()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override FileAbsoluteAddress AsFileAbsolute()
|
|
||||||
{
|
|
||||||
return new FileAbsoluteAddress(Value - ImageBase, ImageBase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -1,40 +0,0 @@
|
|||||||
using X86Disassembler.X86;
|
|
||||||
|
|
||||||
namespace X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
public static class InstructionTypeExtensions
|
|
||||||
{
|
|
||||||
public static bool IsConditionalJump(this InstructionType type)
|
|
||||||
{
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
InstructionType.Jg => true,
|
|
||||||
InstructionType.Jge => true,
|
|
||||||
InstructionType.Jl => true,
|
|
||||||
InstructionType.Jle => true,
|
|
||||||
InstructionType.Ja => true,
|
|
||||||
InstructionType.Jae => true,
|
|
||||||
InstructionType.Jb => true,
|
|
||||||
InstructionType.Jbe => true,
|
|
||||||
InstructionType.Jz => true,
|
|
||||||
InstructionType.Jnz => true,
|
|
||||||
InstructionType.Jo => true,
|
|
||||||
InstructionType.Jno => true,
|
|
||||||
InstructionType.Js => true,
|
|
||||||
InstructionType.Jns => true,
|
|
||||||
InstructionType.Jp => true,
|
|
||||||
InstructionType.Jnp => true,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsRegularJump(this InstructionType type)
|
|
||||||
{
|
|
||||||
return type == InstructionType.Jmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsRet(this InstructionType type)
|
|
||||||
{
|
|
||||||
return type is InstructionType.Ret or InstructionType.Retf;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
using X86Disassembler.X86;
|
|
||||||
using X86Disassembler.X86.Operands;
|
|
||||||
|
|
||||||
namespace X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
public static class OperandExtensions
|
|
||||||
{
|
|
||||||
public static uint GetValue(this Operand operand)
|
|
||||||
{
|
|
||||||
return operand switch
|
|
||||||
{
|
|
||||||
RelativeOffsetOperand roo => roo.TargetAddress,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Utility class for PE format operations
|
|
||||||
/// </summary>
|
|
||||||
public class PEUtility
|
|
||||||
{
|
|
||||||
private readonly List<SectionHeader> _sectionHeaders;
|
|
||||||
private readonly uint _sizeOfHeaders;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize a new instance of the PEUtility class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sectionHeaders">The section headers</param>
|
|
||||||
/// <param name="sizeOfHeaders">The size of the headers</param>
|
|
||||||
public PEUtility(List<SectionHeader> sectionHeaders, uint sizeOfHeaders)
|
|
||||||
{
|
|
||||||
_sectionHeaders = sectionHeaders;
|
|
||||||
_sizeOfHeaders = sizeOfHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 < _sizeOfHeaders)
|
|
||||||
{
|
|
||||||
return rva;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"RVA {rva:X8} is not within any section");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parser for the DOS header of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class DOSHeaderParser : IParser<DOSHeader>
|
|
||||||
{
|
|
||||||
// DOS Header constants
|
|
||||||
private const ushort DOS_SIGNATURE = 0x5A4D; // 'MZ'
|
|
||||||
|
|
||||||
public DOSHeader Parse(BinaryReader reader)
|
|
||||||
{
|
|
||||||
var 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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,161 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parser for the Export Directory of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class ExportDirectoryParser
|
|
||||||
{
|
|
||||||
private readonly PEUtility _utility;
|
|
||||||
|
|
||||||
public ExportDirectoryParser(PEUtility utility)
|
|
||||||
{
|
|
||||||
_utility = utility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse the Export Directory from the binary reader
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader</param>
|
|
||||||
/// <param name="rva">The RVA of the Export Directory</param>
|
|
||||||
/// <returns>The parsed Export Directory</returns>
|
|
||||||
public ExportDirectory Parse(BinaryReader reader, uint rva)
|
|
||||||
{
|
|
||||||
ExportDirectory directory = new ExportDirectory();
|
|
||||||
|
|
||||||
reader.BaseStream.Seek(_utility.RvaToOffset(rva), SeekOrigin.Begin);
|
|
||||||
|
|
||||||
directory.Characteristics = reader.ReadUInt32();
|
|
||||||
directory.TimeDateStamp = reader.ReadUInt32();
|
|
||||||
directory.MajorVersion = reader.ReadUInt16();
|
|
||||||
directory.MinorVersion = reader.ReadUInt16();
|
|
||||||
directory.DllNameRva = 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();
|
|
||||||
|
|
||||||
uint dllNameOffset = _utility.RvaToOffset(directory.DllNameRva);
|
|
||||||
reader.BaseStream.Seek(dllNameOffset, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
// Read the null-terminated ASCII string
|
|
||||||
var nameBuilder = new StringBuilder();
|
|
||||||
byte b;
|
|
||||||
|
|
||||||
while ((b = reader.ReadByte()) != 0)
|
|
||||||
{
|
|
||||||
nameBuilder.Append((char) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
directory.DllName = nameBuilder.ToString();
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse the exported functions using the export directory information
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader</param>
|
|
||||||
/// <param name="directory">The Export Directory</param>
|
|
||||||
/// <param name="exportDirRva">The RVA of the Export Directory</param>
|
|
||||||
/// <param name="exportDirSize">The size of the Export Directory</param>
|
|
||||||
/// <returns>List of exported functions</returns>
|
|
||||||
public List<ExportedFunction> ParseExportedFunctions(BinaryReader reader, ExportDirectory directory, uint exportDirRva, uint exportDirSize)
|
|
||||||
{
|
|
||||||
List<ExportedFunction> exportedFunctions = new List<ExportedFunction>();
|
|
||||||
|
|
||||||
// Read the array of function addresses (RVAs)
|
|
||||||
uint[] functionRVAs = new uint[directory.NumberOfFunctions];
|
|
||||||
reader.BaseStream.Seek(_utility.RvaToOffset(directory.AddressOfFunctions), SeekOrigin.Begin);
|
|
||||||
for (int i = 0; i < directory.NumberOfFunctions; i++)
|
|
||||||
{
|
|
||||||
functionRVAs[i] = reader.ReadUInt32();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the array of name RVAs
|
|
||||||
uint[] nameRVAs = new uint[directory.NumberOfNames];
|
|
||||||
reader.BaseStream.Seek(_utility.RvaToOffset(directory.AddressOfNames), SeekOrigin.Begin);
|
|
||||||
for (int i = 0; i < directory.NumberOfNames; i++)
|
|
||||||
{
|
|
||||||
nameRVAs[i] = reader.ReadUInt32();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the array of name ordinals
|
|
||||||
ushort[] nameOrdinals = new ushort[directory.NumberOfNames];
|
|
||||||
reader.BaseStream.Seek(_utility.RvaToOffset(directory.AddressOfNameOrdinals), SeekOrigin.Begin);
|
|
||||||
for (int i = 0; i < directory.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 < directory.NumberOfNames; i++)
|
|
||||||
{
|
|
||||||
// Read the function name
|
|
||||||
reader.BaseStream.Seek(_utility.RvaToOffset(nameRVAs[i]), SeekOrigin.Begin);
|
|
||||||
var nameBuilder = new StringBuilder();
|
|
||||||
byte b;
|
|
||||||
while ((b = reader.ReadByte()) != 0)
|
|
||||||
{
|
|
||||||
nameBuilder.Append((char) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
string name = nameBuilder.ToString();
|
|
||||||
|
|
||||||
// Map the ordinal to the name
|
|
||||||
ordinalToName[nameOrdinals[i]] = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the exported functions
|
|
||||||
for (ushort i = 0; i < directory.NumberOfFunctions; i++)
|
|
||||||
{
|
|
||||||
uint functionRVA = functionRVAs[i];
|
|
||||||
if (functionRVA == 0)
|
|
||||||
{
|
|
||||||
continue; // Skip empty entries
|
|
||||||
}
|
|
||||||
|
|
||||||
ExportedFunction function = new ExportedFunction();
|
|
||||||
function.Ordinal = (ushort) (i + directory.Base);
|
|
||||||
function.AddressRva = 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 exportDirEnd = exportDirRva + exportDirSize;
|
|
||||||
|
|
||||||
if (functionRVA >= exportDirRva && functionRVA < exportDirEnd)
|
|
||||||
{
|
|
||||||
function.IsForwarder = true;
|
|
||||||
|
|
||||||
// Read the forwarder string
|
|
||||||
reader.BaseStream.Seek(_utility.RvaToOffset(functionRVA), SeekOrigin.Begin);
|
|
||||||
var forwarderBuilder = new StringBuilder();
|
|
||||||
byte b;
|
|
||||||
while ((b = reader.ReadByte()) != 0)
|
|
||||||
{
|
|
||||||
forwarderBuilder.Append((char) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
function.ForwarderName = forwarderBuilder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
exportedFunctions.Add(function);
|
|
||||||
}
|
|
||||||
|
|
||||||
return exportedFunctions;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parser for the File header of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class FileHeaderParser : IParser<FileHeader>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Parse the File header from the binary reader
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader positioned at the start of the File header</param>
|
|
||||||
/// <returns>The parsed File header</returns>
|
|
||||||
public FileHeader Parse(BinaryReader reader)
|
|
||||||
{
|
|
||||||
var 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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for PE format component parsers
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of component to parse</typeparam>
|
|
||||||
public interface IParser<out T>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Parse a component from the binary reader
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader positioned at the start of the component</param>
|
|
||||||
/// <returns>The parsed component</returns>
|
|
||||||
T Parse(BinaryReader reader);
|
|
||||||
}
|
|
@@ -1,162 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parser for Import Descriptors in a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class ImportDescriptorParser
|
|
||||||
{
|
|
||||||
private readonly PEUtility _utility;
|
|
||||||
|
|
||||||
public ImportDescriptorParser(PEUtility utility)
|
|
||||||
{
|
|
||||||
_utility = utility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse the Import Descriptors from the binary reader
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader</param>
|
|
||||||
/// <param name="rva">The RVA of the Import Directory</param>
|
|
||||||
/// <returns>List of Import Descriptors</returns>
|
|
||||||
public List<ImportDescriptor> Parse(BinaryReader reader, uint rva)
|
|
||||||
{
|
|
||||||
var descriptors = new List<ImportDescriptor>();
|
|
||||||
|
|
||||||
uint importTableOffset = _utility.RvaToOffset(rva);
|
|
||||||
reader.BaseStream.Seek(importTableOffset, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
int descriptorCount = 0;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
descriptorCount++;
|
|
||||||
|
|
||||||
// Read the import descriptor
|
|
||||||
uint originalFirstThunk = reader.ReadUInt32();
|
|
||||||
uint timeDateStamp = reader.ReadUInt32();
|
|
||||||
uint forwarderChain = reader.ReadUInt32();
|
|
||||||
uint nameRva = reader.ReadUInt32();
|
|
||||||
uint firstThunk = reader.ReadUInt32();
|
|
||||||
|
|
||||||
// Check if we've reached the end of the import descriptors
|
|
||||||
if (originalFirstThunk == 0 && nameRva == 0 && firstThunk == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportDescriptor descriptor = new ImportDescriptor
|
|
||||||
{
|
|
||||||
OriginalFirstThunkRva = originalFirstThunk,
|
|
||||||
TimeDateStamp = timeDateStamp,
|
|
||||||
ForwarderChain = forwarderChain,
|
|
||||||
DllNameRva = nameRva,
|
|
||||||
FirstThunkRva = firstThunk,
|
|
||||||
DllName = "Unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (nameRva != 0)
|
|
||||||
{
|
|
||||||
uint nameOffset = _utility.RvaToOffset(nameRva);
|
|
||||||
reader.BaseStream.Seek(nameOffset, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
// Read the null-terminated ASCII string
|
|
||||||
StringBuilder nameBuilder = new StringBuilder();
|
|
||||||
byte b;
|
|
||||||
|
|
||||||
while ((b = reader.ReadByte()) != 0)
|
|
||||||
{
|
|
||||||
nameBuilder.Append((char) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptor.DllName = nameBuilder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the imported functions
|
|
||||||
ParseImportedFunctions(reader, descriptor);
|
|
||||||
|
|
||||||
descriptors.Add(descriptor);
|
|
||||||
|
|
||||||
// Return to the import table to read the next descriptor
|
|
||||||
reader.BaseStream.Seek(importTableOffset + (descriptorCount * 20), SeekOrigin.Begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
return descriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse the imported functions for a given import descriptor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader</param>
|
|
||||||
/// <param name="descriptor">The Import Descriptor</param>
|
|
||||||
private void ParseImportedFunctions(BinaryReader reader, ImportDescriptor descriptor)
|
|
||||||
{
|
|
||||||
// Use OriginalFirstThunk if available, otherwise use FirstThunk
|
|
||||||
uint thunkRva = descriptor.OriginalFirstThunkRva != 0
|
|
||||||
? descriptor.OriginalFirstThunkRva
|
|
||||||
: descriptor.FirstThunkRva;
|
|
||||||
|
|
||||||
if (thunkRva == 0)
|
|
||||||
{
|
|
||||||
return; // No functions to parse
|
|
||||||
}
|
|
||||||
|
|
||||||
uint thunkOffset = _utility.RvaToOffset(thunkRva);
|
|
||||||
int functionCount = 0;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
reader.BaseStream.Seek(thunkOffset + (functionCount * 4), SeekOrigin.Begin);
|
|
||||||
uint thunkData = reader.ReadUInt32();
|
|
||||||
|
|
||||||
if (thunkData == 0)
|
|
||||||
{
|
|
||||||
break; // End of the function list
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportedFunction function = new ImportedFunction
|
|
||||||
{
|
|
||||||
ThunkRva = thunkRva + (uint) (functionCount * 4)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if imported by ordinal (high bit set)
|
|
||||||
if ((thunkData & 0x80000000) != 0)
|
|
||||||
{
|
|
||||||
function.IsOrdinal = true;
|
|
||||||
function.Ordinal = (ushort) (thunkData & 0xFFFF);
|
|
||||||
function.Name = $"Ordinal {function.Ordinal}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Imported by name - the thunkData is an RVA to a hint/name structure
|
|
||||||
|
|
||||||
uint hintNameOffset = _utility.RvaToOffset(thunkData);
|
|
||||||
reader.BaseStream.Seek(hintNameOffset, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
// Read the hint (2 bytes)
|
|
||||||
function.Hint = reader.ReadUInt16();
|
|
||||||
|
|
||||||
// Read the function name (null-terminated ASCII string)
|
|
||||||
StringBuilder nameBuilder = new StringBuilder();
|
|
||||||
byte b;
|
|
||||||
|
|
||||||
while ((b = reader.ReadByte()) != 0)
|
|
||||||
{
|
|
||||||
nameBuilder.Append((char) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
function.Name = nameBuilder.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(function.Name))
|
|
||||||
{
|
|
||||||
function.Name = $"Function_at_{thunkData:X8}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptor.Functions.Add(function);
|
|
||||||
functionCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,95 +0,0 @@
|
|||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parser for the Optional header of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class OptionalHeaderParser : IParser<OptionalHeader>
|
|
||||||
{
|
|
||||||
// Optional Header Magic values
|
|
||||||
private const ushort PE32_MAGIC = 0x10B; // 32-bit executable
|
|
||||||
private const ushort PE32PLUS_MAGIC = 0x20B; // 64-bit executable
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse the Optional header from the binary reader
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader positioned at the start of the Optional header</param>
|
|
||||||
/// <returns>The parsed Optional header</returns>
|
|
||||||
public OptionalHeader Parse(BinaryReader reader)
|
|
||||||
{
|
|
||||||
var header = new OptionalHeader();
|
|
||||||
|
|
||||||
// Standard fields
|
|
||||||
header.Magic = reader.ReadUInt16();
|
|
||||||
|
|
||||||
// Determine if this is a PE32 or PE32+ file
|
|
||||||
var 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
|
|
||||||
header.ImageBase = is64Bit
|
|
||||||
? reader.ReadUInt64()
|
|
||||||
: 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
|
|
||||||
header.DataDirectories = new DataDirectory[header.NumberOfRvaAndSizes];
|
|
||||||
|
|
||||||
for (int i = 0; i < header.NumberOfRvaAndSizes; i++)
|
|
||||||
{
|
|
||||||
var dir = new DataDirectory();
|
|
||||||
dir.VirtualAddress = reader.ReadUInt32();
|
|
||||||
dir.Size = reader.ReadUInt32();
|
|
||||||
header.DataDirectories[i] = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,38 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE.Parsers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parser for section headers in a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class SectionHeaderParser : IParser<SectionHeader>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Parse a section header from the binary reader
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">The binary reader positioned at the start of the section header</param>
|
|
||||||
/// <returns>The parsed section header</returns>
|
|
||||||
public SectionHeader Parse(BinaryReader reader)
|
|
||||||
{
|
|
||||||
var header = new SectionHeader();
|
|
||||||
|
|
||||||
// Read section name (8 bytes)
|
|
||||||
var 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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,158 +0,0 @@
|
|||||||
using X86Disassembler.PE.Parsers;
|
|
||||||
using X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
namespace X86Disassembler.PE;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a Portable Executable (PE) file format parser
|
|
||||||
/// </summary>
|
|
||||||
public class PeFile
|
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Parser instances
|
|
||||||
private readonly DOSHeaderParser _dosHeaderParser;
|
|
||||||
private readonly FileHeaderParser _fileHeaderParser;
|
|
||||||
private readonly OptionalHeaderParser _optionalHeaderParser;
|
|
||||||
private readonly SectionHeaderParser _sectionHeaderParser;
|
|
||||||
private PEUtility _peUtility;
|
|
||||||
private ExportDirectoryParser _exportDirectoryParser;
|
|
||||||
private ImportDescriptorParser _importDescriptorParser;
|
|
||||||
|
|
||||||
// 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>
|
|
||||||
/// Initializes a new instance of the PEFormat class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileData">The raw file data</param>
|
|
||||||
public PeFile(byte[] fileData)
|
|
||||||
{
|
|
||||||
_fileData = fileData;
|
|
||||||
SectionHeaders = [];
|
|
||||||
ExportedFunctions = [];
|
|
||||||
ImportDescriptors = [];
|
|
||||||
|
|
||||||
// Initialize parsers
|
|
||||||
_dosHeaderParser = new DOSHeaderParser();
|
|
||||||
_fileHeaderParser = new FileHeaderParser();
|
|
||||||
_optionalHeaderParser = new OptionalHeaderParser();
|
|
||||||
_sectionHeaderParser = new SectionHeaderParser();
|
|
||||||
|
|
||||||
// Initialize properties to avoid nullability warnings
|
|
||||||
DosHeader = new DOSHeader();
|
|
||||||
FileHeader = new FileHeader();
|
|
||||||
OptionalHeader = new OptionalHeader();
|
|
||||||
ExportDirectory = new ExportDirectory();
|
|
||||||
|
|
||||||
// These will be initialized during Parse()
|
|
||||||
_peUtility = null!;
|
|
||||||
_exportDirectoryParser = null!;
|
|
||||||
_importDescriptorParser = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses the PE file structure
|
|
||||||
/// </summary>
|
|
||||||
public void Parse()
|
|
||||||
{
|
|
||||||
using var stream = new MemoryStream(_fileData);
|
|
||||||
using var reader = new BinaryReader(stream);
|
|
||||||
|
|
||||||
// Parse DOS header
|
|
||||||
DosHeader = _dosHeaderParser.Parse(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 = _fileHeaderParser.Parse(reader);
|
|
||||||
|
|
||||||
// Parse Optional Header
|
|
||||||
OptionalHeader = _optionalHeaderParser.Parse(reader);
|
|
||||||
Is64Bit = OptionalHeader.Is64Bit();
|
|
||||||
|
|
||||||
// Parse Section Headers
|
|
||||||
for (int i = 0; i < FileHeader.NumberOfSections; i++)
|
|
||||||
{
|
|
||||||
SectionHeaders.Add(_sectionHeaderParser.Parse(reader));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize utility after section headers are parsed
|
|
||||||
_peUtility = new PEUtility(SectionHeaders, OptionalHeader.SizeOfHeaders);
|
|
||||||
_exportDirectoryParser = new ExportDirectoryParser(_peUtility);
|
|
||||||
_importDescriptorParser = new ImportDescriptorParser(_peUtility);
|
|
||||||
|
|
||||||
// Parse Export Directory
|
|
||||||
if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_EXPORT &&
|
|
||||||
OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0)
|
|
||||||
{
|
|
||||||
uint exportDirRva = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
||||||
uint exportDirSize = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
|
|
||||||
|
|
||||||
ExportDirectory = _exportDirectoryParser.Parse(reader, exportDirRva);
|
|
||||||
ExportedFunctions = _exportDirectoryParser.ParseExportedFunctions(
|
|
||||||
reader,
|
|
||||||
ExportDirectory,
|
|
||||||
exportDirRva,
|
|
||||||
exportDirSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse Import Descriptors
|
|
||||||
if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_IMPORT &&
|
|
||||||
OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress != 0)
|
|
||||||
{
|
|
||||||
uint importDirRva = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
|
|
||||||
ImportDescriptors = _importDescriptorParser.Parse(reader, importDirRva);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the DOS header of a PE file
|
|
||||||
/// </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 = new ushort[4]; // Reserved words
|
|
||||||
public ushort e_oemid; // OEM identifier (for e_oeminfo)
|
|
||||||
public ushort e_oeminfo; // OEM information; e_oemid specific
|
|
||||||
public ushort[] e_res2 = new ushort[10]; // Reserved words
|
|
||||||
public uint e_lfanew; // File address of new exe header
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a data directory in the optional header
|
|
||||||
/// </summary>
|
|
||||||
public class DataDirectory
|
|
||||||
{
|
|
||||||
public uint VirtualAddress; // RVA of the table
|
|
||||||
public uint Size; // Size of the table
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the Export Directory of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class ExportDirectory
|
|
||||||
{
|
|
||||||
public uint Characteristics; // Reserved, must be 0
|
|
||||||
public uint TimeDateStamp; // Time and date stamp
|
|
||||||
public ushort MajorVersion; // Major version
|
|
||||||
public ushort MinorVersion; // Minor version
|
|
||||||
public uint DllNameRva; // RVA of the name of the DLL
|
|
||||||
public string DllName = ""; // The actual name of the DLL
|
|
||||||
public uint Base; // Ordinal base
|
|
||||||
public uint NumberOfFunctions; // Number of functions
|
|
||||||
public uint NumberOfNames; // Number of names
|
|
||||||
public uint AddressOfFunctions; // RVA of the export address table
|
|
||||||
public uint AddressOfNames; // RVA of the export names table
|
|
||||||
public uint AddressOfNameOrdinals; // RVA of the ordinal table
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an exported function in a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class ExportedFunction
|
|
||||||
{
|
|
||||||
public string Name = ""; // Function name
|
|
||||||
public ushort Ordinal; // Function ordinal
|
|
||||||
public uint AddressRva; // Function RVA
|
|
||||||
public bool IsForwarder; // True if this is a forwarder
|
|
||||||
public string ForwarderName = ""; // Name of the forwarded function
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the File header of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class FileHeader
|
|
||||||
{
|
|
||||||
public ushort Machine; // Target machine type
|
|
||||||
public ushort NumberOfSections; // Number of sections
|
|
||||||
public uint TimeDateStamp; // Time and date stamp
|
|
||||||
public uint PointerToSymbolTable; // File pointer to COFF symbol table
|
|
||||||
public uint NumberOfSymbols; // Number of symbols
|
|
||||||
public ushort SizeOfOptionalHeader; // Size of optional header
|
|
||||||
public ushort Characteristics; // Characteristics
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an Import Descriptor in a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class ImportDescriptor
|
|
||||||
{
|
|
||||||
public uint OriginalFirstThunkRva; // RVA to original first thunk
|
|
||||||
public uint TimeDateStamp; // Time and date stamp
|
|
||||||
public uint ForwarderChain; // Forwarder chain
|
|
||||||
public uint DllNameRva; // RVA to the name of the DLL
|
|
||||||
public string DllName = ""; // The actual name of the DLL
|
|
||||||
public uint FirstThunkRva; // RVA to first thunk
|
|
||||||
|
|
||||||
public List<ImportedFunction> Functions = [];
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an imported function in a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class ImportedFunction
|
|
||||||
{
|
|
||||||
public string Name = ""; // Function name
|
|
||||||
public ushort Hint; // Hint value
|
|
||||||
public bool IsOrdinal; // True if imported by ordinal
|
|
||||||
public ushort Ordinal; // Ordinal value (if imported by ordinal)
|
|
||||||
public uint ThunkRva; // RVA of the thunk for this function
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the Optional header of a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class OptionalHeader
|
|
||||||
{
|
|
||||||
// Optional Header Magic values
|
|
||||||
private const ushort PE32_MAGIC = 0x10B; // 32-bit executable
|
|
||||||
private const ushort PE32PLUS_MAGIC = 0x20B; // 64-bit executable
|
|
||||||
|
|
||||||
// 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 section
|
|
||||||
public uint SizeOfUninitializedData; // Size of uninitialized data section
|
|
||||||
public uint AddressOfEntryPoint; // Address of entry point
|
|
||||||
public uint BaseOfCode; // Base of code section
|
|
||||||
public uint BaseOfData; // Base of data section (PE32 only)
|
|
||||||
|
|
||||||
// Windows-specific fields
|
|
||||||
public ulong ImageBase; // Image base address (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 ulong SizeOfStackReserve; // Size of stack reserve (uint for PE32, ulong for PE32+)
|
|
||||||
public ulong SizeOfStackCommit; // Size of stack commit (uint for PE32, ulong for PE32+)
|
|
||||||
public ulong SizeOfHeapReserve; // Size of heap reserve (uint for PE32, ulong for PE32+)
|
|
||||||
public ulong SizeOfHeapCommit; // Size of heap commit (uint for PE32, ulong for PE32+)
|
|
||||||
public uint LoaderFlags; // Loader flags
|
|
||||||
public uint NumberOfRvaAndSizes; // Number of RVA and sizes
|
|
||||||
|
|
||||||
public DataDirectory[] DataDirectories = []; // Data directories
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the PE file is 64-bit based on the Magic value
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the PE file is 64-bit, false otherwise</returns>
|
|
||||||
public bool Is64Bit()
|
|
||||||
{
|
|
||||||
return Magic == PE32PLUS_MAGIC;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,45 +0,0 @@
|
|||||||
namespace X86Disassembler.PE.Types;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a section header in a PE file
|
|
||||||
/// </summary>
|
|
||||||
public class SectionHeader
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
|
|
||||||
public string Name = ""; // Section name
|
|
||||||
public uint VirtualSize; // Virtual size
|
|
||||||
public uint VirtualAddress; // Virtual address
|
|
||||||
public uint SizeOfRawData; // Size of raw data
|
|
||||||
public uint PointerToRawData; // Pointer to raw data
|
|
||||||
public uint PointerToRelocations; // Pointer to relocations
|
|
||||||
public uint PointerToLinenumbers; // Pointer to line numbers
|
|
||||||
public ushort NumberOfRelocations; // Number of relocations
|
|
||||||
public ushort NumberOfLinenumbers; // Number of line numbers
|
|
||||||
public uint Characteristics; // Characteristics
|
|
||||||
|
|
||||||
public bool ContainsCode()
|
|
||||||
{
|
|
||||||
return (Characteristics & IMAGE_SCN_CNT_CODE) != 0 ||
|
|
||||||
(Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsReadable()
|
|
||||||
{
|
|
||||||
return (Characteristics & IMAGE_SCN_MEM_READ) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsWritable()
|
|
||||||
{
|
|
||||||
return (Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsExecutable()
|
|
||||||
{
|
|
||||||
return (Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
using X86Disassembler.Analysers;
|
|
||||||
using X86Disassembler.PE;
|
|
||||||
using X86Disassembler.ProjectSystem;
|
|
||||||
|
|
||||||
namespace X86Disassembler;
|
|
||||||
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
private const string FilePath = @"C:\Program Files (x86)\Nikita\Iron Strategy\Terrain.dll";
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
byte[] fileBytes = File.ReadAllBytes(FilePath);
|
|
||||||
PeFile peFile = new PeFile(fileBytes);
|
|
||||||
peFile.Parse();
|
|
||||||
|
|
||||||
var projectPeFile = new ProjectPeFile()
|
|
||||||
{
|
|
||||||
ImageBase = new VirtualAddress(0, peFile.OptionalHeader.ImageBase),
|
|
||||||
Architecture = peFile.OptionalHeader.Is64Bit()
|
|
||||||
? "64-bit"
|
|
||||||
: "32-bit",
|
|
||||||
Name = Path.GetFileName(FilePath),
|
|
||||||
EntryPointAddress = new FileAbsoluteAddress(peFile.OptionalHeader.AddressOfEntryPoint, peFile.OptionalHeader.ImageBase)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
using X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
namespace X86Disassembler.ProjectSystem;
|
|
||||||
|
|
||||||
public class ProjectPeFile
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Architecture { get; set; }
|
|
||||||
|
|
||||||
public Address EntryPointAddress { get; set; }
|
|
||||||
|
|
||||||
public Address ImageBase { get; set; }
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
using X86Disassembler.Analysers;
|
|
||||||
|
|
||||||
namespace X86Disassembler.ProjectSystem;
|
|
||||||
|
|
||||||
public class ProjectPeFileSection
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public Address VirtualAddress { get; set; }
|
|
||||||
|
|
||||||
public ulong Size { get; set; }
|
|
||||||
|
|
||||||
public SectionFlags Flags { get; set; }
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
namespace X86Disassembler.ProjectSystem;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum SectionFlags
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Code = 1,
|
|
||||||
Exec = 2,
|
|
||||||
Read = 4,
|
|
||||||
Write = 8
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
namespace X86Disassembler.X86;
|
|
||||||
|
|
||||||
public class Constants
|
|
||||||
{
|
|
||||||
// ModR/M byte masks
|
|
||||||
public const byte MOD_MASK = 0xC0; // 11000000b
|
|
||||||
public const byte REG_MASK = 0x38; // 00111000b
|
|
||||||
public const byte RM_MASK = 0x07; // 00000111b
|
|
||||||
|
|
||||||
// SIB byte masks
|
|
||||||
public const byte SIB_SCALE_MASK = 0xC0; // 11000000b
|
|
||||||
public const byte SIB_INDEX_MASK = 0x38; // 00111000b
|
|
||||||
public const byte SIB_BASE_MASK = 0x07; // 00000111b
|
|
||||||
|
|
||||||
// Register names for different sizes
|
|
||||||
public static readonly string[] RegisterNames16 = {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di"};
|
|
||||||
public static readonly string[] RegisterNames32 = {"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"};
|
|
||||||
|
|
||||||
}
|
|
@@ -1,87 +0,0 @@
|
|||||||
using X86Disassembler.X86.Operands;
|
|
||||||
|
|
||||||
namespace X86Disassembler.X86;
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Core x86 instruction disassembler
|
|
||||||
/// </summary>
|
|
||||||
public class Disassembler
|
|
||||||
{
|
|
||||||
// The buffer containing the code to disassemble
|
|
||||||
private readonly byte[] _codeBuffer;
|
|
||||||
|
|
||||||
// The length of the buffer
|
|
||||||
private readonly int _length;
|
|
||||||
|
|
||||||
// The base address of the code
|
|
||||||
private readonly ulong _baseAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the Disassembler class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="codeBuffer">The buffer containing the code to disassemble</param>
|
|
||||||
/// <param name="baseAddress">The base address of the code</param>
|
|
||||||
public Disassembler(byte[] codeBuffer, ulong baseAddress)
|
|
||||||
{
|
|
||||||
_codeBuffer = codeBuffer;
|
|
||||||
_length = codeBuffer.Length;
|
|
||||||
_baseAddress = baseAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disassembles the code buffer sequentially and returns all disassembled instructions
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A list of disassembled instructions</returns>
|
|
||||||
public List<Instruction> Disassemble()
|
|
||||||
{
|
|
||||||
List<Instruction> instructions = new List<Instruction>();
|
|
||||||
|
|
||||||
// Create an instruction decoder
|
|
||||||
InstructionDecoder decoder = new InstructionDecoder(_codeBuffer, _length);
|
|
||||||
|
|
||||||
// Decode instructions until the end of the buffer is reached
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int position = decoder.GetPosition();
|
|
||||||
|
|
||||||
// Check if we've reached the end of the buffer
|
|
||||||
if (!decoder.CanReadByte())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the position before decoding to handle prefixes properly
|
|
||||||
int startPosition = position;
|
|
||||||
|
|
||||||
// Decode the instruction
|
|
||||||
Instruction? instruction = decoder.DecodeInstruction();
|
|
||||||
|
|
||||||
if (instruction != null)
|
|
||||||
{
|
|
||||||
// Adjust the instruction address to include the base address
|
|
||||||
instruction.Address = _baseAddress + (uint)startPosition;
|
|
||||||
|
|
||||||
// Add the instruction to the list
|
|
||||||
instructions.Add(instruction);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If decoding failed, create a dummy instruction for the unknown byte
|
|
||||||
byte unknownByte = decoder.ReadByte();
|
|
||||||
|
|
||||||
Instruction dummyInstruction = new Instruction
|
|
||||||
{
|
|
||||||
Address = _baseAddress + (uint)position,
|
|
||||||
Type = InstructionType.Unknown,
|
|
||||||
StructuredOperands = [OperandFactory.CreateImmediateOperand(unknownByte, 8),]
|
|
||||||
};
|
|
||||||
|
|
||||||
instructions.Add(dummyInstruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return instructions;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
namespace X86Disassembler.X86;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the index values for x87 floating-point unit registers.
|
|
||||||
/// These values correspond to the encoding used in x87 FPU instructions
|
|
||||||
/// for identifying the specific ST(i) register operands.
|
|
||||||
/// </summary>
|
|
||||||
public enum FpuRegisterIndex
|
|
||||||
{
|
|
||||||
/// <summary>FPU register ST(0)</summary>
|
|
||||||
ST0 = 0,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(1)</summary>
|
|
||||||
ST1 = 1,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(2)</summary>
|
|
||||||
ST2 = 2,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(3)</summary>
|
|
||||||
ST3 = 3,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(4)</summary>
|
|
||||||
ST4 = 4,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(5)</summary>
|
|
||||||
ST5 = 5,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(6)</summary>
|
|
||||||
ST6 = 6,
|
|
||||||
|
|
||||||
/// <summary>FPU register ST(7)</summary>
|
|
||||||
ST7 = 7,
|
|
||||||
}
|
|
@@ -1,71 +0,0 @@
|
|||||||
namespace X86Disassembler.X86.Handlers.Adc;
|
|
||||||
|
|
||||||
using Operands;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for ADC AX/EAX, imm16/32 instruction (opcode 0x15)
|
|
||||||
/// </summary>
|
|
||||||
public class AdcAccumulatorImmHandler : InstructionHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the AdcAccumulatorImmHandler class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="decoder">The instruction decoder that owns this handler</param>
|
|
||||||
public AdcAccumulatorImmHandler(InstructionDecoder decoder)
|
|
||||||
: base(decoder)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if this handler can decode the given opcode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode to check</param>
|
|
||||||
/// <returns>True if this handler can decode the opcode</returns>
|
|
||||||
public override bool CanHandle(byte opcode)
|
|
||||||
{
|
|
||||||
// ADC AX/EAX, imm16/32 is encoded as 0x15
|
|
||||||
return opcode == 0x15;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decodes a ADC AX/EAX, imm16/32 instruction
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode of the instruction</param>
|
|
||||||
/// <param name="instruction">The instruction object to populate</param>
|
|
||||||
/// <returns>True if the instruction was successfully decoded</returns>
|
|
||||||
public override bool Decode(byte opcode, Instruction instruction)
|
|
||||||
{
|
|
||||||
// Set the instruction type
|
|
||||||
instruction.Type = InstructionType.Adc;
|
|
||||||
|
|
||||||
// Determine operand size based on prefix
|
|
||||||
int operandSize = Decoder.HasOperandSizePrefix() ? 16 : 32;
|
|
||||||
|
|
||||||
// Check if we have enough bytes for the immediate value
|
|
||||||
if (operandSize == 16 && !Decoder.CanReadUShort())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (operandSize == 32 && !Decoder.CanReadUInt())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the accumulator register operand (AX or EAX)
|
|
||||||
var accumulatorOperand = OperandFactory.CreateRegisterOperand(RegisterIndex.A, operandSize);
|
|
||||||
|
|
||||||
// Read and create the immediate operand based on operand size
|
|
||||||
var immOperand = operandSize == 16
|
|
||||||
? OperandFactory.CreateImmediateOperand(Decoder.ReadUInt16(), operandSize)
|
|
||||||
: OperandFactory.CreateImmediateOperand(Decoder.ReadUInt32(), operandSize);
|
|
||||||
|
|
||||||
// Set the structured operands
|
|
||||||
instruction.StructuredOperands =
|
|
||||||
[
|
|
||||||
accumulatorOperand,
|
|
||||||
immOperand
|
|
||||||
];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,64 +0,0 @@
|
|||||||
using X86Disassembler.X86.Operands;
|
|
||||||
|
|
||||||
namespace X86Disassembler.X86.Handlers.Adc;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for ADC AL, imm8 instruction (0x14)
|
|
||||||
/// </summary>
|
|
||||||
public class AdcAlImmHandler : InstructionHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the AdcAlImmHandler class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="decoder">The instruction decoder that owns this handler</param>
|
|
||||||
public AdcAlImmHandler(InstructionDecoder decoder)
|
|
||||||
: base(decoder)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if this handler can decode the given opcode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode to check</param>
|
|
||||||
/// <returns>True if this handler can decode the opcode</returns>
|
|
||||||
public override bool CanHandle(byte opcode)
|
|
||||||
{
|
|
||||||
return opcode == 0x14;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decodes an ADC AL, imm8 instruction
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode of the instruction</param>
|
|
||||||
/// <param name="instruction">The instruction object to populate</param>
|
|
||||||
/// <returns>True if the instruction was successfully decoded</returns>
|
|
||||||
public override bool Decode(byte opcode, Instruction instruction)
|
|
||||||
{
|
|
||||||
// Set the instruction type
|
|
||||||
instruction.Type = InstructionType.Adc;
|
|
||||||
|
|
||||||
// Check if we have enough bytes for the immediate value
|
|
||||||
if (!Decoder.CanReadByte())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the immediate byte
|
|
||||||
var imm8 = Decoder.ReadByte();
|
|
||||||
|
|
||||||
// Create the AL register operand
|
|
||||||
var destinationOperand = OperandFactory.CreateRegisterOperand8(RegisterIndex8.AL);
|
|
||||||
|
|
||||||
// Create the immediate operand
|
|
||||||
var sourceOperand = OperandFactory.CreateImmediateOperand(imm8);
|
|
||||||
|
|
||||||
// Set the structured operands
|
|
||||||
instruction.StructuredOperands =
|
|
||||||
[
|
|
||||||
destinationOperand,
|
|
||||||
sourceOperand
|
|
||||||
];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,82 +0,0 @@
|
|||||||
namespace X86Disassembler.X86.Handlers.Adc;
|
|
||||||
|
|
||||||
using Operands;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for ADC r/m16, imm16 instruction (0x81 /2 with 0x66 prefix)
|
|
||||||
/// </summary>
|
|
||||||
public class AdcImmToRm16Handler : InstructionHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the AdcImmToRm16Handler class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="decoder">The instruction decoder that owns this handler</param>
|
|
||||||
public AdcImmToRm16Handler(InstructionDecoder decoder)
|
|
||||||
: base(decoder)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if this handler can decode the given opcode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode to check</param>
|
|
||||||
/// <returns>True if this handler can decode the opcode</returns>
|
|
||||||
public override bool CanHandle(byte opcode)
|
|
||||||
{
|
|
||||||
// ADC r/m16, imm16 is encoded as 0x81 /2 with 0x66 prefix
|
|
||||||
if (opcode != 0x81)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have enough bytes to read the ModR/M byte
|
|
||||||
if (!Decoder.CanReadByte())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the reg field of the ModR/M byte is 2 (ADC)
|
|
||||||
var reg = ModRMDecoder.PeakModRMReg();
|
|
||||||
|
|
||||||
// Only handle when the operand size prefix is present
|
|
||||||
return reg == 2 && Decoder.HasOperandSizePrefix();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decodes a ADC r/m16, imm16 instruction
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode of the instruction</param>
|
|
||||||
/// <param name="instruction">The instruction object to populate</param>
|
|
||||||
/// <returns>True if the instruction was successfully decoded</returns>
|
|
||||||
public override bool Decode(byte opcode, Instruction instruction)
|
|
||||||
{
|
|
||||||
// Set the instruction type
|
|
||||||
instruction.Type = InstructionType.Adc;
|
|
||||||
|
|
||||||
// Read the ModR/M byte, specifying that we're dealing with 16-bit operands
|
|
||||||
var (_, _, _, destinationOperand) = ModRMDecoder.ReadModRM16();
|
|
||||||
|
|
||||||
// Note: The operand size is already set to 16-bit by the ReadModRM16 method
|
|
||||||
|
|
||||||
// Check if we have enough bytes for the immediate value
|
|
||||||
if (!Decoder.CanReadUShort())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the immediate value
|
|
||||||
ushort imm16 = Decoder.ReadUInt16();
|
|
||||||
|
|
||||||
// Create the immediate operand
|
|
||||||
var sourceOperand = OperandFactory.CreateImmediateOperand(imm16, 16);
|
|
||||||
|
|
||||||
// Set the structured operands
|
|
||||||
instruction.StructuredOperands =
|
|
||||||
[
|
|
||||||
destinationOperand,
|
|
||||||
sourceOperand
|
|
||||||
];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,84 +0,0 @@
|
|||||||
namespace X86Disassembler.X86.Handlers.Adc;
|
|
||||||
|
|
||||||
using Operands;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for ADC r/m16, imm8 (sign-extended) instruction (0x83 /2 with 0x66 prefix)
|
|
||||||
/// </summary>
|
|
||||||
public class AdcImmToRm16SignExtendedHandler : InstructionHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the AdcImmToRm16SignExtendedHandler class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="decoder">The instruction decoder that owns this handler</param>
|
|
||||||
public AdcImmToRm16SignExtendedHandler(InstructionDecoder decoder)
|
|
||||||
: base(decoder)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if this handler can decode the given opcode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode to check</param>
|
|
||||||
/// <returns>True if this handler can decode the opcode</returns>
|
|
||||||
public override bool CanHandle(byte opcode)
|
|
||||||
{
|
|
||||||
// ADC r/m16, imm8 (sign-extended) is encoded as 0x83 /2 with 0x66 prefix
|
|
||||||
if (opcode != 0x83)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have enough bytes to read the ModR/M byte
|
|
||||||
if (!Decoder.CanReadByte())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the reg field of the ModR/M byte is 2 (ADC)
|
|
||||||
var reg = ModRMDecoder.PeakModRMReg();
|
|
||||||
|
|
||||||
// Only handle when the operand size prefix is present
|
|
||||||
return reg == 2 && Decoder.HasOperandSizePrefix();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decodes a ADC r/m16, imm8 (sign-extended) instruction
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opcode">The opcode of the instruction</param>
|
|
||||||
/// <param name="instruction">The instruction object to populate</param>
|
|
||||||
/// <returns>True if the instruction was successfully decoded</returns>
|
|
||||||
public override bool Decode(byte opcode, Instruction instruction)
|
|
||||||
{
|
|
||||||
// Set the instruction type
|
|
||||||
instruction.Type = InstructionType.Adc;
|
|
||||||
|
|
||||||
// For ADC r/m16, imm8 (sign-extended) (0x83 /2 with 0x66 prefix):
|
|
||||||
// - The r/m field with mod specifies the destination operand (register or memory)
|
|
||||||
// - The immediate value is the source operand (sign-extended from 8 to 16 bits)
|
|
||||||
var (_, _, _, destinationOperand) = ModRMDecoder.ReadModRM16();
|
|
||||||
|
|
||||||
// Note: The operand size is already set to 16-bit by the ReadModRM16 method
|
|
||||||
|
|
||||||
// Check if we have enough bytes for the immediate value
|
|
||||||
if (!Decoder.CanReadByte())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the immediate value (sign-extended from 8 to 16 bits)
|
|
||||||
short imm16 = (sbyte)Decoder.ReadByte();
|
|
||||||
|
|
||||||
// Create the immediate operand
|
|
||||||
var sourceOperand = OperandFactory.CreateImmediateOperand((ushort)imm16, 16);
|
|
||||||
|
|
||||||
// Set the structured operands
|
|
||||||
instruction.StructuredOperands =
|
|
||||||
[
|
|
||||||
destinationOperand,
|
|
||||||
sourceOperand
|
|
||||||
];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user