diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59e6eb6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+**/bin/**
+**/obj/**
diff --git a/.idea/.idea.GsaViewer/.idea/material_theme_project_new.xml b/.idea/.idea.GsaViewer/.idea/material_theme_project_new.xml
index 368f71c..a47b547 100644
--- a/.idea/.idea.GsaViewer/.idea/material_theme_project_new.xml
+++ b/.idea/.idea.GsaViewer/.idea/material_theme_project_new.xml
@@ -3,7 +3,9 @@
diff --git a/GsaEditor.Core/IO/GsaIndexReader.cs b/GsaEditor.Core/IO/GsaIndexReader.cs
index 5937e0b..beffa0e 100644
--- a/GsaEditor.Core/IO/GsaIndexReader.cs
+++ b/GsaEditor.Core/IO/GsaIndexReader.cs
@@ -1,4 +1,4 @@
-using System.Xml.Linq;
+using System.Text.RegularExpressions;
namespace GsaEditor.Core.IO;
@@ -34,10 +34,20 @@ public class GsaIndexEntry
}
///
-/// Parses .idx NML index files (XML-compatible format) into a list of .
+/// Parses .idx NML index files into a list of .
+/// NML is the engine's own tag syntax (<Name=value> blocks, see ),
+/// not standard XML.
///
public static class GsaIndexReader
{
+ private static readonly Regex EntryRegex = new(
+ @".*?)\n\s*>",
+ RegexOptions.Singleline | RegexOptions.Compiled);
+
+ private static readonly Regex TagRegex = new(
+ @"<(?\w+)=""?(?[^"">]*)""?>",
+ RegexOptions.Compiled);
+
///
/// Reads index entries from the specified .idx file.
///
@@ -45,23 +55,36 @@ public static class GsaIndexReader
/// A list of parsed index entries.
public static List Read(string filePath)
{
- var doc = XDocument.Load(filePath);
+ var text = File.ReadAllText(filePath);
var entries = new List();
- var root = doc.Root;
- if (root == null || root.Name.LocalName != "Index")
- return entries;
-
- foreach (var entryEl in root.Elements("Entry"))
+ foreach (Match entryMatch in EntryRegex.Matches(text))
{
- var entry = new GsaIndexEntry
+ var entry = new GsaIndexEntry();
+
+ foreach (Match tag in TagRegex.Matches(entryMatch.Groups["body"].Value))
{
- Id = entryEl.Element("Id")?.Value ?? string.Empty,
- Method = int.TryParse(entryEl.Element("Method")?.Value, out var m) ? m : 0,
- Length = uint.TryParse(entryEl.Element("Len")?.Value, out var l) ? l : 0,
- CompressedLength = uint.TryParse(entryEl.Element("CompLen")?.Value, out var cl) ? cl : 0,
- Offset = long.TryParse(entryEl.Element("Offset")?.Value, out var o) ? o : 0
- };
+ var value = tag.Groups["value"].Value;
+ switch (tag.Groups["name"].Value)
+ {
+ case "ID":
+ entry.Id = value;
+ break;
+ case "Method":
+ entry.Method = value.Equals("Zlib", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
+ break;
+ case "Len":
+ entry.Length = uint.TryParse(value, out var l) ? l : 0;
+ break;
+ case "CompLen":
+ entry.CompressedLength = uint.TryParse(value, out var cl) ? cl : 0;
+ break;
+ case "Offset":
+ entry.Offset = long.TryParse(value, out var o) ? o : 0;
+ break;
+ }
+ }
+
entries.Add(entry);
}
diff --git a/GsaEditor.Core/IO/GsaIndexWriter.cs b/GsaEditor.Core/IO/GsaIndexWriter.cs
index f462cae..e6604ee 100644
--- a/GsaEditor.Core/IO/GsaIndexWriter.cs
+++ b/GsaEditor.Core/IO/GsaIndexWriter.cs
@@ -1,36 +1,62 @@
-using System.Xml.Linq;
+using System.Text;
using GsaEditor.Core.Models;
namespace GsaEditor.Core.IO;
///
/// Writes a .idx NML index file from a .
-/// The index file is XML-compatible and lists each entry with its alias, compression method,
-/// sizes, and data offset for fast lookup without a full sequential scan.
+/// The NML format is the engine's own tag syntax (NOT standard XML):
+///
+/// <Version=1.0>
+/// <Index=
+/// <Entry=
+/// <ID="path/in/archive">
+/// <CompLen=123>
+/// <Len=456>
+/// <Offset=789>
+/// <Method="Zlib">
+/// >
+/// >
+///
+/// Tab indentation, LF line endings, no BOM. Method is "Zlib" or "Raw".
+/// The engine parses this file to locate entries, so the format must match exactly.
///
public static class GsaIndexWriter
{
+ ///
+ /// The engine's bootstrap script is loaded by sequential archive scan and is
+ /// deliberately absent from the shipped index files, so it is skipped here too.
+ ///
+ private const string BootstrapAlias = "bootstrap.txt";
+
///
/// Writes the index file for the given archive to the specified path.
+ /// Entry offsets must be up to date (i.e. call after ).
///
/// The archive whose entries will be indexed.
/// The output .idx file path.
public static void Write(GsaArchive archive, string filePath)
{
- var doc = new XDocument(
- new XElement("Index",
- archive.Entries.Select(e =>
- new XElement("Entry",
- new XElement("Id", e.Alias),
- new XElement("Method", e.IsCompressed ? 1 : 0),
- new XElement("Len", e.OriginalLength),
- new XElement("CompLen", e.CompressedLength),
- new XElement("Offset", e.DataOffset)
- )
- )
- )
- );
+ var sb = new StringBuilder();
+ sb.Append("\n");
+ sb.Append("\n");
+ sb.Append("\t\t\n");
+ sb.Append("\t\t\n");
+ sb.Append("\t\t\n");
+ sb.Append("\t\t\n");
+ sb.Append("\t>\n");
+ }
+
+ sb.Append(">\n");
+
+ File.WriteAllText(filePath, sb.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}
}
diff --git a/GsaEditor.Core/IO/GsaWriter.cs b/GsaEditor.Core/IO/GsaWriter.cs
index 333474c..3d29e18 100644
--- a/GsaEditor.Core/IO/GsaWriter.cs
+++ b/GsaEditor.Core/IO/GsaWriter.cs
@@ -15,6 +15,12 @@ public static class GsaWriter
/// Magic word for the NARD (enhanced) format.
private const uint MAGIC_NARD = 0x4E415244;
+ ///
+ /// End-of-archive terminator written where the next entry's alias length would be.
+ /// The engine's sequential reader stops when it hits this invalid length.
+ ///
+ private const uint TERMINATOR = 0xFFFFFFFF;
+
///
/// Writes the archive to the specified file path.
///
@@ -53,6 +59,11 @@ public static class GsaWriter
{
WriteEntry(writer, entry, archive);
}
+
+ // Write the end-of-archive terminator, aligned like an entry header
+ if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
+ WritePadding(writer, archive.OffsetPadding);
+ writer.Write(TERMINATOR);
}
///
diff --git a/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.dll b/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.dll
index 7e45f4a..d7d1423 100644
Binary files a/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.dll and b/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.dll differ
diff --git a/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.pdb b/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.pdb
index 3abb27e..383ce41 100644
Binary files a/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.pdb and b/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.pdb differ
diff --git a/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.xml b/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.xml
index 79f2155..30a5815 100644
--- a/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.xml
+++ b/GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.xml
@@ -67,7 +67,9 @@
- Parses .idx NML index files (XML-compatible format) into a list of .
+ Parses .idx NML index files into a list of .
+ NML is the engine's own tag syntax (<Name=value> blocks, see ),
+ not standard XML.
@@ -80,13 +82,33 @@
Writes a .idx NML index file from a .
- The index file is XML-compatible and lists each entry with its alias, compression method,
- sizes, and data offset for fast lookup without a full sequential scan.
+ The NML format is the engine's own tag syntax (NOT standard XML):
+
+ <Version=1.0>
+ <Index=
+ <Entry=
+ <ID="path/in/archive">
+ <CompLen=123>
+ <Len=456>
+ <Offset=789>
+ <Method="Zlib">
+ >
+ >
+
+ Tab indentation, LF line endings, no BOM. Method is "Zlib" or "Raw".
+ The engine parses this file to locate entries, so the format must match exactly.
+
+
+
+
+ The engine's bootstrap script is loaded by sequential archive scan and is
+ deliberately absent from the shipped index files, so it is skipped here too.
Writes the index file for the given archive to the specified path.
+ Entry offsets must be up to date (i.e. call after ).
The archive whose entries will be indexed.
The output .idx file path.
@@ -149,6 +171,12 @@
Magic word for the NARD (enhanced) format.
+
+
+ End-of-archive terminator written where the next entry's alias length would be.
+ The engine's sequential reader stops when it hits this invalid length.
+
+
Writes the archive to the specified file path.
diff --git a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfo.cs b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfo.cs
index 44a6762..e4e60c7 100644
--- a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfo.cs
+++ b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfo.cs
@@ -13,10 +13,10 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaEditor.Core")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d6d621dc92b3083d8e47827baa0ccf59d5b0a4c4")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+cbf0891ba467ebec2dd40e9b34eb1f2d6379d7dd")]
[assembly: System.Reflection.AssemblyProductAttribute("GsaEditor.Core")]
[assembly: System.Reflection.AssemblyTitleAttribute("GsaEditor.Core")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
-// Généré par la classe MSBuild WriteCodeFragment.
+// Generated by the MSBuild WriteCodeFragment class.
diff --git a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfoInputs.cache b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfoInputs.cache
index f61127c..e346c7e 100644
--- a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfoInputs.cache
+++ b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.AssemblyInfoInputs.cache
@@ -1 +1 @@
-27416b735d6b5e6056e4f76966d345ebebb5a18e412e4170cdbcf381f9ab7092
+24bad01704fb232d5dd4f84bfcb34e9e1b11dc1475b9ee697c24bd2eb1122246
diff --git a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.dll b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.dll
index 7e45f4a..d7d1423 100644
Binary files a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.dll and b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.dll differ
diff --git a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.pdb b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.pdb
index 3abb27e..383ce41 100644
Binary files a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.pdb and b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.pdb differ
diff --git a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.xml b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.xml
index 79f2155..30a5815 100644
--- a/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.xml
+++ b/GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.xml
@@ -67,7 +67,9 @@
- Parses .idx NML index files (XML-compatible format) into a list of .
+ Parses .idx NML index files into a list of .
+ NML is the engine's own tag syntax (<Name=value> blocks, see ),
+ not standard XML.
@@ -80,13 +82,33 @@
Writes a .idx NML index file from a .
- The index file is XML-compatible and lists each entry with its alias, compression method,
- sizes, and data offset for fast lookup without a full sequential scan.
+ The NML format is the engine's own tag syntax (NOT standard XML):
+
+ <Version=1.0>
+ <Index=
+ <Entry=
+ <ID="path/in/archive">
+ <CompLen=123>
+ <Len=456>
+ <Offset=789>
+ <Method="Zlib">
+ >
+ >
+
+ Tab indentation, LF line endings, no BOM. Method is "Zlib" or "Raw".
+ The engine parses this file to locate entries, so the format must match exactly.
+
+
+
+
+ The engine's bootstrap script is loaded by sequential archive scan and is
+ deliberately absent from the shipped index files, so it is skipped here too.
Writes the index file for the given archive to the specified path.
+ Entry offsets must be up to date (i.e. call after ).
The archive whose entries will be indexed.
The output .idx file path.
@@ -149,6 +171,12 @@
Magic word for the NARD (enhanced) format.
+
+
+ End-of-archive terminator written where the next entry's alias length would be.
+ The engine's sequential reader stops when it hits this invalid length.
+
+
Writes the archive to the specified file path.
diff --git a/GsaEditor.Core/obj/Debug/net8.0/ref/GsaEditor.Core.dll b/GsaEditor.Core/obj/Debug/net8.0/ref/GsaEditor.Core.dll
index 3b897f7..4b083fb 100644
Binary files a/GsaEditor.Core/obj/Debug/net8.0/ref/GsaEditor.Core.dll and b/GsaEditor.Core/obj/Debug/net8.0/ref/GsaEditor.Core.dll differ
diff --git a/GsaEditor.Core/obj/Debug/net8.0/refint/GsaEditor.Core.dll b/GsaEditor.Core/obj/Debug/net8.0/refint/GsaEditor.Core.dll
index 3b897f7..4b083fb 100644
Binary files a/GsaEditor.Core/obj/Debug/net8.0/refint/GsaEditor.Core.dll and b/GsaEditor.Core/obj/Debug/net8.0/refint/GsaEditor.Core.dll differ
diff --git a/GsaEditor/ViewModels/EntryTreeNodeViewModel.cs b/GsaEditor/ViewModels/EntryTreeNodeViewModel.cs
index b68f02a..cca24e4 100644
--- a/GsaEditor/ViewModels/EntryTreeNodeViewModel.cs
+++ b/GsaEditor/ViewModels/EntryTreeNodeViewModel.cs
@@ -13,6 +13,9 @@ public partial class EntryTreeNodeViewModel : ViewModelBase
[ObservableProperty]
private string _name = string.Empty;
+ [ObservableProperty]
+ private bool _isExpanded;
+
///
/// Full relative path of this node within the archive (using '/' separator).
///
diff --git a/GsaEditor/ViewModels/MainWindowViewModel.cs b/GsaEditor/ViewModels/MainWindowViewModel.cs
index fad9cbd..e84e2a5 100644
--- a/GsaEditor/ViewModels/MainWindowViewModel.cs
+++ b/GsaEditor/ViewModels/MainWindowViewModel.cs
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
+using System.Text.RegularExpressions;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
@@ -84,8 +85,24 @@ public partial class MainWindowViewModel : ViewModelBase
[ObservableProperty]
private bool _isEditingText;
+ [ObservableProperty]
+ private string? _searchQuery;
+
+ [ObservableProperty]
+ private bool _searchInContents;
+
+ [ObservableProperty]
+ private string? _searchStatus;
+
+ [ObservableProperty]
+ private SearchResultViewModel? _selectedSearchResult;
+
public ObservableCollection TreeRoots { get; } = new();
+ public ObservableCollection SearchResults { get; } = new();
+
+ public bool HasSearchResults => SearchResults.Count > 0;
+
// --- Derived properties ---
public string Title => ArchivePath != null
@@ -255,6 +272,8 @@ public partial class MainWindowViewModel : ViewModelBase
_archive = archive;
ArchivePath = path;
IsDirty = false;
+ SearchQuery = null;
+ ClearSearchResults();
BuildTree();
NotifyStatusChanged();
}
@@ -347,6 +366,8 @@ public partial class MainWindowViewModel : ViewModelBase
TreeRoots.Clear();
SelectedEntry = null;
SelectedNode = null;
+ SearchQuery = null;
+ ClearSearchResults();
ClearPreview();
NotifyStatusChanged();
}
@@ -663,6 +684,215 @@ public partial class MainWindowViewModel : ViewModelBase
}
}
+ [RelayCommand]
+ private async Task OpenAsText()
+ {
+ if (SelectedNode?.Entry == null) return;
+
+ var data = SelectedNode.Entry.GetDecompressedData();
+
+ try
+ {
+ new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(data);
+ }
+ catch (DecoderFallbackException)
+ {
+ if (_window != null)
+ {
+ var confirmed = await Dialogs.ConfirmAsync(_window, "Not a Text File",
+ "This file is not valid UTF-8 text. Editing and saving it as text will corrupt its content.\n\nOpen as text anyway?");
+ if (!confirmed) return;
+ }
+ }
+
+ PreviewMode = PreviewMode.Text;
+ PreviewText = Encoding.UTF8.GetString(data);
+ PreviewImage?.Dispose();
+ PreviewImage = null;
+ HexDumpText = null;
+ }
+
+ // =========================================================================
+ // Search
+ // =========================================================================
+
+ private const int MaxNameResults = 200;
+ private const int MaxContentResults = 1000;
+
+ partial void OnSearchQueryChanged(string? value)
+ {
+ // Name search is live; content search only runs on Enter / the Search button
+ if (!SearchInContents)
+ RunNameSearch();
+ else if (string.IsNullOrWhiteSpace(value))
+ ClearSearchResults();
+ }
+
+ partial void OnSearchInContentsChanged(bool value)
+ {
+ if (!value)
+ RunNameSearch();
+ else
+ ClearSearchResults();
+ }
+
+ partial void OnSelectedSearchResultChanged(SearchResultViewModel? value)
+ {
+ if (value != null && value.Alias.Length > 0)
+ SelectEntryNode(value.Alias);
+ }
+
+ [RelayCommand]
+ private async Task Search()
+ {
+ if (_archive == null) return;
+
+ if (!SearchInContents)
+ {
+ RunNameSearch();
+ return;
+ }
+
+ if (string.IsNullOrEmpty(SearchQuery)) return;
+
+ Regex regex;
+ try
+ {
+ regex = new Regex(SearchQuery, RegexOptions.None, TimeSpan.FromSeconds(5));
+ }
+ catch (ArgumentException ex)
+ {
+ if (_window != null)
+ await Dialogs.ShowMessageAsync(_window, "Invalid Regex", $"Invalid regular expression:\n{ex.Message}");
+ return;
+ }
+
+ IsLoading = true;
+ try
+ {
+ var entries = _archive.Entries.ToList();
+ var results = await Task.Run(() =>
+ {
+ var found = new List();
+ foreach (var entry in entries)
+ {
+ string text;
+ try
+ {
+ text = Encoding.UTF8.GetString(entry.GetDecompressedData());
+ }
+ catch
+ {
+ continue;
+ }
+
+ int lineNo = 0;
+ foreach (var rawLine in text.Split('\n'))
+ {
+ lineNo++;
+ var line = rawLine.TrimEnd('\r');
+
+ bool isMatch;
+ try
+ {
+ isMatch = regex.IsMatch(line);
+ }
+ catch (RegexMatchTimeoutException)
+ {
+ break;
+ }
+
+ if (!isMatch) continue;
+
+ var snippet = line.Trim();
+ if (snippet.Length > 120) snippet = snippet.Substring(0, 120) + "…";
+ found.Add(new SearchResultViewModel
+ {
+ Alias = entry.Alias,
+ Detail = $"line {lineNo}: {snippet}"
+ });
+
+ if (found.Count >= MaxContentResults)
+ return found;
+ }
+ }
+ return found;
+ });
+
+ SearchResults.Clear();
+ foreach (var r in results)
+ SearchResults.Add(r);
+ OnPropertyChanged(nameof(HasSearchResults));
+
+ SearchStatus = results.Count >= MaxContentResults
+ ? $"{results.Count} matches (stopped at {MaxContentResults})"
+ : $"{results.Count} match(es)";
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ private void RunNameSearch()
+ {
+ SearchResults.Clear();
+
+ var query = SearchQuery;
+ if (_archive == null || string.IsNullOrWhiteSpace(query))
+ {
+ SearchStatus = null;
+ OnPropertyChanged(nameof(HasSearchResults));
+ return;
+ }
+
+ int total = 0;
+ foreach (var entry in _archive.Entries)
+ {
+ if (!entry.Alias.Contains(query, StringComparison.OrdinalIgnoreCase)) continue;
+ total++;
+ if (SearchResults.Count < MaxNameResults)
+ SearchResults.Add(new SearchResultViewModel { Alias = entry.Alias });
+ }
+
+ SearchStatus = total > MaxNameResults
+ ? $"{total} file(s) (showing first {MaxNameResults})"
+ : $"{total} file(s)";
+ OnPropertyChanged(nameof(HasSearchResults));
+ }
+
+ private void ClearSearchResults()
+ {
+ SearchResults.Clear();
+ SearchStatus = null;
+ SelectedSearchResult = null;
+ OnPropertyChanged(nameof(HasSearchResults));
+ }
+
+ ///
+ /// Selects the tree node matching the given alias, expanding parent folders along the way.
+ ///
+ private void SelectEntryNode(string alias)
+ {
+ var parts = alias.Split('/');
+ var level = TreeRoots;
+ EntryTreeNodeViewModel? node = null;
+
+ foreach (var part in parts)
+ {
+ node = level.FirstOrDefault(n => n.Name == part);
+ if (node == null) return;
+ if (node.IsFolder)
+ {
+ node.IsExpanded = true;
+ level = node.Children;
+ }
+ }
+
+ if (node != null && !node.IsFolder)
+ SelectedNode = node;
+ }
+
// =========================================================================
// About
// =========================================================================
diff --git a/GsaEditor/ViewModels/SearchResultViewModel.cs b/GsaEditor/ViewModels/SearchResultViewModel.cs
new file mode 100644
index 0000000..b856ddb
--- /dev/null
+++ b/GsaEditor/ViewModels/SearchResultViewModel.cs
@@ -0,0 +1,19 @@
+namespace GsaEditor.ViewModels;
+
+///
+/// A single search hit: a file name match, or a matching line inside a file.
+///
+public class SearchResultViewModel
+{
+ ///
+ /// Alias of the matching archive entry.
+ ///
+ public string Alias { get; init; } = string.Empty;
+
+ ///
+ /// Match context for content searches (e.g. "line 12: ..."). Empty for name matches.
+ ///
+ public string Detail { get; init; } = string.Empty;
+
+ public bool HasDetail => Detail.Length > 0;
+}
diff --git a/GsaEditor/Views/MainWindow.axaml b/GsaEditor/Views/MainWindow.axaml
index 935ba7d..80af9fe 100644
--- a/GsaEditor/Views/MainWindow.axaml
+++ b/GsaEditor/Views/MainWindow.axaml
@@ -93,10 +93,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -221,14 +272,21 @@
-
+
+
+
+
+
+
diff --git a/GsaEditor/Views/MainWindow.axaml.cs b/GsaEditor/Views/MainWindow.axaml.cs
index 28e33d6..796ac3c 100644
--- a/GsaEditor/Views/MainWindow.axaml.cs
+++ b/GsaEditor/Views/MainWindow.axaml.cs
@@ -59,6 +59,19 @@ public partial class MainWindow : Window
{
ApplySyntaxHighlighting(vm);
}
+
+ if (e.PropertyName == nameof(MainWindowViewModel.SelectedNode))
+ {
+ // Reflect programmatic selection (e.g. from search results) in the tree.
+ // Posted so newly expanded parent containers are realized first.
+ var tree = this.FindControl("ArchiveTree");
+ if (tree != null && vm.SelectedNode != null && !ReferenceEquals(tree.SelectedItem, vm.SelectedNode))
+ {
+ Avalonia.Threading.Dispatcher.UIThread.Post(
+ () => tree.SelectedItem = vm.SelectedNode,
+ Avalonia.Threading.DispatcherPriority.Background);
+ }
+ }
}
private void ApplySyntaxHighlighting(MainWindowViewModel vm)
diff --git a/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.dll b/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.dll
index 7e45f4a..d7d1423 100644
Binary files a/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.dll and b/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.dll differ
diff --git a/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.pdb b/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.pdb
index 3abb27e..383ce41 100644
Binary files a/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.pdb and b/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.pdb differ
diff --git a/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.xml b/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.xml
index 79f2155..30a5815 100644
--- a/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.xml
+++ b/GsaEditor/bin/Debug/net8.0/GsaEditor.Core.xml
@@ -67,7 +67,9 @@
- Parses .idx NML index files (XML-compatible format) into a list of .
+ Parses .idx NML index files into a list of .
+ NML is the engine's own tag syntax (<Name=value> blocks, see ),
+ not standard XML.
@@ -80,13 +82,33 @@
Writes a .idx NML index file from a .
- The index file is XML-compatible and lists each entry with its alias, compression method,
- sizes, and data offset for fast lookup without a full sequential scan.
+ The NML format is the engine's own tag syntax (NOT standard XML):
+
+ <Version=1.0>
+ <Index=
+ <Entry=
+ <ID="path/in/archive">
+ <CompLen=123>
+ <Len=456>
+ <Offset=789>
+ <Method="Zlib">
+ >
+ >
+
+ Tab indentation, LF line endings, no BOM. Method is "Zlib" or "Raw".
+ The engine parses this file to locate entries, so the format must match exactly.
+
+
+
+
+ The engine's bootstrap script is loaded by sequential archive scan and is
+ deliberately absent from the shipped index files, so it is skipped here too.
Writes the index file for the given archive to the specified path.
+ Entry offsets must be up to date (i.e. call after ).
The archive whose entries will be indexed.
The output .idx file path.
@@ -149,6 +171,12 @@
Magic word for the NARD (enhanced) format.
+
+
+ End-of-archive terminator written where the next entry's alias length would be.
+ The engine's sequential reader stops when it hits this invalid length.
+
+
Writes the archive to the specified file path.
diff --git a/GsaEditor/bin/Debug/net8.0/GsaEditor.dll b/GsaEditor/bin/Debug/net8.0/GsaEditor.dll
index 907dea6..f11d93f 100644
Binary files a/GsaEditor/bin/Debug/net8.0/GsaEditor.dll and b/GsaEditor/bin/Debug/net8.0/GsaEditor.dll differ
diff --git a/GsaEditor/bin/Debug/net8.0/GsaEditor.exe b/GsaEditor/bin/Debug/net8.0/GsaEditor.exe
index e260077..783e902 100644
Binary files a/GsaEditor/bin/Debug/net8.0/GsaEditor.exe and b/GsaEditor/bin/Debug/net8.0/GsaEditor.exe differ
diff --git a/GsaEditor/bin/Debug/net8.0/GsaEditor.pdb b/GsaEditor/bin/Debug/net8.0/GsaEditor.pdb
index 5ecdcdd..869073d 100644
Binary files a/GsaEditor/bin/Debug/net8.0/GsaEditor.pdb and b/GsaEditor/bin/Debug/net8.0/GsaEditor.pdb differ
diff --git a/GsaEditor/obj/Debug/net8.0/Avalonia/Resources.Inputs.cache b/GsaEditor/obj/Debug/net8.0/Avalonia/Resources.Inputs.cache
index 2669050..8c5b987 100644
--- a/GsaEditor/obj/Debug/net8.0/Avalonia/Resources.Inputs.cache
+++ b/GsaEditor/obj/Debug/net8.0/Avalonia/Resources.Inputs.cache
@@ -1 +1 @@
-aada552c8fd51710edc7ad0d0c49003781116f6ee0520c030a54982aadcd9736
+eae8f0d208f1a483cabdff404aa7aa142ad05798b023a7ebd7aceb317939a4cf
diff --git a/GsaEditor/obj/Debug/net8.0/Avalonia/resources b/GsaEditor/obj/Debug/net8.0/Avalonia/resources
index 4cecab2..f4eab94 100644
Binary files a/GsaEditor/obj/Debug/net8.0/Avalonia/resources and b/GsaEditor/obj/Debug/net8.0/Avalonia/resources differ
diff --git a/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfo.cs b/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfo.cs
index 453d086..ee9ca33 100644
--- a/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfo.cs
+++ b/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfo.cs
@@ -13,10 +13,10 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaEditor")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d6d621dc92b3083d8e47827baa0ccf59d5b0a4c4")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+cbf0891ba467ebec2dd40e9b34eb1f2d6379d7dd")]
[assembly: System.Reflection.AssemblyProductAttribute("GsaEditor")]
[assembly: System.Reflection.AssemblyTitleAttribute("GsaEditor")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
-// Généré par la classe MSBuild WriteCodeFragment.
+// Generated by the MSBuild WriteCodeFragment class.
diff --git a/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfoInputs.cache b/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfoInputs.cache
index 78b77dd..c2ced9d 100644
--- a/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfoInputs.cache
+++ b/GsaEditor/obj/Debug/net8.0/GsaEditor.AssemblyInfoInputs.cache
@@ -1 +1 @@
-2ff94264e42d1924e3c6fd4243d279b8ea5cccc75f6ef146a562319dedcab0d3
+27365fc2eec4d91990ba8ea7d96835a6e6291b2f5002237551bfa5d30ea5cac6
diff --git a/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.AssemblyReference.cache b/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.AssemblyReference.cache
index 5ff9aef..72e52f8 100644
Binary files a/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.AssemblyReference.cache and b/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.AssemblyReference.cache differ
diff --git a/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.CoreCompileInputs.cache b/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.CoreCompileInputs.cache
index 177e761..e794590 100644
--- a/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.CoreCompileInputs.cache
+++ b/GsaEditor/obj/Debug/net8.0/GsaEditor.csproj.CoreCompileInputs.cache
@@ -1 +1 @@
-39a488a720409bb8a448fa3ac77a4fee498f2436a8299f5fec82e7f76321352a
+9aadcb9c3b29da493ac1af6d86cd2bccb6af7d19576833358fa0b759709d9ce1
diff --git a/GsaEditor/obj/Debug/net8.0/GsaEditor.dll b/GsaEditor/obj/Debug/net8.0/GsaEditor.dll
index 907dea6..f11d93f 100644
Binary files a/GsaEditor/obj/Debug/net8.0/GsaEditor.dll and b/GsaEditor/obj/Debug/net8.0/GsaEditor.dll differ
diff --git a/GsaEditor/obj/Debug/net8.0/GsaEditor.pdb b/GsaEditor/obj/Debug/net8.0/GsaEditor.pdb
index 5ecdcdd..869073d 100644
Binary files a/GsaEditor/obj/Debug/net8.0/GsaEditor.pdb and b/GsaEditor/obj/Debug/net8.0/GsaEditor.pdb differ
diff --git a/GsaEditor/obj/Debug/net8.0/apphost.exe b/GsaEditor/obj/Debug/net8.0/apphost.exe
index e260077..783e902 100644
Binary files a/GsaEditor/obj/Debug/net8.0/apphost.exe and b/GsaEditor/obj/Debug/net8.0/apphost.exe differ
diff --git a/GsaEditor/obj/Debug/net8.0/ref/GsaEditor.dll b/GsaEditor/obj/Debug/net8.0/ref/GsaEditor.dll
index 0d4bfc3..7e4a430 100644
Binary files a/GsaEditor/obj/Debug/net8.0/ref/GsaEditor.dll and b/GsaEditor/obj/Debug/net8.0/ref/GsaEditor.dll differ
diff --git a/GsaEditor/obj/Debug/net8.0/refint/GsaEditor.dll b/GsaEditor/obj/Debug/net8.0/refint/GsaEditor.dll
index 0d4bfc3..7e4a430 100644
Binary files a/GsaEditor/obj/Debug/net8.0/refint/GsaEditor.dll and b/GsaEditor/obj/Debug/net8.0/refint/GsaEditor.dll differ
diff --git a/obj/Debug/net9.0/Avalonia/Resources.Inputs.cache b/obj/Debug/net9.0/Avalonia/Resources.Inputs.cache
index d8aa2f5..138e4ce 100644
--- a/obj/Debug/net9.0/Avalonia/Resources.Inputs.cache
+++ b/obj/Debug/net9.0/Avalonia/Resources.Inputs.cache
@@ -1 +1 @@
-7e7dc0454609f380d65d4e6792d0f069435ad58b608973047eb829ce8ac3f4b4
+889fc292feef8bf02dfc3026d9915bb9baa08741fc71839c03938c2e64645e14
diff --git a/obj/Debug/net9.0/Avalonia/resources b/obj/Debug/net9.0/Avalonia/resources
index 1d1f424..30a9371 100644
Binary files a/obj/Debug/net9.0/Avalonia/resources and b/obj/Debug/net9.0/Avalonia/resources differ
diff --git a/obj/Debug/net9.0/GsaViewer.AssemblyInfo.cs b/obj/Debug/net9.0/GsaViewer.AssemblyInfo.cs
index 72e5d64..71aa8e1 100644
--- a/obj/Debug/net9.0/GsaViewer.AssemblyInfo.cs
+++ b/obj/Debug/net9.0/GsaViewer.AssemblyInfo.cs
@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaViewer")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+cbf0891ba467ebec2dd40e9b34eb1f2d6379d7dd")]
[assembly: System.Reflection.AssemblyProductAttribute("GsaViewer")]
[assembly: System.Reflection.AssemblyTitleAttribute("GsaViewer")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
diff --git a/obj/Debug/net9.0/GsaViewer.AssemblyInfoInputs.cache b/obj/Debug/net9.0/GsaViewer.AssemblyInfoInputs.cache
index d7decc8..554f501 100644
--- a/obj/Debug/net9.0/GsaViewer.AssemblyInfoInputs.cache
+++ b/obj/Debug/net9.0/GsaViewer.AssemblyInfoInputs.cache
@@ -1 +1 @@
-02bce5f0f4908bf808f9b59b3bc005e20eed825237a3bf3d65ea07bda1696b95
+4a85afe2a84876aeeaf24b10d0721cd3b7f5dba258044299d3123346f674505b
diff --git a/obj/Debug/net9.0/GsaViewer.GeneratedMSBuildEditorConfig.editorconfig b/obj/Debug/net9.0/GsaViewer.GeneratedMSBuildEditorConfig.editorconfig
index 2914137..100d1e7 100644
--- a/obj/Debug/net9.0/GsaViewer.GeneratedMSBuildEditorConfig.editorconfig
+++ b/obj/Debug/net9.0/GsaViewer.GeneratedMSBuildEditorConfig.editorconfig
@@ -24,5 +24,11 @@ build_property.EnableCodeStyleSeverity =
[C:/Users/simulateur/Desktop/GsaViewer/App.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
+[C:/Users/simulateur/Desktop/GsaViewer/GsaEditor/App.axaml]
+build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
+
+[C:/Users/simulateur/Desktop/GsaViewer/GsaEditor/Views/MainWindow.axaml]
+build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
+
[C:/Users/simulateur/Desktop/GsaViewer/Views/MainWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml