fixed GSA writing, added always-edit button and search in file and for file
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
**/bin/**
|
||||||
|
**/obj/**
|
||||||
@ -3,7 +3,9 @@
|
|||||||
<component name="MaterialThemeProjectNewConfig">
|
<component name="MaterialThemeProjectNewConfig">
|
||||||
<option name="metadata">
|
<option name="metadata">
|
||||||
<MTProjectMetadataState>
|
<MTProjectMetadataState>
|
||||||
<option name="userId" value="24a2abbf:19d72afb14b:-7ffd" />
|
<option name="migrated" value="true" />
|
||||||
|
<option name="pristineConfig" value="false" />
|
||||||
|
<option name="userId" value="4ce26237:199b9d43f46:-7ffe" />
|
||||||
</MTProjectMetadataState>
|
</MTProjectMetadataState>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System.Xml.Linq;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace GsaEditor.Core.IO;
|
namespace GsaEditor.Core.IO;
|
||||||
|
|
||||||
@ -34,10 +34,20 @@ public class GsaIndexEntry
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses .idx NML index files (XML-compatible format) into a list of <see cref="GsaIndexEntry"/>.
|
/// Parses .idx NML index files into a list of <see cref="GsaIndexEntry"/>.
|
||||||
|
/// NML is the engine's own tag syntax (<Name=value> blocks, see <see cref="GsaIndexWriter"/>),
|
||||||
|
/// not standard XML.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class GsaIndexReader
|
public static class GsaIndexReader
|
||||||
{
|
{
|
||||||
|
private static readonly Regex EntryRegex = new(
|
||||||
|
@"<Entry=(?<body>.*?)\n\s*>",
|
||||||
|
RegexOptions.Singleline | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex TagRegex = new(
|
||||||
|
@"<(?<name>\w+)=""?(?<value>[^"">]*)""?>",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads index entries from the specified .idx file.
|
/// Reads index entries from the specified .idx file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -45,23 +55,36 @@ public static class GsaIndexReader
|
|||||||
/// <returns>A list of parsed index entries.</returns>
|
/// <returns>A list of parsed index entries.</returns>
|
||||||
public static List<GsaIndexEntry> Read(string filePath)
|
public static List<GsaIndexEntry> Read(string filePath)
|
||||||
{
|
{
|
||||||
var doc = XDocument.Load(filePath);
|
var text = File.ReadAllText(filePath);
|
||||||
var entries = new List<GsaIndexEntry>();
|
var entries = new List<GsaIndexEntry>();
|
||||||
|
|
||||||
var root = doc.Root;
|
foreach (Match entryMatch in EntryRegex.Matches(text))
|
||||||
if (root == null || root.Name.LocalName != "Index")
|
|
||||||
return entries;
|
|
||||||
|
|
||||||
foreach (var entryEl in root.Elements("Entry"))
|
|
||||||
{
|
{
|
||||||
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,
|
var value = tag.Groups["value"].Value;
|
||||||
Method = int.TryParse(entryEl.Element("Method")?.Value, out var m) ? m : 0,
|
switch (tag.Groups["name"].Value)
|
||||||
Length = uint.TryParse(entryEl.Element("Len")?.Value, out var l) ? l : 0,
|
{
|
||||||
CompressedLength = uint.TryParse(entryEl.Element("CompLen")?.Value, out var cl) ? cl : 0,
|
case "ID":
|
||||||
Offset = long.TryParse(entryEl.Element("Offset")?.Value, out var o) ? o : 0
|
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);
|
entries.Add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,36 +1,62 @@
|
|||||||
using System.Xml.Linq;
|
using System.Text;
|
||||||
using GsaEditor.Core.Models;
|
using GsaEditor.Core.Models;
|
||||||
|
|
||||||
namespace GsaEditor.Core.IO;
|
namespace GsaEditor.Core.IO;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes a .idx NML index file from a <see cref="GsaArchive"/>.
|
/// Writes a .idx NML index file from a <see cref="GsaArchive"/>.
|
||||||
/// The index file is XML-compatible and lists each entry with its alias, compression method,
|
/// The NML format is the engine's own tag syntax (NOT standard XML):
|
||||||
/// sizes, and data offset for fast lookup without a full sequential scan.
|
/// <code>
|
||||||
|
/// <Version=1.0>
|
||||||
|
/// <Index=
|
||||||
|
/// <Entry=
|
||||||
|
/// <ID="path/in/archive">
|
||||||
|
/// <CompLen=123>
|
||||||
|
/// <Len=456>
|
||||||
|
/// <Offset=789>
|
||||||
|
/// <Method="Zlib">
|
||||||
|
/// >
|
||||||
|
/// >
|
||||||
|
/// </code>
|
||||||
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class GsaIndexWriter
|
public static class GsaIndexWriter
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private const string BootstrapAlias = "bootstrap.txt";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the index file for the given archive to the specified path.
|
/// Writes the index file for the given archive to the specified path.
|
||||||
|
/// Entry offsets must be up to date (i.e. call after <see cref="GsaWriter.Write(GsaArchive, string)"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">The archive whose entries will be indexed.</param>
|
/// <param name="archive">The archive whose entries will be indexed.</param>
|
||||||
/// <param name="filePath">The output .idx file path.</param>
|
/// <param name="filePath">The output .idx file path.</param>
|
||||||
public static void Write(GsaArchive archive, string filePath)
|
public static void Write(GsaArchive archive, string filePath)
|
||||||
{
|
{
|
||||||
var doc = new XDocument(
|
var sb = new StringBuilder();
|
||||||
new XElement("Index",
|
sb.Append("<Version=1.0>\n");
|
||||||
archive.Entries.Select(e =>
|
sb.Append("<Index=\n");
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
doc.Save(filePath);
|
foreach (var e in archive.Entries)
|
||||||
|
{
|
||||||
|
if (string.Equals(e.Alias, BootstrapAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sb.Append("\t<Entry=\n");
|
||||||
|
sb.Append("\t\t<ID=\"").Append(e.Alias).Append("\">\n");
|
||||||
|
sb.Append("\t\t<CompLen=").Append(e.CompressedLength).Append(">\n");
|
||||||
|
sb.Append("\t\t<Len=").Append(e.OriginalLength).Append(">\n");
|
||||||
|
sb.Append("\t\t<Offset=").Append(e.DataOffset).Append(">\n");
|
||||||
|
sb.Append("\t\t<Method=\"").Append(e.IsCompressed ? "Zlib" : "Raw").Append("\">\n");
|
||||||
|
sb.Append("\t>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(">\n");
|
||||||
|
|
||||||
|
File.WriteAllText(filePath, sb.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,12 @@ public static class GsaWriter
|
|||||||
/// <summary>Magic word for the NARD (enhanced) format.</summary>
|
/// <summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
private const uint MAGIC_NARD = 0x4E415244;
|
private const uint MAGIC_NARD = 0x4E415244;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private const uint TERMINATOR = 0xFFFFFFFF;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the archive to the specified file path.
|
/// Writes the archive to the specified file path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -53,6 +59,11 @@ public static class GsaWriter
|
|||||||
{
|
{
|
||||||
WriteEntry(writer, entry, archive);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -67,7 +67,9 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
||||||
<summary>
|
<summary>
|
||||||
Parses .idx NML index files (XML-compatible format) into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
Parses .idx NML index files into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
||||||
|
NML is the engine's own tag syntax (<Name=value> blocks, see <see cref="T:GsaEditor.Core.IO.GsaIndexWriter"/>),
|
||||||
|
not standard XML.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
||||||
@ -80,13 +82,33 @@
|
|||||||
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
||||||
<summary>
|
<summary>
|
||||||
Writes a .idx NML index file from a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
Writes a .idx NML index file from a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
||||||
The index file is XML-compatible and lists each entry with its alias, compression method,
|
The NML format is the engine's own tag syntax (NOT standard XML):
|
||||||
sizes, and data offset for fast lookup without a full sequential scan.
|
<code>
|
||||||
|
<Version=1.0>
|
||||||
|
<Index=
|
||||||
|
<Entry=
|
||||||
|
<ID="path/in/archive">
|
||||||
|
<CompLen=123>
|
||||||
|
<Len=456>
|
||||||
|
<Offset=789>
|
||||||
|
<Method="Zlib">
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</code>
|
||||||
|
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.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaIndexWriter.BootstrapAlias">
|
||||||
|
<summary>
|
||||||
|
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.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
Writes the index file for the given archive to the specified path.
|
Writes the index file for the given archive to the specified path.
|
||||||
|
Entry offsets must be up to date (i.e. call after <see cref="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)"/>).
|
||||||
</summary>
|
</summary>
|
||||||
<param name="archive">The archive whose entries will be indexed.</param>
|
<param name="archive">The archive whose entries will be indexed.</param>
|
||||||
<param name="filePath">The output .idx file path.</param>
|
<param name="filePath">The output .idx file path.</param>
|
||||||
@ -149,6 +171,12 @@
|
|||||||
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
||||||
<summary>Magic word for the NARD (enhanced) format.</summary>
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.TERMINATOR">
|
||||||
|
<summary>
|
||||||
|
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.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
Writes the archive to the specified file path.
|
Writes the archive to the specified file path.
|
||||||
|
|||||||
@ -13,10 +13,10 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaEditor.Core")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaEditor.Core")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[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.AssemblyProductAttribute("GsaEditor.Core")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GsaEditor.Core")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GsaEditor.Core")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
// Généré par la classe MSBuild WriteCodeFragment.
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
27416b735d6b5e6056e4f76966d345ebebb5a18e412e4170cdbcf381f9ab7092
|
24bad01704fb232d5dd4f84bfcb34e9e1b11dc1475b9ee697c24bd2eb1122246
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -67,7 +67,9 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
||||||
<summary>
|
<summary>
|
||||||
Parses .idx NML index files (XML-compatible format) into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
Parses .idx NML index files into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
||||||
|
NML is the engine's own tag syntax (<Name=value> blocks, see <see cref="T:GsaEditor.Core.IO.GsaIndexWriter"/>),
|
||||||
|
not standard XML.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
||||||
@ -80,13 +82,33 @@
|
|||||||
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
||||||
<summary>
|
<summary>
|
||||||
Writes a .idx NML index file from a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
Writes a .idx NML index file from a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
||||||
The index file is XML-compatible and lists each entry with its alias, compression method,
|
The NML format is the engine's own tag syntax (NOT standard XML):
|
||||||
sizes, and data offset for fast lookup without a full sequential scan.
|
<code>
|
||||||
|
<Version=1.0>
|
||||||
|
<Index=
|
||||||
|
<Entry=
|
||||||
|
<ID="path/in/archive">
|
||||||
|
<CompLen=123>
|
||||||
|
<Len=456>
|
||||||
|
<Offset=789>
|
||||||
|
<Method="Zlib">
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</code>
|
||||||
|
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.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaIndexWriter.BootstrapAlias">
|
||||||
|
<summary>
|
||||||
|
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.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
Writes the index file for the given archive to the specified path.
|
Writes the index file for the given archive to the specified path.
|
||||||
|
Entry offsets must be up to date (i.e. call after <see cref="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)"/>).
|
||||||
</summary>
|
</summary>
|
||||||
<param name="archive">The archive whose entries will be indexed.</param>
|
<param name="archive">The archive whose entries will be indexed.</param>
|
||||||
<param name="filePath">The output .idx file path.</param>
|
<param name="filePath">The output .idx file path.</param>
|
||||||
@ -149,6 +171,12 @@
|
|||||||
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
||||||
<summary>Magic word for the NARD (enhanced) format.</summary>
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.TERMINATOR">
|
||||||
|
<summary>
|
||||||
|
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.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
Writes the archive to the specified file path.
|
Writes the archive to the specified file path.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -13,6 +13,9 @@ public partial class EntryTreeNodeViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isExpanded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Full relative path of this node within the archive (using '/' separator).
|
/// Full relative path of this node within the archive (using '/' separator).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
@ -84,8 +85,24 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isEditingText;
|
private bool _isEditingText;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? _searchQuery;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _searchInContents;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? _searchStatus;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private SearchResultViewModel? _selectedSearchResult;
|
||||||
|
|
||||||
public ObservableCollection<EntryTreeNodeViewModel> TreeRoots { get; } = new();
|
public ObservableCollection<EntryTreeNodeViewModel> TreeRoots { get; } = new();
|
||||||
|
|
||||||
|
public ObservableCollection<SearchResultViewModel> SearchResults { get; } = new();
|
||||||
|
|
||||||
|
public bool HasSearchResults => SearchResults.Count > 0;
|
||||||
|
|
||||||
// --- Derived properties ---
|
// --- Derived properties ---
|
||||||
|
|
||||||
public string Title => ArchivePath != null
|
public string Title => ArchivePath != null
|
||||||
@ -255,6 +272,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
_archive = archive;
|
_archive = archive;
|
||||||
ArchivePath = path;
|
ArchivePath = path;
|
||||||
IsDirty = false;
|
IsDirty = false;
|
||||||
|
SearchQuery = null;
|
||||||
|
ClearSearchResults();
|
||||||
BuildTree();
|
BuildTree();
|
||||||
NotifyStatusChanged();
|
NotifyStatusChanged();
|
||||||
}
|
}
|
||||||
@ -347,6 +366,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
TreeRoots.Clear();
|
TreeRoots.Clear();
|
||||||
SelectedEntry = null;
|
SelectedEntry = null;
|
||||||
SelectedNode = null;
|
SelectedNode = null;
|
||||||
|
SearchQuery = null;
|
||||||
|
ClearSearchResults();
|
||||||
ClearPreview();
|
ClearPreview();
|
||||||
NotifyStatusChanged();
|
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<SearchResultViewModel>();
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects the tree node matching the given alias, expanding parent folders along the way.
|
||||||
|
/// </summary>
|
||||||
|
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
|
// About
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
19
GsaEditor/ViewModels/SearchResultViewModel.cs
Normal file
19
GsaEditor/ViewModels/SearchResultViewModel.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single search hit: a file name match, or a matching line inside a file.
|
||||||
|
/// </summary>
|
||||||
|
public class SearchResultViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Alias of the matching archive entry.
|
||||||
|
/// </summary>
|
||||||
|
public string Alias { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match context for content searches (e.g. "line 12: ..."). Empty for name matches.
|
||||||
|
/// </summary>
|
||||||
|
public string Detail { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public bool HasDetail => Detail.Length > 0;
|
||||||
|
}
|
||||||
@ -93,10 +93,61 @@
|
|||||||
<TextBlock DockPanel.Dock="Top" Text="Archive Contents"
|
<TextBlock DockPanel.Dock="Top" Text="Archive Contents"
|
||||||
FontWeight="SemiBold" Padding="8,6" FontSize="13"
|
FontWeight="SemiBold" Padding="8,6" FontSize="13"
|
||||||
Background="#EEEEEE"/>
|
Background="#EEEEEE"/>
|
||||||
|
|
||||||
|
<!-- Search area -->
|
||||||
|
<StackPanel DockPanel.Dock="Top" Margin="6,6,6,0" Spacing="4"
|
||||||
|
IsEnabled="{Binding HasArchive}">
|
||||||
|
<TextBox Text="{Binding SearchQuery, Mode=TwoWay}"
|
||||||
|
Watermark="Search...">
|
||||||
|
<TextBox.KeyBindings>
|
||||||
|
<KeyBinding Gesture="Enter" Command="{Binding SearchCommand}"/>
|
||||||
|
</TextBox.KeyBindings>
|
||||||
|
</TextBox>
|
||||||
|
<DockPanel>
|
||||||
|
<Button DockPanel.Dock="Right" Content="Search"
|
||||||
|
Command="{Binding SearchCommand}"
|
||||||
|
FontSize="12" Padding="8,3"
|
||||||
|
IsVisible="{Binding SearchInContents}"/>
|
||||||
|
<CheckBox Content="In contents (regex)"
|
||||||
|
IsChecked="{Binding SearchInContents, Mode=TwoWay}"
|
||||||
|
FontSize="12"/>
|
||||||
|
</DockPanel>
|
||||||
|
<TextBlock Text="{Binding SearchStatus}"
|
||||||
|
FontSize="11" Foreground="#777777"
|
||||||
|
IsVisible="{Binding SearchStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Search results -->
|
||||||
|
<ListBox DockPanel.Dock="Top"
|
||||||
|
ItemsSource="{Binding SearchResults}"
|
||||||
|
SelectedItem="{Binding SelectedSearchResult, Mode=TwoWay}"
|
||||||
|
IsVisible="{Binding HasSearchResults}"
|
||||||
|
MaxHeight="220" Margin="6,4,6,4"
|
||||||
|
BorderThickness="1" BorderBrush="#DDDDDD">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="0,1">
|
||||||
|
<TextBlock Text="{Binding Alias}" FontSize="12"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<TextBlock Text="{Binding Detail}" FontSize="11"
|
||||||
|
Foreground="#777777"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
IsVisible="{Binding HasDetail}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
<TreeView Name="ArchiveTree"
|
<TreeView Name="ArchiveTree"
|
||||||
ItemsSource="{Binding TreeRoots}"
|
ItemsSource="{Binding TreeRoots}"
|
||||||
SelectionChanged="TreeView_SelectionChanged">
|
SelectionChanged="TreeView_SelectionChanged">
|
||||||
|
|
||||||
|
<TreeView.Styles>
|
||||||
|
<Style Selector="TreeViewItem">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||||
|
</Style>
|
||||||
|
</TreeView.Styles>
|
||||||
|
|
||||||
<TreeView.ContextMenu>
|
<TreeView.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<!-- File node operations -->
|
<!-- File node operations -->
|
||||||
@ -221,14 +272,21 @@
|
|||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- Hex Dump Preview -->
|
<!-- Hex Dump Preview -->
|
||||||
<TextBox IsVisible="{Binding IsHexPreview}"
|
<DockPanel IsVisible="{Binding IsHexPreview}">
|
||||||
Text="{Binding HexDumpText}"
|
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
|
||||||
IsReadOnly="True"
|
Spacing="5" Margin="10,6">
|
||||||
FontFamily="Consolas, Courier New, monospace"
|
<Button Content="Edit as Text"
|
||||||
FontSize="13"
|
Command="{Binding OpenAsTextCommand}"
|
||||||
AcceptsReturn="True"
|
IsVisible="{Binding HasSelectedEntry}"/>
|
||||||
TextWrapping="NoWrap"
|
</StackPanel>
|
||||||
Margin="10"/>
|
<TextBox Text="{Binding HexDumpText}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
FontFamily="Consolas, Courier New, monospace"
|
||||||
|
FontSize="13"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
Margin="10,0,10,10"/>
|
||||||
|
</DockPanel>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -59,6 +59,19 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
ApplySyntaxHighlighting(vm);
|
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<TreeView>("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)
|
private void ApplySyntaxHighlighting(MainWindowViewModel vm)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -67,7 +67,9 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
||||||
<summary>
|
<summary>
|
||||||
Parses .idx NML index files (XML-compatible format) into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
Parses .idx NML index files into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
||||||
|
NML is the engine's own tag syntax (<Name=value> blocks, see <see cref="T:GsaEditor.Core.IO.GsaIndexWriter"/>),
|
||||||
|
not standard XML.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
||||||
@ -80,13 +82,33 @@
|
|||||||
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
||||||
<summary>
|
<summary>
|
||||||
Writes a .idx NML index file from a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
Writes a .idx NML index file from a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
||||||
The index file is XML-compatible and lists each entry with its alias, compression method,
|
The NML format is the engine's own tag syntax (NOT standard XML):
|
||||||
sizes, and data offset for fast lookup without a full sequential scan.
|
<code>
|
||||||
|
<Version=1.0>
|
||||||
|
<Index=
|
||||||
|
<Entry=
|
||||||
|
<ID="path/in/archive">
|
||||||
|
<CompLen=123>
|
||||||
|
<Len=456>
|
||||||
|
<Offset=789>
|
||||||
|
<Method="Zlib">
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</code>
|
||||||
|
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.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaIndexWriter.BootstrapAlias">
|
||||||
|
<summary>
|
||||||
|
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.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
Writes the index file for the given archive to the specified path.
|
Writes the index file for the given archive to the specified path.
|
||||||
|
Entry offsets must be up to date (i.e. call after <see cref="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)"/>).
|
||||||
</summary>
|
</summary>
|
||||||
<param name="archive">The archive whose entries will be indexed.</param>
|
<param name="archive">The archive whose entries will be indexed.</param>
|
||||||
<param name="filePath">The output .idx file path.</param>
|
<param name="filePath">The output .idx file path.</param>
|
||||||
@ -149,6 +171,12 @@
|
|||||||
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
||||||
<summary>Magic word for the NARD (enhanced) format.</summary>
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.TERMINATOR">
|
||||||
|
<summary>
|
||||||
|
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.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
Writes the archive to the specified file path.
|
Writes the archive to the specified file path.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
|||||||
aada552c8fd51710edc7ad0d0c49003781116f6ee0520c030a54982aadcd9736
|
eae8f0d208f1a483cabdff404aa7aa142ad05798b023a7ebd7aceb317939a4cf
|
||||||
|
|||||||
Binary file not shown.
@ -13,10 +13,10 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaEditor")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaEditor")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[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.AssemblyProductAttribute("GsaEditor")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GsaEditor")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GsaEditor")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
// Généré par la classe MSBuild WriteCodeFragment.
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
2ff94264e42d1924e3c6fd4243d279b8ea5cccc75f6ef146a562319dedcab0d3
|
27365fc2eec4d91990ba8ea7d96835a6e6291b2f5002237551bfa5d30ea5cac6
|
||||||
|
|||||||
Binary file not shown.
@ -1 +1 @@
|
|||||||
39a488a720409bb8a448fa3ac77a4fee498f2436a8299f5fec82e7f76321352a
|
9aadcb9c3b29da493ac1af6d86cd2bccb6af7d19576833358fa0b759709d9ce1
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
|||||||
7e7dc0454609f380d65d4e6792d0f069435ad58b608973047eb829ce8ac3f4b4
|
889fc292feef8bf02dfc3026d9915bb9baa08741fc71839c03938c2e64645e14
|
||||||
|
|||||||
Binary file not shown.
@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaViewer")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GsaViewer")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[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.AssemblyProductAttribute("GsaViewer")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GsaViewer")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GsaViewer")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
02bce5f0f4908bf808f9b59b3bc005e20eed825237a3bf3d65ea07bda1696b95
|
4a85afe2a84876aeeaf24b10d0721cd3b7f5dba258044299d3123346f674505b
|
||||||
|
|||||||
@ -24,5 +24,11 @@ build_property.EnableCodeStyleSeverity =
|
|||||||
[C:/Users/simulateur/Desktop/GsaViewer/App.axaml]
|
[C:/Users/simulateur/Desktop/GsaViewer/App.axaml]
|
||||||
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
|
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]
|
[C:/Users/simulateur/Desktop/GsaViewer/Views/MainWindow.axaml]
|
||||||
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
|
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
|
||||||
|
|||||||
Reference in New Issue
Block a user