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));
+ }
+ }
+}
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..a76d420 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 < _luaBytecodeMagic.Length)
+ 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);
}
@@ -96,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++;
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);