diff --git a/ConverterApp/App.axaml b/ConverterApp/App.axaml
new file mode 100644
index 00000000..8f1d37ae
--- /dev/null
+++ b/ConverterApp/App.axaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ConverterApp/App.axaml.cs b/ConverterApp/App.axaml.cs
new file mode 100644
index 00000000..15de164a
--- /dev/null
+++ b/ConverterApp/App.axaml.cs
@@ -0,0 +1,86 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using ReactiveUI;
+using ReactiveUI.Avalonia;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Windows.Input;
+
+namespace LSTools.DivineGUI;
+
+public class BatchProcessItem : ReactiveObject
+{
+ private bool _isChecked;
+ private string _name = string.Empty;
+ private string _type = string.Empty;
+ private string _batchStatusText = string.Empty;
+ private double _batchProgressValue;
+
+ public bool IsChecked { get => _isChecked; set => this.RaiseAndSetIfChanged(ref _isChecked, value); }
+ public string Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
+ public string Type { get => _type; set => this.RaiseAndSetIfChanged(ref _type, value); }
+
+ public ConverterAppSettings Settings { get; set; } = new();
+
+ public bool FlipUVs { get; set; }
+ public bool MirrorSkeletons { get; set; }
+ public bool ApplyBasisTransforms { get; set; }
+ public bool MeshRigidChecked { get; set; }
+ public bool MeshRigidEnabled { get; set; }
+ public bool MeshClothChecked { get; set; }
+ public bool MeshClothEnabled { get; set; }
+ public bool MeshProxyChecked { get; set; }
+ public bool MeshProxyEnabled { get; set; }
+
+ public string BatchStatusText { get => _batchStatusText; set => this.RaiseAndSetIfChanged(ref _batchStatusText, value); }
+ public double BatchProgressValue { get => _batchProgressValue; set => this.RaiseAndSetIfChanged(ref _batchProgressValue, value); }
+}
+
+public class MainSettingsProvider : ISettingsDataSource
+{
+ public ConverterAppSettings Settings { get; set; } = new();
+}
+
+public partial class App : Application
+{
+ public ObservableCollection BatchProcessItemsCollection { get; } = [];
+ public ICommand? BrowsePathCommand { get; set; }
+ public ICommand? SaveOutputCommand { get; set; }
+
+ [STAThread]
+ public static void Main(string[] args)
+ {
+ CultureInfo customCulture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
+ customCulture.NumberFormat.NumberDecimalSeparator = ".";
+
+ Thread.CurrentThread.CurrentCulture = customCulture;
+ Thread.CurrentThread.CurrentUICulture = customCulture;
+ CultureInfo.DefaultThreadCurrentCulture = customCulture;
+ CultureInfo.DefaultThreadCurrentUICulture = customCulture;
+
+ AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace()
+ .UseReactiveUI(rxui => { })
+ .RegisterReactiveUIViewsFromEntryAssembly()
+ .StartWithClassicDesktopLifetime(args);
+ }
+
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ var mainSettingsContext = new MainSettingsProvider();
+ desktop.MainWindow = new MainWindow(mainSettingsContext);
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/ConverterApp/App.config b/ConverterApp/App.config
deleted file mode 100644
index d0f8440b..00000000
--- a/ConverterApp/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/ConverterApp/ConverterApp.csproj b/ConverterApp/ConverterApp.csproj
index e8a4d4c4..4adbf123 100644
--- a/ConverterApp/ConverterApp.csproj
+++ b/ConverterApp/ConverterApp.csproj
@@ -1,94 +1,44 @@
-
- net8.0-windows
- WinExe
- publish\
- true
- Disk
- false
- Foreground
- 7
- Days
- false
- false
- true
- 0
- 1.0.0.%2a
- false
- false
- true
- false
- true
- true
-
-
- x64
-
-
- x64
-
-
-
-
- Always
-
-
- bin\Editor Debug\
- true
- MinimumRecommendedRules.ruleset
-
-
-
- UserControl
-
-
- Component
-
-
- UserControl
-
-
- UserControl
-
-
- UserControl
-
-
- UserControl
-
-
- UserControl
-
-
- UserControl
-
-
-
-
-
-
-
- False
- Microsoft .NET Framework 4.5 %28x86 and x64%29
- true
-
-
- False
- .NET Framework 3.5 SP1 Client Profile
- false
-
-
- False
- .NET Framework 3.5 SP1
- false
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+ net10.0
+ WinExe
+ LSTools.DivineGUI
+ LSTools.DivineGUI.App
+
+ 14
+ enable
+ enable
+ true
+
+ true
+ link
+ Speed
+
+ true
+
+ x64
+ AnyCPU;x64
+
+ LSLib - Divine Cross-Platform GUI Tool
+ LSLib
+ Copyright © Norbyte 2012-2026
+ 1.18.5.0
+ 1.18.5.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ConverterApp/ConverterAppSettings.cs b/ConverterApp/ConverterAppSettings.cs
index e377ce57..4cd112e7 100644
--- a/ConverterApp/ConverterAppSettings.cs
+++ b/ConverterApp/ConverterAppSettings.cs
@@ -1,412 +1,166 @@
-using LSLib.Granny.Model;
-using LSLib.LS;
+using LSLib.LS;
using LSLib.LS.Enums;
-using System;
-using System.Collections.Generic;
+using ReactiveUI;
using System.ComponentModel;
-using System.Globalization;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
+using System.Text.Json;
+using System.Text.Json.Serialization;
-namespace ConverterApp;
+namespace LSTools.DivineGUI;
public interface ISettingsDataSource
{
ConverterAppSettings Settings { get; set; }
}
-public class SettingsBase : INotifyPropertyChanged
+public interface IGameSettingsTarget
{
- public event PropertyChangedEventHandler PropertyChanged;
-
- protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
- {
- var handler = PropertyChanged;
- if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
- }
+ void SetGame(Game game);
}
-public class ConverterAppSettings : SettingsBase
+public class ConverterAppSettings : ReactiveObject
{
- private GR2PaneSettings gr2;
-
- public GR2PaneSettings GR2
- {
- get { return gr2; }
- set { gr2 = value; }
- }
-
- private PackagePaneSettings pakSettings;
-
- public PackagePaneSettings PAK
- {
- get { return pakSettings; }
- set { pakSettings = value; }
- }
-
- private ResourcePaneSettings resourceSettings;
-
- public ResourcePaneSettings Resources
- {
- get { return resourceSettings; }
- set { resourceSettings = value; }
- }
-
- private VirtualTexturesPaneSettings virtualTextureSettings;
- public VirtualTexturesPaneSettings VirtualTextures
- {
- get { return virtualTextureSettings; }
- set { virtualTextureSettings = value; }
- }
-
- private OsirisPaneSettings storySettings;
-
- public OsirisPaneSettings Story
- {
- get { return storySettings; }
- set { storySettings = value; }
- }
-
- private DebugPaneSettings debugSettings;
-
- public DebugPaneSettings Debugging
- {
- get { return debugSettings; }
- set { debugSettings = value; }
- }
-
- private Game selectedGame = Game.BaldursGate3;
+ public GR2PaneSettings GR2 { get; init; } = new();
+ public PackagePaneSettings PAK { get; init; } = new();
+ public ResourcePaneSettings Resources { get; init; } = new();
+ public VirtualTexturesPaneSettings VirtualTextures { get; init; } = new();
+ public OsirisPaneSettings Story { get; init; } = new();
+ public DebugPaneSettings Debugging { get; init; } = new();
public int SelectedGame
{
- get { return (int)selectedGame; }
- set { selectedGame = (Game)value; OnPropertyChanged(); }
- }
-
- private string version = "";
+ get;
+ set => this.RaiseAndSetIfChanged(ref field, value);
+ } = 4; // Defaults to index 4 (Baldur's Gate 3)
- public string Version
- {
- get { return version; }
- set { version = value; }
- }
+ public string Version { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
public void SetPropertyChangedEvent(PropertyChangedEventHandler eventHandler)
{
- this.PropertyChanged += eventHandler;
+ PropertyChanged += eventHandler;
GR2.PropertyChanged += eventHandler;
PAK.PropertyChanged += eventHandler;
Resources.PropertyChanged += eventHandler;
+ VirtualTextures.PropertyChanged += eventHandler;
Story.PropertyChanged += eventHandler;
- }
-
- public ConverterAppSettings()
- {
- GR2 = new GR2PaneSettings();
- PAK = new PackagePaneSettings();
- Resources = new ResourcePaneSettings();
- VirtualTextures = new VirtualTexturesPaneSettings();
- Story = new OsirisPaneSettings();
- Debugging = new DebugPaneSettings();
+ Debugging.PropertyChanged += eventHandler;
}
}
-public class GR2PaneSettings : SettingsBase
+public class GR2PaneSettings : ReactiveObject
{
- private string inputPath = "";
-
- public string InputPath
- {
- get { return inputPath; }
- set { inputPath = value; OnPropertyChanged(); }
- }
-
- private string outputPath = "";
-
- public string OutputPath
- {
- get { return outputPath; }
- set { outputPath = value; OnPropertyChanged(); }
- }
-
- private string batchInputPath = "";
-
- public string BatchInputPath
- {
- get { return batchInputPath; }
- set { batchInputPath = value; OnPropertyChanged(); }
- }
-
- private string batchOutputPath = "";
-
- public string BatchOutputPath
- {
- get { return batchOutputPath; }
- set { batchOutputPath = value; OnPropertyChanged(); }
- }
+ public string InputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string OutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string BatchInputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string BatchOutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
- private ExportFormat batchInputFormat = ExportFormat.GR2;
-
- public int BatchInputFormat
- {
- get { return (int)batchInputFormat; }
- set { batchInputFormat = (ExportFormat)value; OnPropertyChanged(); }
- }
-
- private ExportFormat batchOutputFormat = ExportFormat.DAE;
-
- public int BatchOutputFormat
+ public LSLib.Granny.ExportFormat BatchInputFormat
{
- get { return (int)batchOutputFormat; }
- set { batchOutputFormat = (ExportFormat)value; OnPropertyChanged(); }
- }
+ get;
+ set => this.RaiseAndSetIfChanged(ref field, value);
+ } = LSLib.Granny.ExportFormat.GR2;
- private string conformPath;
-
- public string ConformPath
+ public LSLib.Granny.ExportFormat BatchOutputFormat
{
- get { return conformPath; }
- set { conformPath = value; OnPropertyChanged(); }
- }
+ get;
+ set => this.RaiseAndSetIfChanged(ref field, value);
+ } = LSLib.Granny.ExportFormat.DAE;
+ public string ConformPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
}
-public class PackagePaneSettings : SettingsBase
+public class PackagePaneSettings : ReactiveObject
{
- private string extractInputPath = "";
-
- public string ExtractInputPath
- {
- get { return extractInputPath; }
- set { extractInputPath = value; OnPropertyChanged(); }
- }
-
- private string extractOutputPath = "";
-
- public string ExtractOutputPath
- {
- get { return extractOutputPath; }
- set { extractOutputPath = value; OnPropertyChanged(); }
- }
-
- private string createInputPath = "";
+ public string ExtractInputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string ExtractOutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string CreateInputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string CreateOutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
- public string CreateInputPath
- {
- get { return createInputPath; }
- set { createInputPath = value; OnPropertyChanged(); }
- }
-
- private string createOutputPath = "";
-
- public string CreateOutputPath
- {
- get { return createOutputPath; }
- set { createOutputPath = value; OnPropertyChanged(); }
- }
-
- private int createPackageVersion = 0;
-
- public int CreatePackageVersion
- {
- get { return createPackageVersion; }
- set { createPackageVersion = value; OnPropertyChanged(); }
- }
-
- private int createPackageCompression = 3;
-
- public int CreatePackageCompression
- {
- get { return createPackageCompression; }
- set { createPackageCompression = value; OnPropertyChanged(); }
- }
+ [JsonConverter(typeof(PackageVersionJsonConverter))]
+ public LSLib.LS.PackageVersion CreatePackageVersion { get; set => this.RaiseAndSetIfChanged(ref field, value); }
- //public string BatchInputPath { get; set; } = "";
- //public string BatchOutputPath { get; set; } = "";
+ [JsonConverter(typeof(CompressionMethodJsonConverter))]
+ public CompressionMethod CreatePackageCompression { get; set => this.RaiseAndSetIfChanged(ref field, value); } = CompressionMethod.LZ4;
}
-public class ResourcePaneSettings : SettingsBase
+public class ResourcePaneSettings : ReactiveObject
{
- private string inputPath = "";
-
- public string InputPath
- {
- get { return inputPath; }
- set { inputPath = value; OnPropertyChanged(); }
- }
-
- private string outputPath = "";
-
- public string OutputPath
- {
- get { return outputPath; }
- set { outputPath = value; OnPropertyChanged(); }
- }
-
- private string batchInputPath = "";
-
- public string BatchInputPath
- {
- get { return batchInputPath; }
- set { batchInputPath = value; OnPropertyChanged(); }
- }
-
- private string batchOutputPath = "";
-
- public string BatchOutputPath
- {
- get { return batchOutputPath; }
- set { batchOutputPath = value; OnPropertyChanged(); }
- }
-
- private int batchInputFormat;
-
- public int BatchInputFormat
- {
- get { return batchInputFormat; }
- set { batchInputFormat = value; OnPropertyChanged(); }
- }
-
- private int batchOutputFormat;
-
- public int BatchOutputFormat
- {
- get { return batchOutputFormat; }
- set { batchOutputFormat = value; OnPropertyChanged(); }
- }
+ public string InputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string OutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string BatchInputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string BatchOutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public LSLib.Granny.ExportFormat BatchInputFormat { get; set => this.RaiseAndSetIfChanged(ref field, value); }
+ public LSLib.Granny.ExportFormat BatchOutputFormat { get; set => this.RaiseAndSetIfChanged(ref field, value); }
}
-public class VirtualTexturesPaneSettings : SettingsBase
+public class VirtualTexturesPaneSettings : ReactiveObject
{
- private string gtsPath = "";
-
- public string GTSPath
- {
- get { return gtsPath; }
- set { gtsPath = value; OnPropertyChanged(); }
- }
-
- private string destinationPath = "";
-
- public string DestinationPath
- {
- get { return destinationPath; }
- set { destinationPath = value; OnPropertyChanged(); }
- }
+ public string GTSPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string DestinationPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
}
-public class OsirisPaneSettings : SettingsBase
+public class OsirisPaneSettings : ReactiveObject
{
- private string inputPath = "";
-
- public string InputPath
- {
- get { return inputPath; }
- set { inputPath = value; OnPropertyChanged(); }
- }
-
- private string outputPath = "";
-
- public string OutputPath
- {
- get { return outputPath; }
- set { outputPath = value; OnPropertyChanged(); }
- }
-
- private string filterText = "";
-
- public string FilterText
- {
- get { return filterText; }
- set { filterText = value; OnPropertyChanged(); }
- }
-
- private bool filterMatchCase = false;
-
- public bool FilterMatchCase
- {
- get { return filterMatchCase; }
- set { filterMatchCase = value; OnPropertyChanged(); }
- }
+ public string InputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string OutputPath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public string FilterText { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
+ public bool FilterMatchCase { get; set => this.RaiseAndSetIfChanged(ref field, value); }
}
-public class DebugPaneSettings : SettingsBase
+public class DebugPaneSettings : ReactiveObject
{
- private string savePath = "";
-
- public string SavePath
- {
- get { return savePath; }
- set { savePath = value; OnPropertyChanged(); }
- }
+ public string SavePath { get; set => this.RaiseAndSetIfChanged(ref field, value); } = string.Empty;
}
-sealed class PackageVersionConverter : TypeConverter
+public sealed class PackageVersionJsonConverter : JsonConverter
{
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ public override LSLib.LS.PackageVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- return true;
+ int index = reader.GetInt32();
+ return index switch
+ {
+ 2 => LSLib.LS.PackageVersion.V10,
+ 3 => LSLib.LS.PackageVersion.V9,
+ 4 => LSLib.LS.PackageVersion.V7,
+ _ => LSLib.LS.PackageVersion.V18
+ };
}
- public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
+ public override void Write(Utf8JsonWriter writer, LSLib.LS.PackageVersion value, JsonSerializerOptions options)
{
- if(value is PackageVersion version)
+ int outIndex = value switch
{
- switch (version)
- {
- case PackageVersion.V10:
- {
- return 2;
- }
- case PackageVersion.V9:
- {
- return 3;
- }
- case PackageVersion.V7:
- {
- return 4;
- }
- case PackageVersion.V13:
- default:
- {
- return 0;
- }
- }
- }
- return 0;
+ LSLib.LS.PackageVersion.V10 => 2,
+ LSLib.LS.PackageVersion.V9 => 3,
+ LSLib.LS.PackageVersion.V7 => 4,
+ _ => 0
+ };
+ writer.WriteNumberValue(outIndex);
}
}
-sealed class CompressionConverter : TypeConverter
+public sealed class CompressionMethodJsonConverter : JsonConverter
{
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ public override CompressionMethod Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- return true;
+ int index = reader.GetInt32();
+ return index switch
+ {
+ 0 => CompressionMethod.None,
+ 1 => CompressionMethod.Zlib,
+ 3 => CompressionMethod.LZ4,
+ _ => CompressionMethod.LZ4
+ };
}
- public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
+ public override void Write(Utf8JsonWriter writer, CompressionMethod value, JsonSerializerOptions options)
{
- if (value is CompressionMethod compression)
+ int outIndex = value switch
{
- switch (compression)
- {
- case CompressionMethod.Zlib:
- {
- return 1;
- }
- case CompressionMethod.None:
- {
- return 0;
- }
- case CompressionMethod.LZ4:
- default:
- {
- return 3;
- }
- }
- }
- return 0;
+ CompressionMethod.None => 0,
+ CompressionMethod.Zlib => 1,
+ CompressionMethod.LZ4 => 3,
+ _ => 3
+ };
+ writer.WriteNumberValue(outIndex);
}
}
diff --git a/ConverterApp/DatabaseDumper.cs b/ConverterApp/DatabaseDumper.cs
index 096ca76e..bb893c14 100644
--- a/ConverterApp/DatabaseDumper.cs
+++ b/ConverterApp/DatabaseDumper.cs
@@ -1,80 +1,122 @@
-using LSLib.LS.Story;
-using System;
-using System.IO;
-using System.Linq;
-using System.Text;
+using System.Text;
+using LSLib.LS.Story;
-namespace ConverterApp;
+namespace LSTools.DivineGUI;
-class DatabaseDumper : IDisposable
+public sealed class DatabaseDumper : IDisposable
{
- private StreamWriter Writer;
+ public bool DumpUnnamedDbs { get; set; } = false;
- public bool DumpUnnamedDbs { get; set; }
+ private readonly StreamWriter _writer;
public DatabaseDumper(Stream outputStream)
{
- Writer = new StreamWriter(outputStream, Encoding.UTF8);
- DumpUnnamedDbs = false;
+ ArgumentNullException.ThrowIfNull(outputStream);
+ _writer = new StreamWriter(outputStream, Encoding.UTF8);
}
- public void Dispose()
- {
- Writer.Dispose();
- }
-
+ public void Dispose() => _writer.Dispose();
+
private void DumpFact(Story story, Fact fact)
{
- Writer.Write("(");
- for (var i = 0; i < fact.Columns.Count; i++)
+ _writer.Write("(");
+
+ int columnCount = fact.Columns.Count;
+ for (int i = 0; i < columnCount; i++)
{
- fact.Columns[i].DebugDump(Writer, story);
- if (i + 1 < fact.Columns.Count)
+ fact.Columns[i].DebugDump(_writer, story);
+ if (i + 1 < columnCount)
{
- Writer.Write(", ");
+ _writer.Write(", ");
}
}
- Writer.WriteLine(")");
+
+ _writer.Write(")\n");
}
public void DumpDatabase(Story story, Database database)
{
- if (database.OwnerNode != null)
+ if (database.OwnerNode is { } node)
{
- if (database.OwnerNode.Name.Length > 0)
+ if (!string.IsNullOrEmpty(node.Name))
{
- Writer.Write($"Database '{database.OwnerNode.Name}'");
+ _writer.Write($"Database '{node.Name}'");
}
else
{
- Writer.Write($"Database #{database.Index} <{database.OwnerNode.TypeName()}>");
+ _writer.Write($"Database #{database.Index} <{node.TypeName()}>");
}
}
else
{
- Writer.Write($"Database #{database.Index}");
+ _writer.Write($"Database #{database.Index}");
}
- var types = String.Join(", ", database.Parameters.Types.Select(ty => story.Types[ty].Name));
- Writer.WriteLine($" ({types}):");
+ var typeNamesList = new List(database.Parameters.Types.Count);
+ foreach (int typeId in database.Parameters.Types.Select(v => (int)v))
+ {
+ if (story.Types.TryGetValue((uint)typeId, out var storyType))
+ {
+ typeNamesList.Add(storyType.Name);
+ }
+ }
+ string typesString = string.Join(", ", typeNamesList);
+ _writer.Write($" ({typesString}):\n");
- foreach (var fact in database.Facts)
+ if (database.Facts is IEnumerable stronglyTypedFacts)
{
- Writer.Write("\t");
- DumpFact(story, fact);
+ foreach (Fact fact in stronglyTypedFacts)
+ {
+ _writer.Write("\t");
+ DumpFact(story, fact);
+ }
}
}
public void DumpAll(Story story)
{
- Writer.WriteLine(" === DUMP OF DATABASES === ");
- foreach (var db in story.Databases)
+ ArgumentNullException.ThrowIfNull(story);
+ _writer.Write(" === DUMP OF DATABASES === \n");
+
+ foreach (KeyValuePair entry in story.Databases)
{
- if (DumpUnnamedDbs || (db.Value.OwnerNode != null && db.Value.OwnerNode.Name.Length > 0))
+ Database db = entry.Value;
+ if (DumpUnnamedDbs || (db.OwnerNode is { } node && !string.IsNullOrEmpty(node.Name)))
{
- DumpDatabase(story, db.Value);
- Writer.WriteLine("");
+ DumpDatabase(story, db);
+ _writer.Write("\n");
}
}
}
-}
+
+ public List GenerateGridRows(Story story)
+ {
+ ArgumentNullException.ThrowIfNull(story);
+ var rowsCollection = new List();
+
+ foreach (KeyValuePair entry in story.Databases)
+ {
+ Database db = entry.Value;
+
+ if (DumpUnnamedDbs || (db.OwnerNode is { } node && !string.IsNullOrEmpty(node.Name)))
+ {
+ if (db.Facts is IEnumerable stronglyTypedFacts)
+ {
+ foreach (Fact fact in stronglyTypedFacts)
+ {
+ var evaluatedDisplayValues = new string[fact.Columns.Count];
+ for (int i = 0; i < fact.Columns.Count; i++)
+ {
+ using var stringWriter = new StringWriter();
+ fact.Columns[i].DebugDump(stringWriter, story);
+ evaluatedDisplayValues[i] = stringWriter.ToString();
+ }
+ rowsCollection.Add(new FactRowModel(fact, evaluatedDisplayValues));
+ }
+ }
+ }
+ }
+
+ return rowsCollection;
+ }
+}
\ No newline at end of file
diff --git a/ConverterApp/DebugDumper.cs b/ConverterApp/DebugDumper.cs
index df1d2ad8..9ce78505 100644
--- a/ConverterApp/DebugDumper.cs
+++ b/ConverterApp/DebugDumper.cs
@@ -1,174 +1,167 @@
-using LSLib.LS;
-using LSLib.LS.Enums;
-using LSLib.LS.Story;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Windows.Forms;
+using System.Threading.Tasks;
+using LSLib.LS;
+using LSLib.LS.Enums;
+using LSLib.LS.Resources.LSF;
+using LSLib.LS.Story;
+using LSLib.LS.Story.Compiler; // For handling the Diagnostic collection context
-namespace ConverterApp;
+namespace LSTools.DivineGUI;
-public delegate void DumugDumperReportProgress(int percentage, string statusText);
+public record DumperProgressEventArgs(int Percentage, string StatusText);
public class DebugDumperTask
{
- private Package SavePackage;
- private Resource SaveMeta;
- private Resource SaveGlobals;
- private Story SaveStory;
+ private Package? _savePackage;
+ private Resource? _saveMeta;
+ private Resource? _saveGlobals;
+ private Story? _saveStory;
public Game GameVersion { get; set; }
- public string SaveFilePath { get; set; }
- public string ExtractionPath { get; set; }
- public string DataDumpPath { get; set; }
-
- // General savegame dumping settings
- public bool ExtractAll { get; set; }
- public bool ConvertToLsx { get; set; }
- public bool DumpModList { get; set; }
-
- // Behavior variable dumping settings
- public bool DumpGlobalVars { get; set; }
- public bool DumpCharacterVars { get; set; }
- public bool DumpItemVars { get; set; }
- public bool IncludeDeletedVars { get; set; }
- public bool IncludeLocalScopes { get; set; }
-
- // Story dump settings
- public bool DumpStoryDatabases { get; set; }
- public bool DumpStoryGoals { get; set; }
- public bool IncludeUnnamedDatabases { get; set; }
-
- public event DumugDumperReportProgress ReportProgress;
-
- public DebugDumperTask()
- {
- ExtractAll = true;
- ConvertToLsx = true;
- DumpModList = true;
-
- DumpGlobalVars = true;
- DumpCharacterVars = true;
- DumpItemVars = true;
- IncludeDeletedVars = false;
- IncludeLocalScopes = false;
- // TODO ------------------ RE-IMPORTABLE VARS/DBS FORMAT ----------------------
-
- DumpStoryDatabases = true;
- DumpStoryGoals = true;
- IncludeUnnamedDatabases = false;
- }
+ public string SaveFilePath { get; set; } = string.Empty;
+ public string ExtractionPath { get; set; } = string.Empty;
+ public string DataDumpPath { get; set; } = string.Empty;
+ public bool ExtractAll { get; set; } = true;
+ public bool ConvertToLsx { get; set; } = true;
+ public bool DumpModList { get; set; } = true;
+ public bool DumpGlobalVars { get; set; } = true;
+ public bool DumpCharacterVars { get; set; } = true;
+ public bool DumpItemVars { get; set; } = true;
+ public bool IncludeDeletedVars { get; set; } = false;
+ public bool IncludeLocalScopes { get; set; } = false;
+ public bool DumpStoryDatabases { get; set; } = true;
+ public bool DumpStoryGoals { get; set; } = true;
+ public bool IncludeUnnamedDatabases { get; set; } = false;
+
+ // TARGET EXTRACTION HOOK: Passes collected file logs, data validations, or errors back to the DataGrid collection
+ public List TaskDiagnostics { get; } = [];
+
+ public event Action? ProgressUpdated;
+
+ private void ReportProgress(int percentage, string statusText) =>
+ ProgressUpdated?.Invoke(new DumperProgressEventArgs(percentage, statusText));
private void DoExtractPackage()
{
- var packager = new Packager();
- packager.ProgressUpdate = (file, numerator, denominator) => {
- ReportProgress(5 + (int)(numerator * 15 / denominator), "Extracting: " + file);
+ ArgumentNullException.ThrowIfNull(_savePackage);
+
+ var packager = new Packager
+ {
+ ProgressUpdate = (file, numerator, denominator) =>
+ ReportProgress(5 + (int)(denominator == 0 ? 0 : numerator * 15 / denominator), $"Extracting: {file}")
};
- packager.UncompressPackage(SavePackage, ExtractionPath);
+
+ packager.UncompressPackage(_savePackage, ExtractionPath);
+ TaskDiagnostics.Add(new Diagnostic(null, MessageLevel.Info, "PAK01", $"Extracted complete payload successfully to {ExtractionPath}"));
}
private void DoLsxConversion()
{
+ ArgumentNullException.ThrowIfNull(_savePackage);
+
var conversionParams = ResourceConversionParameters.FromGameVersion(GameVersion);
var loadParams = ResourceLoadParameters.FromGameVersion(GameVersion);
- var lsfList = SavePackage.Files.Where(p => p.Name.EndsWith(".lsf"));
+ var lsfList = _savePackage.Files.Where(p => p.Name.EndsWith(".lsf", StringComparison.OrdinalIgnoreCase)).ToList();
var numProcessed = 0;
+
foreach (var lsf in lsfList)
{
- var lsfPath = Path.Combine(ExtractionPath, lsf.Name);
- var lsxPath = Path.Combine(ExtractionPath, lsf.Name.Substring(0, lsf.Name.Length - 4) + ".lsx");
+ try
+ {
+ var lsfPath = Path.Combine(ExtractionPath, lsf.Name);
+ string baseName = lsf.Name.EndsWith(".lsf", StringComparison.OrdinalIgnoreCase) ? lsf.Name[..^4] : lsf.Name;
+ var lsxPath = Path.Combine(ExtractionPath, $"{baseName}.lsx");
- ReportProgress(20 + (numProcessed * 30 / lsfList.Count()), "Converting to LSX: " + lsf.Name);
- var resource = ResourceUtils.LoadResource(lsfPath, ResourceFormat.LSF, loadParams);
- ResourceUtils.SaveResource(resource, lsxPath, ResourceFormat.LSX, conversionParams);
- numProcessed++;
+ int calculatedProgress = lsfList.Count == 0 ? 0 : numProcessed * 30 / lsfList.Count;
+ ReportProgress(20 + calculatedProgress, $"Converting to LSX: {lsf.Name}");
+
+ var resource = ResourceUtils.LoadResource(lsfPath, ResourceFormat.LSF, loadParams);
+ ResourceUtils.SaveResource(resource, lsxPath, ResourceFormat.LSX, conversionParams);
+ numProcessed++;
+ }
+ catch (Exception ex)
+ {
+ // Logs any localized serialization error cleanly into the grid display
+ TaskDiagnostics.Add(new Diagnostic(null, MessageLevel.Warning, "CONV02", $"Failed transforming '{lsf.Name}': {ex.Message}"));
+ }
}
}
private Resource LoadPackagedResource(string path)
{
- var fileInfo = SavePackage.Files.FirstOrDefault(p => p.Name.ToLowerInvariant() == path);
- if (fileInfo == null)
- {
- throw new ArgumentException($"Could not locate file in package: '{path}");
- }
+ ArgumentNullException.ThrowIfNull(_savePackage);
- Resource resource;
- using var rsrcStream = fileInfo.CreateContentReader();
- using (var rsrcReader = new LSFReader(rsrcStream))
- {
- resource = rsrcReader.Read();
- }
+ var fileInfo = _savePackage.Files.FirstOrDefault(p => p.Name.Equals(path, StringComparison.OrdinalIgnoreCase))
+ ?? throw new ArgumentException($"Could not locate file in package: '{path}'");
- return resource;
+ using var rsrcStream = fileInfo.CreateContentReader();
+ using var rsrcReader = new LSFReader(rsrcStream);
+ return rsrcReader.Read();
}
private void DumpMods(string outputPath)
{
- using (var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read))
- using (var writer = new StreamWriter(outputStream))
+ ArgumentNullException.ThrowIfNull(_saveMeta);
+
+ using var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ using var writer = new StreamWriter(outputStream);
+
+ if (!_saveMeta.Regions.TryGetValue("MetaData", out var metaRegion)) return;
+ if (!metaRegion.Children.TryGetValue("MetaData", out var metaChildrenList) || metaChildrenList.Count == 0) return;
+
+ var meta = metaChildrenList[0];
+ if (!meta.Children.TryGetValue("ModuleSettings", out var settingsList) || settingsList.Count == 0) return;
+ if (!settingsList[0].Children.TryGetValue("Mods", out var modsList) || modsList.Count == 0) return;
+ if (!modsList[0].Children.TryGetValue("ModuleShortDesc", out var moduleDescs)) return;
+
+ foreach (var modDesc in moduleDescs)
{
- var meta = SaveMeta.Regions["MetaData"].Children["MetaData"][0];
- var moduleDescs = meta.Children["ModuleSettings"][0].Children["Mods"][0].Children["ModuleShortDesc"];
- foreach (var modDesc in moduleDescs)
- {
- var folder = (string)modDesc.Attributes["Folder"].Value;
- var name = (string)modDesc.Attributes["Name"].Value;
- PackedVersion version;
- if (modDesc.Attributes.ContainsKey("Version64"))
- {
- var versionNum = (Int64)modDesc.Attributes["Version64"].Value;
- version = PackedVersion.FromInt64(versionNum);
- }
- else
- {
- var versionNum = (Int32)modDesc.Attributes["Version"].Value;
- version = PackedVersion.FromInt32(versionNum);
- }
+ string folder = modDesc.Attributes.TryGetValue("Folder", out var fAttr) && fAttr.Value is string folderName ? folderName : string.Empty;
+ string name = modDesc.Attributes.TryGetValue("Name", out var nAttr) && nAttr.Value is string modName ? modName : string.Empty;
- writer.WriteLine($"{name} (v{version.Major}.{version.Minor}.{version.Revision}.{version.Build}) @ {folder}");
- }
+ var version = modDesc.Attributes.TryGetValue("Version64", out var v64) && v64.Value is long packed64
+ ? PackedVersion.FromInt64(packed64)
+ : (modDesc.Attributes.TryGetValue("Version", out var v32) && v32.Value is int packed32
+ ? PackedVersion.FromInt32(packed32)
+ : PackedVersion.FromInt32(0));
+
+ writer.WriteLine($"{name} (v{version.Major}.{version.Minor}.{version.Revision}.{version.Build}) @ {folder}");
}
}
private void DumpVariables(string outputPath, bool globals, bool characters, bool items)
{
- using (var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- var varDumper = new VariableDumper(outputStream);
- varDumper.IncludeDeletedVars = IncludeDeletedVars;
- varDumper.IncludeLocalScopes = IncludeLocalScopes;
- if (varDumper.Load(SaveGlobals))
- {
- if (globals)
- {
- varDumper.DumpGlobals();
- }
+ ArgumentNullException.ThrowIfNull(_saveGlobals);
- if (characters)
- {
- varDumper.DumpCharacters();
- }
+ using var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ var varDumper = new VariableDumper(outputStream)
+ {
+ IncludeDeletedVars = IncludeDeletedVars,
+ IncludeLocalScopes = IncludeLocalScopes
+ };
- if (items)
- {
- varDumper.DumpItems();
- }
- }
+ if (varDumper.Load(_saveGlobals))
+ {
+ if (globals) varDumper.DumpGlobals();
+ if (characters) varDumper.DumpCharacters();
+ if (items) varDumper.DumpItems();
}
}
private void DumpGoals()
{
+ ArgumentNullException.ThrowIfNull(_saveStory);
+
ReportProgress(80, "Dumping story ...");
string debugPath = Path.Combine(DataDumpPath, "GoalsDebug.log");
using (var debugFile = new FileStream(debugPath, FileMode.Create, FileAccess.Write))
using (var writer = new StreamWriter(debugFile))
{
- SaveStory.DebugDump(writer);
+ _saveStory.DebugDump(writer);
}
ReportProgress(85, "Dumping story goals ...");
@@ -179,132 +172,139 @@ private void DumpGoals()
using (var goalFile = new FileStream(unassignedPath, FileMode.Create, FileAccess.Write))
using (var writer = new StreamWriter(goalFile))
{
- var dummyGoal = new Goal(SaveStory)
+ var dummyGoal = new Goal(_saveStory)
{
- ExitCalls = new List(),
- InitCalls = new List(),
- ParentGoals = new List(),
- SubGoals = new List(),
+ ExitCalls = [],
+ InitCalls = [],
+ ParentGoals = [],
+ SubGoals = [],
Name = "UNASSIGNED_RULES",
Index = 0
};
- dummyGoal.MakeScript(writer, SaveStory);
+ dummyGoal.MakeScript(writer, _saveStory);
}
- foreach (KeyValuePair goal in SaveStory.Goals)
+ foreach (var goalEntry in _saveStory.Goals)
{
- string filePath = Path.Combine(goalsPath, $"{goal.Value.Name}.txt");
- using (var goalFile = new FileStream(filePath, FileMode.Create, FileAccess.Write))
- using (var writer = new StreamWriter(goalFile))
- {
- goal.Value.MakeScript(writer, SaveStory);
- }
+ var goal = goalEntry.Value;
+ string filePath = Path.Combine(goalsPath, $"{goal.Name}.txt");
+ using var goalFile = new FileStream(filePath, FileMode.Create, FileAccess.Write);
+ using var writer = new StreamWriter(goalFile);
+ goal.MakeScript(writer, _saveStory);
}
}
private void RunTasks()
{
- if (ExtractAll)
- {
- DoExtractPackage();
- }
+ ArgumentNullException.ThrowIfNull(_savePackage);
- if (ConvertToLsx)
- {
- DoLsxConversion();
- }
+ if (ExtractAll) DoExtractPackage();
+ if (ConvertToLsx) DoLsxConversion();
FileManager.TryToCreateDirectory(Path.Combine(DataDumpPath, "Dummy"));
ReportProgress(50, "Loading meta.lsf ...");
- SaveMeta = LoadPackagedResource("meta.lsf");
+ _saveMeta = LoadPackagedResource("meta.lsf");
ReportProgress(52, "Loading globals.lsf ...");
- SaveGlobals = LoadPackagedResource("globals.lsf");
+ _saveGlobals = LoadPackagedResource("globals.lsf");
- ReportProgress(60, "Dumping mod list ...");
if (DumpModList)
{
- var modListPath = Path.Combine(DataDumpPath, "ModList.txt");
- DumpMods(modListPath);
+ ReportProgress(60, "Dumping mod list ...");
+ DumpMods(Path.Combine(DataDumpPath, "ModList.txt"));
}
ReportProgress(62, "Dumping variables ...");
- if (DumpGlobalVars)
- {
- var varsPath = Path.Combine(DataDumpPath, "GlobalVars.txt");
- DumpVariables(varsPath, true, false, false);
- }
-
- if (DumpCharacterVars)
- {
- var varsPath = Path.Combine(DataDumpPath, "CharacterVars.txt");
- DumpVariables(varsPath, false, true, false);
- }
-
- if (DumpItemVars)
- {
- var varsPath = Path.Combine(DataDumpPath, "ItemVars.txt");
- DumpVariables(varsPath, false, false, true);
- }
+ if (DumpGlobalVars) DumpVariables(Path.Combine(DataDumpPath, "GlobalVars.txt"), true, false, false);
+ if (DumpCharacterVars) DumpVariables(Path.Combine(DataDumpPath, "CharacterVars.txt"), false, true, false);
+ if (DumpItemVars) DumpVariables(Path.Combine(DataDumpPath, "ItemVars.txt"), false, false, true);
ReportProgress(70, "Loading story ...");
- var storySave = SavePackage.Files.FirstOrDefault(p => p.Name == "StorySave.bin");
- Stream storyStream;
+ var storySave = _savePackage.Files.FirstOrDefault(p => p.Name.Equals("StorySave.bin", StringComparison.OrdinalIgnoreCase));
+
+ using var storyStream = new MemoryStream();
if (storySave != null)
{
- var bin = storySave.CreateContentReader();
- storyStream = new MemoryStream();
+ using var bin = storySave.CreateContentReader();
bin.CopyTo(storyStream);
- storyStream.Position = 0;
}
else
{
- LSLib.LS.Node storyNode = SaveGlobals.Regions["Story"].Children["Story"][0];
- storyStream = new MemoryStream(storyNode.Attributes["Story"].Value as byte[]);
- }
-
- var reader = new StoryReader();
- SaveStory = reader.Read(storyStream);
-
- if (DumpStoryGoals)
- {
- DumpGoals();
- }
-
- if (DumpStoryDatabases)
- {
- ReportProgress(90, "Dumping databases ...");
- var dbDumpPath = Path.Combine(DataDumpPath, "Databases.txt");
- using (var dbDumpStream = new FileStream(dbDumpPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ if (_saveGlobals.Regions.TryGetValue("Story", out var storyRegion) &&
+ storyRegion.Children.TryGetValue("Story", out var storyChildrenList) &&
+storyChildrenList.Count > 0)
{
- var dbDumper = new DatabaseDumper(dbDumpStream);
- dbDumper.DumpUnnamedDbs = IncludeUnnamedDatabases;
- dbDumper.DumpAll(SaveStory);
+ var storyNode = storyChildrenList[0];
+ if (storyNode.Attributes.TryGetValue("Story", out var storyAttr) && storyAttr.Value is byte[] byteData)
+ {
+ storyStream.Write(byteData, 0, byteData.Length);
+ }
+ else
+ {
+ throw new InvalidDataException("Story element stream attribute could not be extracted or was not a valid byte array.");
+ }
+ }
+ else
+ {
+ throw new InvalidDataException("The specified globals resource does not contain an active Story save node tree.");
}
}
-
- ReportProgress(100, "");
+ storyStream.Position = 0;
+ _saveStory = StoryReader.Read(storyStream);
+ if (DumpStoryGoals) DumpGoals();
+ if (DumpStoryDatabases) DumpStoryDatabasesTask();
+ ReportProgress(100, "Done");
+ TaskDiagnostics.Add(new Diagnostic(null, MessageLevel.Info, "SUCCESS", "All requested data blocks dumped completely."));
}
-
- public void Run()
+ private void DumpStoryDatabasesTask()
{
- ReportProgress(0, "Reading package ...");
-
- var packageReader = new PackageReader();
- using var savePackage = packageReader.Read(SaveFilePath);
-
- SavePackage = savePackage;
- var abstractFileInfo = SavePackage.Files.FirstOrDefault(p => p.Name.ToLowerInvariant() == "globals.lsf");
- if (abstractFileInfo == null)
+ ArgumentNullException.ThrowIfNull(_saveStory);
+ ReportProgress(90, "Dumping databases ...");
+ var dbDumpPath = Path.Combine(DataDumpPath, "Databases.txt");
+ using var dbDumpStream = new FileStream(dbDumpPath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ var dbDumper = new DatabaseDumper(dbDumpStream)
+ {
+ DumpUnnamedDbs = IncludeUnnamedDatabases
+ };
+ dbDumper.DumpAll(_saveStory);
+ }
+ public async Task RunAsync()
+ {
+ TaskDiagnostics.Clear();
+ if (string.IsNullOrWhiteSpace(SaveFilePath) || !File.Exists(SaveFilePath))
{
- MessageBox.Show("The specified package is not a valid savegame (globals.lsf not found)", "Load Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ ReportProgress(0, "Execution Aborted: Source package target is blank or inaccessible.");
+ TaskDiagnostics.Add(new Diagnostic(null, MessageLevel.Error, "FILE01", "The specified archive path is null or target file does not exist."));
return;
}
-
- RunTasks();
- SavePackage = null;
-
- MessageBox.Show($"Savegame dumped to {DataDumpPath}.");
+ ReportProgress(0, "Reading package metadata structure components...");
+ await Task.Run(() =>
+ {
+ var packageReader = new PackageReader();
+ try
+ {
+ _savePackage = packageReader.Read(SaveFilePath);
+ bool hasGlobals = _savePackage.Files.Any(p => p.Name.Equals("globals.lsf", StringComparison.OrdinalIgnoreCase));
+ if (!hasGlobals)
+ {
+ throw new InvalidDataException("The specified package is not a valid savegame (globals.lsf not found).");
+ }
+ RunTasks();
+ }
+ catch (Exception ex)
+ {
+ ReportProgress(0, $"Processing Interrupted: {ex.Message}");
+ TaskDiagnostics.Add(new Diagnostic(null, MessageLevel.Error, "CRIT01", ex.Message));
+ }
+ finally
+ {
+ _savePackage?.Dispose();
+ _savePackage = null;
+ _saveMeta = null;
+ _saveGlobals = null;
+ _saveStory = null;
+ }
+ });
}
-}
+}
\ No newline at end of file
diff --git a/ConverterApp/DebugPane.Designer.cs b/ConverterApp/DebugPane.Designer.cs
deleted file mode 100644
index 05283a2d..00000000
--- a/ConverterApp/DebugPane.Designer.cs
+++ /dev/null
@@ -1,315 +0,0 @@
-namespace ConverterApp
-{
- partial class DebugPane
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Component Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.dumpVariablesBtn = new System.Windows.Forms.Button();
- this.saveFilePath = new System.Windows.Forms.TextBox();
- this.savePathLbl = new System.Windows.Forms.Label();
- this.saveFileBrowseBtn = new System.Windows.Forms.Button();
- this.savePathDlg = new System.Windows.Forms.OpenFileDialog();
- this.dumpGlobalVars = new System.Windows.Forms.CheckBox();
- this.variableDumperBox = new System.Windows.Forms.GroupBox();
- this.includeLocalScopes = new System.Windows.Forms.CheckBox();
- this.includeUnnamedDbs = new System.Windows.Forms.CheckBox();
- this.dumpDatabases = new System.Windows.Forms.CheckBox();
- this.includeDeleted = new System.Windows.Forms.CheckBox();
- this.dumpItemVars = new System.Windows.Forms.CheckBox();
- this.dumpCharacterVars = new System.Windows.Forms.CheckBox();
- this.lblProgressStatus = new System.Windows.Forms.Label();
- this.dumpProgressBar = new System.Windows.Forms.ProgressBar();
- this.lblProgressText = new System.Windows.Forms.Label();
- this.dumpGoals = new System.Windows.Forms.CheckBox();
- this.extractPackage = new System.Windows.Forms.CheckBox();
- this.exportModList = new System.Windows.Forms.CheckBox();
- this.convertLsf = new System.Windows.Forms.CheckBox();
- this.variableDumperBox.SuspendLayout();
- this.SuspendLayout();
- //
- // dumpVariablesBtn
- //
- this.dumpVariablesBtn.Location = new System.Drawing.Point(234, 111);
- this.dumpVariablesBtn.Name = "dumpVariablesBtn";
- this.dumpVariablesBtn.Size = new System.Drawing.Size(174, 23);
- this.dumpVariablesBtn.TabIndex = 72;
- this.dumpVariablesBtn.Text = "Dump savegame";
- this.dumpVariablesBtn.UseVisualStyleBackColor = true;
- this.dumpVariablesBtn.Click += new System.EventHandler(this.dumpVariablesBtn_Click);
- //
- // saveFilePath
- //
- this.saveFilePath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
- | System.Windows.Forms.AnchorStyles.Right)));
- this.saveFilePath.Location = new System.Drawing.Point(13, 29);
- this.saveFilePath.Name = "saveFilePath";
- this.saveFilePath.Size = new System.Drawing.Size(777, 20);
- this.saveFilePath.TabIndex = 74;
- //
- // savePathLbl
- //
- this.savePathLbl.AutoSize = true;
- this.savePathLbl.Location = new System.Drawing.Point(10, 13);
- this.savePathLbl.Name = "savePathLbl";
- this.savePathLbl.Size = new System.Drawing.Size(101, 13);
- this.savePathLbl.TabIndex = 73;
- this.savePathLbl.Text = "Savegame file path:";
- //
- // saveFileBrowseBtn
- //
- this.saveFileBrowseBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.saveFileBrowseBtn.Location = new System.Drawing.Point(796, 28);
- this.saveFileBrowseBtn.Name = "saveFileBrowseBtn";
- this.saveFileBrowseBtn.Size = new System.Drawing.Size(41, 22);
- this.saveFileBrowseBtn.TabIndex = 75;
- this.saveFileBrowseBtn.Text = "...";
- this.saveFileBrowseBtn.UseVisualStyleBackColor = true;
- this.saveFileBrowseBtn.Click += new System.EventHandler(this.saveFileBrowseBtn_Click);
- //
- // savePathDlg
- //
- this.savePathDlg.Filter = "LS savegame files|*.lsv";
- //
- // dumpGlobalVars
- //
- this.dumpGlobalVars.AutoSize = true;
- this.dumpGlobalVars.Checked = true;
- this.dumpGlobalVars.CheckState = System.Windows.Forms.CheckState.Checked;
- this.dumpGlobalVars.Location = new System.Drawing.Point(16, 25);
- this.dumpGlobalVars.Name = "dumpGlobalVars";
- this.dumpGlobalVars.Size = new System.Drawing.Size(133, 17);
- this.dumpGlobalVars.TabIndex = 77;
- this.dumpGlobalVars.Text = "Dump global variables ";
- this.dumpGlobalVars.UseVisualStyleBackColor = true;
- //
- // variableDumperBox
- //
- this.variableDumperBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
- | System.Windows.Forms.AnchorStyles.Right)));
- this.variableDumperBox.Controls.Add(this.extractPackage);
- this.variableDumperBox.Controls.Add(this.exportModList);
- this.variableDumperBox.Controls.Add(this.convertLsf);
- this.variableDumperBox.Controls.Add(this.dumpGoals);
- this.variableDumperBox.Controls.Add(this.includeLocalScopes);
- this.variableDumperBox.Controls.Add(this.includeUnnamedDbs);
- this.variableDumperBox.Controls.Add(this.dumpDatabases);
- this.variableDumperBox.Controls.Add(this.includeDeleted);
- this.variableDumperBox.Controls.Add(this.dumpItemVars);
- this.variableDumperBox.Controls.Add(this.dumpCharacterVars);
- this.variableDumperBox.Controls.Add(this.dumpVariablesBtn);
- this.variableDumperBox.Controls.Add(this.dumpGlobalVars);
- this.variableDumperBox.Location = new System.Drawing.Point(13, 68);
- this.variableDumperBox.Name = "variableDumperBox";
- this.variableDumperBox.Size = new System.Drawing.Size(824, 149);
- this.variableDumperBox.TabIndex = 78;
- this.variableDumperBox.TabStop = false;
- this.variableDumperBox.Text = "Dump Settings";
- //
- // includeLocalScopes
- //
- this.includeLocalScopes.AutoSize = true;
- this.includeLocalScopes.Location = new System.Drawing.Point(16, 117);
- this.includeLocalScopes.Name = "includeLocalScopes";
- this.includeLocalScopes.Size = new System.Drawing.Size(123, 17);
- this.includeLocalScopes.TabIndex = 83;
- this.includeLocalScopes.Text = "Include local scopes";
- this.includeLocalScopes.UseVisualStyleBackColor = true;
- //
- // includeUnnamedDbs
- //
- this.includeUnnamedDbs.AutoSize = true;
- this.includeUnnamedDbs.Location = new System.Drawing.Point(235, 71);
- this.includeUnnamedDbs.Name = "includeUnnamedDbs";
- this.includeUnnamedDbs.Size = new System.Drawing.Size(173, 17);
- this.includeUnnamedDbs.TabIndex = 82;
- this.includeUnnamedDbs.Text = "Include intermediate databases";
- this.includeUnnamedDbs.UseVisualStyleBackColor = true;
- //
- // dumpDatabases
- //
- this.dumpDatabases.AutoSize = true;
- this.dumpDatabases.Checked = true;
- this.dumpDatabases.CheckState = System.Windows.Forms.CheckState.Checked;
- this.dumpDatabases.Location = new System.Drawing.Point(235, 48);
- this.dumpDatabases.Name = "dumpDatabases";
- this.dumpDatabases.Size = new System.Drawing.Size(106, 17);
- this.dumpDatabases.TabIndex = 81;
- this.dumpDatabases.Text = "Dump databases";
- this.dumpDatabases.UseVisualStyleBackColor = true;
- //
- // includeDeleted
- //
- this.includeDeleted.AutoSize = true;
- this.includeDeleted.Location = new System.Drawing.Point(16, 94);
- this.includeDeleted.Name = "includeDeleted";
- this.includeDeleted.Size = new System.Drawing.Size(133, 17);
- this.includeDeleted.TabIndex = 80;
- this.includeDeleted.Text = "Include deleted entries";
- this.includeDeleted.UseVisualStyleBackColor = true;
- //
- // dumpItemVars
- //
- this.dumpItemVars.AutoSize = true;
- this.dumpItemVars.Checked = true;
- this.dumpItemVars.CheckState = System.Windows.Forms.CheckState.Checked;
- this.dumpItemVars.Location = new System.Drawing.Point(16, 71);
- this.dumpItemVars.Name = "dumpItemVars";
- this.dumpItemVars.Size = new System.Drawing.Size(124, 17);
- this.dumpItemVars.TabIndex = 79;
- this.dumpItemVars.Text = "Dump item variables ";
- this.dumpItemVars.UseVisualStyleBackColor = true;
- //
- // dumpCharacterVars
- //
- this.dumpCharacterVars.AutoSize = true;
- this.dumpCharacterVars.Checked = true;
- this.dumpCharacterVars.CheckState = System.Windows.Forms.CheckState.Checked;
- this.dumpCharacterVars.Location = new System.Drawing.Point(16, 48);
- this.dumpCharacterVars.Name = "dumpCharacterVars";
- this.dumpCharacterVars.Size = new System.Drawing.Size(150, 17);
- this.dumpCharacterVars.TabIndex = 78;
- this.dumpCharacterVars.Text = "Dump character variables ";
- this.dumpCharacterVars.UseVisualStyleBackColor = true;
- //
- // lblProgressStatus
- //
- this.lblProgressStatus.AutoSize = true;
- this.lblProgressStatus.Location = new System.Drawing.Point(77, 231);
- this.lblProgressStatus.Name = "lblProgressStatus";
- this.lblProgressStatus.Size = new System.Drawing.Size(0, 13);
- this.lblProgressStatus.TabIndex = 81;
- //
- // dumpProgressBar
- //
- this.dumpProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
- | System.Windows.Forms.AnchorStyles.Right)));
- this.dumpProgressBar.Location = new System.Drawing.Point(13, 247);
- this.dumpProgressBar.Name = "dumpProgressBar";
- this.dumpProgressBar.Size = new System.Drawing.Size(824, 23);
- this.dumpProgressBar.TabIndex = 79;
- //
- // lblProgressText
- //
- this.lblProgressText.AutoSize = true;
- this.lblProgressText.Location = new System.Drawing.Point(10, 231);
- this.lblProgressText.Name = "lblProgressText";
- this.lblProgressText.Size = new System.Drawing.Size(51, 13);
- this.lblProgressText.TabIndex = 80;
- this.lblProgressText.Text = "Progress:";
- //
- // dumpGoals
- //
- this.dumpGoals.AutoSize = true;
- this.dumpGoals.Checked = true;
- this.dumpGoals.CheckState = System.Windows.Forms.CheckState.Checked;
- this.dumpGoals.Location = new System.Drawing.Point(235, 25);
- this.dumpGoals.Name = "dumpGoals";
- this.dumpGoals.Size = new System.Drawing.Size(82, 17);
- this.dumpGoals.TabIndex = 84;
- this.dumpGoals.Text = "Dump goals";
- this.dumpGoals.UseVisualStyleBackColor = true;
- //
- // extractPackage
- //
- this.extractPackage.AutoSize = true;
- this.extractPackage.Checked = true;
- this.extractPackage.CheckState = System.Windows.Forms.CheckState.Checked;
- this.extractPackage.Location = new System.Drawing.Point(443, 25);
- this.extractPackage.Name = "extractPackage";
- this.extractPackage.Size = new System.Drawing.Size(111, 17);
- this.extractPackage.TabIndex = 87;
- this.extractPackage.Text = "Extract savegame";
- this.extractPackage.UseVisualStyleBackColor = true;
- //
- // exportModList
- //
- this.exportModList.AutoSize = true;
- this.exportModList.Checked = true;
- this.exportModList.CheckState = System.Windows.Forms.CheckState.Checked;
- this.exportModList.Location = new System.Drawing.Point(443, 71);
- this.exportModList.Name = "exportModList";
- this.exportModList.Size = new System.Drawing.Size(94, 17);
- this.exportModList.TabIndex = 86;
- this.exportModList.Text = "Export mod list";
- this.exportModList.UseVisualStyleBackColor = true;
- //
- // convertLsf
- //
- this.convertLsf.AutoSize = true;
- this.convertLsf.Checked = true;
- this.convertLsf.CheckState = System.Windows.Forms.CheckState.Checked;
- this.convertLsf.Location = new System.Drawing.Point(443, 48);
- this.convertLsf.Name = "convertLsf";
- this.convertLsf.Size = new System.Drawing.Size(125, 17);
- this.convertLsf.TabIndex = 85;
- this.convertLsf.Text = "Convert LSFs to LSX";
- this.convertLsf.UseVisualStyleBackColor = true;
- //
- // DebugPane
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.Controls.Add(this.lblProgressStatus);
- this.Controls.Add(this.dumpProgressBar);
- this.Controls.Add(this.lblProgressText);
- this.Controls.Add(this.variableDumperBox);
- this.Controls.Add(this.saveFilePath);
- this.Controls.Add(this.savePathLbl);
- this.Controls.Add(this.saveFileBrowseBtn);
- this.Name = "DebugPane";
- this.Size = new System.Drawing.Size(856, 475);
- this.variableDumperBox.ResumeLayout(false);
- this.variableDumperBox.PerformLayout();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.Button dumpVariablesBtn;
- private System.Windows.Forms.TextBox saveFilePath;
- private System.Windows.Forms.Label savePathLbl;
- private System.Windows.Forms.Button saveFileBrowseBtn;
- private System.Windows.Forms.OpenFileDialog savePathDlg;
- private System.Windows.Forms.CheckBox dumpGlobalVars;
- private System.Windows.Forms.GroupBox variableDumperBox;
- private System.Windows.Forms.CheckBox includeUnnamedDbs;
- private System.Windows.Forms.CheckBox dumpDatabases;
- private System.Windows.Forms.CheckBox includeDeleted;
- private System.Windows.Forms.CheckBox dumpItemVars;
- private System.Windows.Forms.CheckBox dumpCharacterVars;
- private System.Windows.Forms.CheckBox includeLocalScopes;
- private System.Windows.Forms.Label lblProgressStatus;
- private System.Windows.Forms.ProgressBar dumpProgressBar;
- private System.Windows.Forms.Label lblProgressText;
- private System.Windows.Forms.CheckBox extractPackage;
- private System.Windows.Forms.CheckBox exportModList;
- private System.Windows.Forms.CheckBox convertLsf;
- private System.Windows.Forms.CheckBox dumpGoals;
- }
-}
diff --git a/ConverterApp/DebugPane.axaml b/ConverterApp/DebugPane.axaml
new file mode 100644
index 00000000..2856506c
--- /dev/null
+++ b/ConverterApp/DebugPane.axaml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ConverterApp/DebugPane.axaml.cs b/ConverterApp/DebugPane.axaml.cs
new file mode 100644
index 00000000..2c70a214
--- /dev/null
+++ b/ConverterApp/DebugPane.axaml.cs
@@ -0,0 +1,189 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Platform.Storage;
+using Avalonia.Threading;
+using LSLib.LS.Enums;
+using LSLib.LS.Story.Compiler;
+using ReactiveUI.Avalonia;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows.Input;
+
+namespace LSTools.DivineGUI;
+
+public partial class DebugPane : ReactiveUserControl, IGameSettingsTarget, INotifyPropertyChanged
+{
+ private readonly ISettingsDataSource? _settingsDataSource;
+
+ public ObservableCollection DiagnosticLogs { get; } = [];
+
+ public Game Game { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(Game)); } } } = Game.BaldursGate3;
+
+ public string SaveFilePath
+ {
+ get => _settingsDataSource?.Settings?.Debugging?.SavePath ?? string.Empty;
+ set
+ {
+ if (_settingsDataSource?.Settings?.Debugging != null && _settingsDataSource.Settings.Debugging.SavePath != value)
+ {
+ _settingsDataSource.Settings.Debugging.SavePath = value;
+ InvokeLocalProperty(nameof(SaveFilePath));
+ UpdateCommandStates();
+ }
+ }
+ }
+
+ public bool ExtractAll { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(ExtractAll)); } } } = true;
+ public bool ConvertToLsx { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(ConvertToLsx)); } } } = true;
+ public bool DumpModList { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(DumpModList)); } } } = true;
+ public bool DumpGlobalVars { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(DumpGlobalVars)); } } } = true;
+ public bool DumpCharacterVars { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(DumpCharacterVars)); } } } = true;
+ public bool DumpItemVars { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(DumpItemVars)); } } } = true;
+ public bool IncludeDeletedVars { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(IncludeDeletedVars)); } } }
+ public bool IncludeLocalScopes { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(IncludeLocalScopes)); } } }
+ public bool DumpStoryDatabases { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(DumpStoryDatabases)); } } } = true;
+ public bool IncludeUnnamedDatabases { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(IncludeUnnamedDatabases)); } } }
+ public int ProgressPercentage { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(ProgressPercentage)); } } }
+ public string ProgressStatusText { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(ProgressStatusText)); } } } = string.Empty;
+ public string ErrorMessage { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(ErrorMessage)); } } } = string.Empty;
+ public string SuccessMessage { get; set { if (field != value) { field = value; InvokeLocalProperty(nameof(SuccessMessage)); } } } = string.Empty;
+
+ public ICommand SaveFileBrowseCommand { get; }
+ public ICommand DumpVariablesCommand { get; }
+
+ public DebugPane()
+ {
+ InitializeComponent();
+ DataContext = this;
+ ViewModel = this;
+
+ SaveFileBrowseCommand = new DebugPaneRelayCommand(_ => _ = ExecuteBrowseAsync());
+ DumpVariablesCommand = new DebugPaneRelayCommand(_ => _ = ExecuteDumpAsync(), () => !string.IsNullOrWhiteSpace(SaveFilePath) && SaveFilePath.EndsWith(".lsv", StringComparison.OrdinalIgnoreCase));
+ }
+
+ public DebugPane(ISettingsDataSource settingsDataSource) : this()
+ {
+ _settingsDataSource = settingsDataSource ?? throw new ArgumentNullException(nameof(settingsDataSource));
+ }
+
+
+ private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
+
+ private void UpdateCommandStates() => ((DebugPaneRelayCommand)DumpVariablesCommand).RaiseCanExecuteChanged();
+
+ public void SetGame(Game game)
+ {
+ Game = game;
+ }
+
+ private DebugDumperTask CreateDumperFromSettings()
+ {
+ string directoryName = Path.GetDirectoryName(SaveFilePath) ?? AppContext.BaseDirectory;
+ string fileName = Path.GetFileNameWithoutExtension(SaveFilePath);
+ string dumpPath = Path.Combine(directoryName, fileName);
+
+ return new DebugDumperTask
+ {
+ GameVersion = Game,
+ ExtractionPath = Path.Combine(dumpPath, "SaveArchive"),
+ DataDumpPath = Path.Combine(dumpPath, "Dumps"),
+ SaveFilePath = SaveFilePath,
+ ExtractAll = ExtractAll,
+ ConvertToLsx = ConvertToLsx,
+ DumpModList = DumpModList,
+ DumpGlobalVars = DumpGlobalVars,
+ DumpCharacterVars = DumpCharacterVars,
+ DumpItemVars = DumpItemVars,
+ IncludeDeletedVars = IncludeDeletedVars,
+ IncludeLocalScopes = IncludeLocalScopes,
+ DumpStoryDatabases = DumpStoryDatabases,
+ IncludeUnnamedDatabases = IncludeUnnamedDatabases
+ };
+ }
+
+ private async Task ExecuteBrowseAsync()
+ {
+ var topLevel = TopLevel.GetTopLevel(this);
+ if (topLevel == null) return;
+
+ var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
+ {
+ Title = "Open LSLib Savegame Package File",
+ AllowMultiple = false,
+ FileTypeFilter = [
+ new FilePickerFileType("Larian Savegame Package (*.lsv)")
+ {
+ Patterns = ["*.lsv"]
+ }
+ ]
+ });
+
+ if (files.Count > 0)
+ {
+ SaveFilePath = files[0].Path.LocalPath;
+ }
+ }
+
+ private async Task ExecuteDumpAsync()
+ {
+ ErrorMessage = string.Empty;
+ SuccessMessage = string.Empty;
+ DiagnosticLogs.Clear(); // Clear out previous run diagnostics
+
+ var dumper = CreateDumperFromSettings();
+
+ dumper.ProgressUpdated += (args) =>
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ ProgressPercentage = args.Percentage;
+ ProgressStatusText = args.StatusText;
+ });
+ };
+
+ try
+ {
+ await dumper.RunAsync();
+ SuccessMessage = $"Savegame successfully dumped to:\n{dumper.DataDumpPath}";
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Dump Failed!\n\nInternal processing error:\n{ex.Message}";
+ }
+ finally
+ {
+ // FIX: Copies the processed background logs out of your task onto the UI thread grid collection
+ foreach (var log in dumper.TaskDiagnostics)
+ {
+ DiagnosticLogs.Add(log);
+ }
+
+ ProgressPercentage = 0;
+ ProgressStatusText = string.Empty;
+ }
+ }
+
+ private PropertyChangedEventHandler? _localPropertyChanged;
+
+ event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged
+ {
+ add => _localPropertyChanged += value;
+ remove => _localPropertyChanged -= value;
+ }
+
+ private void InvokeLocalProperty(string propertyName)
+ {
+ _localPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
+
+public class DebugPaneRelayCommand(Action