From c27b77cbfce8f5d60a3bae46398ff71efc30b336 Mon Sep 17 00:00:00 2001 From: bird_egop Date: Sun, 23 Nov 2025 00:04:05 +0300 Subject: [PATCH] parse PAL file --- PalLib/PalFile.cs | 54 ++++++++++++++++++++++++ PalLib/PalLib.csproj | 7 +++ PalLib/PalParser.cs | 37 ++++++++++++++++ ParkanPlayground.slnx | 1 + ParkanPlayground/ParkanPlayground.csproj | 1 + ParkanPlayground/Program.cs | 37 +++++++++------- README.md | 3 ++ VarsetLib/BinaryVarsetFile.cs | 3 ++ 8 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 PalLib/PalFile.cs create mode 100644 PalLib/PalLib.csproj create mode 100644 PalLib/PalParser.cs diff --git a/PalLib/PalFile.cs b/PalLib/PalFile.cs new file mode 100644 index 0000000..082fc9c --- /dev/null +++ b/PalLib/PalFile.cs @@ -0,0 +1,54 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace PalLib; + +/// +/// PAL файл по сути это indexed текстура (1024 байт - 256 цветов lookup + 4 байта "Ipol" и затем 256x256 индексов в lookup) +/// +public class PalFile +{ + public required string FileName { get; set; } + + /// + /// 256 цветов lookup (1024 байт) + /// + public required byte[] Palette { get; set; } + + /// + /// 256x256 индексов в lookup + /// + public required byte[] Indices { get; set; } + + public void SaveAsPng(string outputPath) + { + const int width = 256; + const int height = 256; + + var rgbaBytes = new byte[width * height * 4]; + + for (int i = 0; i < Indices.Length; i++) + { + var index = Indices[i]; + + // Palette is 256 colors * 4 bytes (ARGB usually, based on TexmLib) + // TexmLib: r = lookup[i*4+0], g = lookup[i*4+1], b = lookup[i*4+2], a = lookup[i*4+3] + // Assuming same format here. + + // since PAL is likely directx related, the format is is likely BGRA + + var b = Palette[index * 4 + 0]; + var g = Palette[index * 4 + 1]; + var r = Palette[index * 4 + 2]; + var a = Palette[index * 4 + 3]; // Alpha? Or is it unused/padding? TexmLib sets alpha to 255 manually for indexed. + + rgbaBytes[i * 4 + 0] = r; + rgbaBytes[i * 4 + 1] = g; + rgbaBytes[i * 4 + 2] = b; + rgbaBytes[i * 4 + 3] = 255; + } + + using var image = Image.LoadPixelData(rgbaBytes, width, height); + image.SaveAsPng(outputPath); + } +} diff --git a/PalLib/PalLib.csproj b/PalLib/PalLib.csproj new file mode 100644 index 0000000..a0f23a0 --- /dev/null +++ b/PalLib/PalLib.csproj @@ -0,0 +1,7 @@ + + + + + + + diff --git a/PalLib/PalParser.cs b/PalLib/PalParser.cs new file mode 100644 index 0000000..efcc815 --- /dev/null +++ b/PalLib/PalParser.cs @@ -0,0 +1,37 @@ +using System.Text; + +namespace PalLib; + +public class PalParser +{ + public static PalFile ReadFromStream(Stream stream, string filename) + { + // Expected size: 1024 (palette) + 4 ("Ipol") + 65536 (indices) = 66564 + if (stream.Length != 66564) + { + throw new InvalidDataException($"Invalid PAL file size. Expected 66564 bytes, got {stream.Length}."); + } + + var palette = new byte[1024]; + stream.ReadExactly(palette, 0, 1024); + + var signatureBytes = new byte[4]; + stream.ReadExactly(signatureBytes, 0, 4); + var signature = Encoding.ASCII.GetString(signatureBytes); + + if (signature != "Ipol") + { + throw new InvalidDataException($"Invalid PAL file signature. Expected 'Ipol', got '{signature}'."); + } + + var indices = new byte[65536]; + stream.ReadExactly(indices, 0, 65536); + + return new PalFile + { + FileName = filename, + Palette = palette, + Indices = indices + }; + } +} diff --git a/ParkanPlayground.slnx b/ParkanPlayground.slnx index 5383a33..ac5de6f 100644 --- a/ParkanPlayground.slnx +++ b/ParkanPlayground.slnx @@ -12,6 +12,7 @@ + diff --git a/ParkanPlayground/ParkanPlayground.csproj b/ParkanPlayground/ParkanPlayground.csproj index b749716..ed750c5 100644 --- a/ParkanPlayground/ParkanPlayground.csproj +++ b/ParkanPlayground/ParkanPlayground.csproj @@ -7,6 +7,7 @@ + diff --git a/ParkanPlayground/Program.cs b/ParkanPlayground/Program.cs index c5c064b..7449e01 100644 --- a/ParkanPlayground/Program.cs +++ b/ParkanPlayground/Program.cs @@ -2,6 +2,7 @@ using System.Buffers.Binary; using Common; using MissionTmaLib.Parsing; using NResLib; +using PalLib; using ParkanPlayground; using VarsetLib; @@ -17,21 +18,27 @@ using VarsetLib; // converter.Convert("E:\\ParkanUnpacked\\static.rlb\\2_MESH_s_stn_0_01.msh"); // converter.Convert("E:\\ParkanUnpacked\\bases.rlb\\25_MESH_R_H_02.msh"); +var fs = new FileStream("E:\\ParkanUnpacked\\sys.lib\\1_Palm_PAL.PAL", FileMode.Open, + FileAccess.Read, FileShare.Read); -foreach (var path in Directory.EnumerateFiles("E:\\ParkanUnpacked\\behpsp.res")) -{ - using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); +var palFile = PalParser.ReadFromStream(fs, "1_Palm_PAL.PAL"); - var file = BinaryVarsetFileParser.Parse(fs); +palFile.SaveAsPng("pal.png"); - _ = 5; -} - -{ - var fs = new FileStream("E:\\ParkanUnpacked\\behpsp.res\\31_00 00 00 00_prof_generator.var", FileMode.Open, - FileAccess.Read, FileShare.Read); - - var file = BinaryVarsetFileParser.Parse(fs); - - _ = 5; -} \ No newline at end of file +// foreach (var path in Directory.EnumerateFiles("E:\\ParkanUnpacked\\behpsp.res")) +// { +// using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); +// +// var file = BinaryVarsetFileParser.Parse(fs); +// +// _ = 5; +// } +// +// { +// var fs = new FileStream("E:\\ParkanUnpacked\\behpsp.res\\31_00 00 00 00_prof_generator.var", FileMode.Open, +// FileAccess.Read, FileShare.Read); +// +// var file = BinaryVarsetFileParser.Parse(fs); +// +// _ = 5; +// } \ No newline at end of file diff --git a/README.md b/README.md index 44ac408..042e89a 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,9 @@ IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4) - `0x701` - INetworkInterface - `0x10d` - CreateVertexBufferData +## SuperAI = Clan с точки зрения индексации + +Т.е. у каждого клана свой SuperAI ## Опции diff --git a/VarsetLib/BinaryVarsetFile.cs b/VarsetLib/BinaryVarsetFile.cs index 490c9bb..5f7f84f 100644 --- a/VarsetLib/BinaryVarsetFile.cs +++ b/VarsetLib/BinaryVarsetFile.cs @@ -1,3 +1,6 @@ namespace VarsetLib; +/// +/// Бинарный файл, который можно найти в behpsp.res +/// public record BinaryVarsetFile(int Count, List Items);