From d95a00eef12afca8d16e74e0941e2a112d51922a Mon Sep 17 00:00:00 2001 From: jezztify Date: Sun, 8 Feb 2026 10:50:42 +1300 Subject: [PATCH 1/3] Decompile LUB files on extact - Added option in Settings > General - Allow decompilation of lub files instead of the binary files when extracting them --- GRF/AssemblyInfo.cs | 3 ++ GRF/GrfSystem/Settings.cs | 1 + GRF/Threading/GrfThreadExtract.cs | 38 +++++++++++++++++++ GRFEditor.sln | 6 +++ .../GrfEditorConfiguration.cs | 8 ++++ GRFEditor/EditorMainWindow.xaml.cs | 1 + GRFEditor/WPF/SettingsDialog.xaml.cs | 1 + 7 files changed, 58 insertions(+) create mode 100644 GRF/AssemblyInfo.cs diff --git a/GRF/AssemblyInfo.cs b/GRF/AssemblyInfo.cs new file mode 100644 index 0000000..0e94b81 --- /dev/null +++ b/GRF/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("GRF.Tests")] diff --git a/GRF/GrfSystem/Settings.cs b/GRF/GrfSystem/Settings.cs index cc0344b..7fce4b2 100644 --- a/GRF/GrfSystem/Settings.cs +++ b/GRF/GrfSystem/Settings.cs @@ -11,6 +11,7 @@ public static class Settings { public static bool CpuMonitoringEnabled = true; public static bool LockFiles { get; set; } public static bool PreserveDirectoryStructure { get; set; } + public static bool DecompileLubOnExtract { get; set; } public static Action OnSavingFailed; public static float CpuUsageCritical = 90f; public static bool AddHashFileForThor = false; diff --git a/GRF/Threading/GrfThreadExtract.cs b/GRF/Threading/GrfThreadExtract.cs index 4d92d5a..068e231 100644 --- a/GRF/Threading/GrfThreadExtract.cs +++ b/GRF/Threading/GrfThreadExtract.cs @@ -4,7 +4,10 @@ using System.Linq; using System.Threading; using GRF.Core; +using GRF.FileFormats.LubFormat; +using GRF.GrfSystem; using Utilities.Extension; +using Utilities.Services; namespace GRF.Threading { /// @@ -12,9 +15,40 @@ namespace GRF.Threading { /// It's used to optimize the data transfer. /// public class GrfThreadExtract : GrfWriterThread { + private static readonly byte[] _luaBytecodeMagic = { 0x1b, 0x4c, 0x75, 0x61 }; private const int _bufferSize = 8388608; private readonly StreamReadBlockInfo _srb = new StreamReadBlockInfo(_bufferSize); + internal static bool IsLuaBytecode(byte[] data) { + if (data == null || data.Length < 4) + return false; + + for (int i = 0; i < _luaBytecodeMagic.Length; i++) { + if (data[i] != _luaBytecodeMagic[i]) + return false; + } + + return true; + } + + private static bool _shouldDecompileLub(FileEntry entry, byte[] data) { + if (!Settings.DecompileLubOnExtract) + return false; + + string extension = entry.RelativePath.GetExtension(); + return extension != null && extension.Equals(".lub", StringComparison.OrdinalIgnoreCase) && IsLuaBytecode(data); + } + + private static byte[] _tryDecompileLub(byte[] data) { + try { + string text = new Lub(data).Decompile(); + return EncodingService.DisplayEncoding.GetBytes(text); + } + catch { + return data; + } + } + public override void Start() { new Thread(_start) { Name = "GRF - Extract files thread " + StartIndex }.Start(); } @@ -73,6 +107,10 @@ private void _start() { else dataTmp = Compression.Decompress(dataTmp, entry.SizeDecompressed); + if (_shouldDecompileLub(entry, dataTmp)) { + dataTmp = _tryDecompileLub(dataTmp); + } + using (FileStream fs = new FileStream(entry.ExtractionFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) fs.Write(dataTmp, 0, dataTmp.Length); } diff --git a/GRFEditor.sln b/GRFEditor.sln index 3121b13..ec684ff 100644 --- a/GRFEditor.sln +++ b/GRFEditor.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GRFEditor", "GRFEditor\GRFE EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GRF", "GRF\GRF.csproj", "{F21476B8-04E5-43EE-AD1B-CF7BD2ED3D4F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GRF.Tests", "GRF.Tests\GRF.Tests.csproj", "{E59F3B21-1C1D-4D68-B137-821E89C2F52D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokeiLibrary", "TokeiLibrary\TokeiLibrary.csproj", "{812355C7-A277-4E8C-B6A0-306310F8F183}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleProject", "ExampleProject\ExampleProject.csproj", "{8030A7E4-9456-481B-A111-7AF0DB19ABFD}" @@ -44,6 +46,10 @@ Global {F21476B8-04E5-43EE-AD1B-CF7BD2ED3D4F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F21476B8-04E5-43EE-AD1B-CF7BD2ED3D4F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F21476B8-04E5-43EE-AD1B-CF7BD2ED3D4F}.Release|Any CPU.Build.0 = Release|Any CPU + {E59F3B21-1C1D-4D68-B137-821E89C2F52D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E59F3B21-1C1D-4D68-B137-821E89C2F52D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E59F3B21-1C1D-4D68-B137-821E89C2F52D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E59F3B21-1C1D-4D68-B137-821E89C2F52D}.Release|Any CPU.Build.0 = Release|Any CPU {812355C7-A277-4E8C-B6A0-306310F8F183}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {812355C7-A277-4E8C-B6A0-306310F8F183}.Debug|Any CPU.Build.0 = Debug|Any CPU {812355C7-A277-4E8C-B6A0-306310F8F183}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/GRFEditor/ApplicationConfiguration/GrfEditorConfiguration.cs b/GRFEditor/ApplicationConfiguration/GrfEditorConfiguration.cs index ad2e51e..00ca05e 100644 --- a/GRFEditor/ApplicationConfiguration/GrfEditorConfiguration.cs +++ b/GRFEditor/ApplicationConfiguration/GrfEditorConfiguration.cs @@ -444,6 +444,14 @@ public static bool AlwaysOpenAfterExtraction { set { ConfigAsker["[GRFEditor - ExtractingService - Always open after extraction]"] = value.ToString(); } } + public static bool DecompileLubOnExtract { + get { return Boolean.Parse(ConfigAsker["[GRFEditor - ExtractingService - Decompile LUB on extract]", false.ToString()]); } + set { + ConfigAsker["[GRFEditor - ExtractingService - Decompile LUB on extract]"] = value.ToString(); + Settings.DecompileLubOnExtract = value; + } + } + public static bool AutomaticallyPlaceFiles { get { return Boolean.Parse(ConfigAsker["[GRFEditor - AutomaticallyPlaceFiles]", false.ToString()]); } set { ConfigAsker["[GRFEditor - AutomaticallyPlaceFiles]"] = value.ToString(); } diff --git a/GRFEditor/EditorMainWindow.xaml.cs b/GRFEditor/EditorMainWindow.xaml.cs index 8fc4256..a874462 100644 --- a/GRFEditor/EditorMainWindow.xaml.cs +++ b/GRFEditor/EditorMainWindow.xaml.cs @@ -81,6 +81,7 @@ private int _loadBasicSettings() { Settings.CpuMonitoringEnabled = Configuration.CpuPerformanceManagement; Settings.LockFiles = Configuration.LockFiles; Settings.AddHashFileForThor = Configuration.AddHashFileForThor; + Settings.DecompileLubOnExtract = Configuration.DecompileLubOnExtract; TemporaryFilesManager.ClearTemporaryFiles(); Settings.OnSavingFailed = _onSavingFailed; return encoding; diff --git a/GRFEditor/WPF/SettingsDialog.xaml.cs b/GRFEditor/WPF/SettingsDialog.xaml.cs index 8d83ec1..7e503a4 100644 --- a/GRFEditor/WPF/SettingsDialog.xaml.cs +++ b/GRFEditor/WPF/SettingsDialog.xaml.cs @@ -116,6 +116,7 @@ public SettingsDialog(GrfHolder grfData, EditorMainWindow editor) _add(_gridGeneral, row = 9, "CPU performance management", "This option monitors your CPU usage and it'll be used to dynamically change the number of threads doing work. " + "The main purpose of this feature is to avoid situations where the CPU could reach 100% usage and hence lagging the entire system.", () => GrfEditorConfiguration.CpuPerformanceManagement, () => Settings.CpuMonitoringEnabled = GrfEditorConfiguration.CpuPerformanceManagement); _add(_gridGeneral, ++row, "Always open folder after extraction", "Prevents services from showing a file or folder in Windows Explorer after an extraction.", () => GrfEditorConfiguration.AlwaysOpenAfterExtraction, () => OpeningService.Enabled = GrfEditorConfiguration.AlwaysOpenAfterExtraction); + _add(_gridGeneral, ++row, "Decompile LUB on extraction", "If enabled, compiled .lub files will be decompiled to text during extraction while keeping the .lub extension.", () => GrfEditorConfiguration.DecompileLubOnExtract, () => Settings.DecompileLubOnExtract = GrfEditorConfiguration.DecompileLubOnExtract); _add(_gridGeneral, ++row, "Always reopen latest opened GRF", "Always reopen the most recently opened GRF when starting the application.", () => GrfEditorConfiguration.AlwaysReopenLatestGrf); //_add(_gridGeneral, ++row, "Use the opened GRF path to extract", "If enabled, the files extraced will be placed in the same folder as the GRF. They will be placed within the working directory of the application otherwise.", () => GrfEditorConfiguration.UseGrfPathToExtract); _add(_gridGeneral, ++row, "Enable windows ownership", "Makes the windows linked together, resulting in only being able to focus on one at a time. Disabling this features enables you to open multiple windows (as in tools).", () => Configuration.EnableWindowsOwnership); From 26d519ec33c13c0d243fb932c60e65ee8bb48188 Mon Sep 17 00:00:00 2001 From: jezztify Date: Sun, 8 Feb 2026 12:38:17 +1300 Subject: [PATCH 2/3] GRF Tests --- GRF.Tests/GRF.Tests.csproj | 21 +++++++++++++++++++++ GRF.Tests/GrfThreadExtractTests.cs | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 GRF.Tests/GRF.Tests.csproj create mode 100644 GRF.Tests/GrfThreadExtractTests.cs diff --git a/GRF.Tests/GRF.Tests.csproj b/GRF.Tests/GRF.Tests.csproj new file mode 100644 index 0000000..b1d4136 --- /dev/null +++ b/GRF.Tests/GRF.Tests.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/GRF.Tests/GrfThreadExtractTests.cs b/GRF.Tests/GrfThreadExtractTests.cs new file mode 100644 index 0000000..eb6be86 --- /dev/null +++ b/GRF.Tests/GrfThreadExtractTests.cs @@ -0,0 +1,18 @@ +using GRF.Threading; +using Xunit; + +namespace GRF.Tests { + public class GrfThreadExtractTests { + [Fact] + public void IsLuaBytecode_ReturnsTrueForMagicHeader() { + byte[] data = { 0x1b, 0x4c, 0x75, 0x61, 0x00 }; + Assert.True(GrfThreadExtract.IsLuaBytecode(data)); + } + + [Fact] + public void IsLuaBytecode_ReturnsFalseForNonMagicHeader() { + byte[] data = { 0x00, 0x4c, 0x75, 0x61 }; + Assert.False(GrfThreadExtract.IsLuaBytecode(data)); + } + } +} From 881425630b682e74854e66b45977064f36506567 Mon Sep 17 00:00:00 2001 From: jezztify Date: Sun, 8 Feb 2026 12:55:30 +1300 Subject: [PATCH 3/3] - Added decompile-on-extract handling for Modification.Added entries by reading source bytes, detecting bytecode, and writing the decompiled output. - Included error handling consistent with the main extraction branch. --- GRF/Threading/GrfThreadExtract.cs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/GRF/Threading/GrfThreadExtract.cs b/GRF/Threading/GrfThreadExtract.cs index 068e231..a76d420 100644 --- a/GRF/Threading/GrfThreadExtract.cs +++ b/GRF/Threading/GrfThreadExtract.cs @@ -20,7 +20,7 @@ public class GrfThreadExtract : GrfWriterThread { private readonly StreamReadBlockInfo _srb = new StreamReadBlockInfo(_bufferSize); internal static bool IsLuaBytecode(byte[] data) { - if (data == null || data.Length < 4) + if (data == null || data.Length < _luaBytecodeMagic.Length) return false; for (int i = 0; i < _luaBytecodeMagic.Length; i++) { @@ -134,10 +134,28 @@ private void _start() { if (IsPaused) Pause(); - if (File.Exists(entryCopy.ExtractionFilePath)) - File.Delete(entryCopy.ExtractionFilePath); - - File.Copy(entryCopy.SourceFilePath, entryCopy.ExtractionFilePath); + try { + if (File.Exists(entryCopy.ExtractionFilePath)) + File.Delete(entryCopy.ExtractionFilePath); + + if (Settings.DecompileLubOnExtract) { + byte[] rawData = File.ReadAllBytes(entryCopy.SourceFilePath); + if (_shouldDecompileLub(entryCopy, rawData)) { + rawData = _tryDecompileLub(rawData); + File.WriteAllBytes(entryCopy.ExtractionFilePath, rawData); + } + else { + File.WriteAllBytes(entryCopy.ExtractionFilePath, rawData); + } + } + else { + File.Copy(entryCopy.SourceFilePath, entryCopy.ExtractionFilePath); + } + } + catch (Exception err) { + Error = true; + Exception = new Exception("#File: " + entryCopy.RelativePath, err); + } } NumberOfFilesProcessed++;