Add project restore files and NuGet cache for GsaViewer
- Created project.nuget.cache to store NuGet package cache information. - Added project.packagespec.json to define project restore settings and dependencies. - Included rider.project.restore.info for Rider IDE integration.
This commit is contained in:
13
.idea/.idea.GsaViewer/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.GsaViewer/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.GsaViewer.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
10
.idea/.idea.GsaViewer/.idea/avalonia.xml
generated
Normal file
10
.idea/.idea.GsaViewer/.idea/avalonia.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AvaloniaProject">
|
||||||
|
<option name="projectPerEditor">
|
||||||
|
<map>
|
||||||
|
<entry key="Views/MainWindow.axaml" value="GsaViewer.csproj" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.agent.xml
generated
Normal file
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.agent.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.ask.xml
generated
Normal file
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.ask.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AskMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.edit.xml
generated
Normal file
6
.idea/.idea.GsaViewer/.idea/copilot.data.migration.edit.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EditMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/.idea.GsaViewer/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.GsaViewer/.idea/encodings.xml
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
8
.idea/.idea.GsaViewer/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.GsaViewer/.idea/indexLayout.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/.idea.GsaViewer/.idea/material_theme_project_new.xml
generated
Normal file
10
.idea/.idea.GsaViewer/.idea/material_theme_project_new.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MaterialThemeProjectNewConfig">
|
||||||
|
<option name="metadata">
|
||||||
|
<MTProjectMetadataState>
|
||||||
|
<option name="userId" value="24a2abbf:19d72afb14b:-7ffd" />
|
||||||
|
</MTProjectMetadataState>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
App.axaml
Normal file
10
App.axaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="GsaViewer.App"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
||||||
47
App.axaml.cs
Normal file
47
App.axaml.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Data.Core;
|
||||||
|
using Avalonia.Data.Core.Plugins;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using GsaViewer.ViewModels;
|
||||||
|
using GsaViewer.Views;
|
||||||
|
|
||||||
|
namespace GsaViewer;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
|
||||||
|
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
|
||||||
|
DisableAvaloniaDataAnnotationValidation();
|
||||||
|
desktop.MainWindow = new MainWindow
|
||||||
|
{
|
||||||
|
DataContext = new MainWindowViewModel(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableAvaloniaDataAnnotationValidation()
|
||||||
|
{
|
||||||
|
// Get an array of plugins to remove
|
||||||
|
var dataValidationPluginsToRemove =
|
||||||
|
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
|
||||||
|
|
||||||
|
// remove each entry found
|
||||||
|
foreach (var plugin in dataValidationPluginsToRemove)
|
||||||
|
{
|
||||||
|
BindingPlugins.DataValidators.Remove(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Assets/avalonia-logo.ico
Normal file
BIN
Assets/avalonia-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
88
GsaEditor.Core/Compression/ZlibHelper.cs
Normal file
88
GsaEditor.Core/Compression/ZlibHelper.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace GsaEditor.Core.Compression;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides zlib compression and decompression utilities for GSA archive entries.
|
||||||
|
/// The GSA format stores zlib-wrapped data: 2-byte header (CMF+FLG) + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
/// This helper skips the 2-byte zlib header and uses <see cref="DeflateStream"/> for the core deflate data.
|
||||||
|
/// </summary>
|
||||||
|
public static class ZlibHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Decompresses zlib-wrapped data into a byte array of the specified original size.
|
||||||
|
/// Skips the 2-byte zlib header before feeding data to <see cref="DeflateStream"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compressedData">The zlib-wrapped compressed data (header + deflate + checksum).</param>
|
||||||
|
/// <param name="originalLength">The expected decompressed size in bytes.</param>
|
||||||
|
/// <returns>The decompressed byte array.</returns>
|
||||||
|
/// <exception cref="InvalidDataException">Thrown when decompression fails or data is corrupt.</exception>
|
||||||
|
public static byte[] Decompress(byte[] compressedData, uint originalLength)
|
||||||
|
{
|
||||||
|
if (compressedData.Length < 2)
|
||||||
|
throw new InvalidDataException("Compressed data is too short to contain a zlib header.");
|
||||||
|
|
||||||
|
// Skip the 2-byte zlib header (typically 0x78 0x9C for default compression)
|
||||||
|
using var ms = new MemoryStream(compressedData, 2, compressedData.Length - 2);
|
||||||
|
using var deflate = new DeflateStream(ms, CompressionMode.Decompress);
|
||||||
|
|
||||||
|
var result = new byte[originalLength];
|
||||||
|
int totalRead = 0;
|
||||||
|
while (totalRead < result.Length)
|
||||||
|
{
|
||||||
|
int bytesRead = deflate.Read(result, totalRead, result.Length - totalRead);
|
||||||
|
if (bytesRead == 0)
|
||||||
|
break;
|
||||||
|
totalRead += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The uncompressed data to compress.</param>
|
||||||
|
/// <returns>The zlib-wrapped compressed byte array.</returns>
|
||||||
|
public static byte[] Compress(byte[] data)
|
||||||
|
{
|
||||||
|
using var output = new MemoryStream();
|
||||||
|
|
||||||
|
// Write zlib header: CMF=0x78 (deflate method, 32K window), FLG=0x9C (default level, valid check bits)
|
||||||
|
output.WriteByte(0x78);
|
||||||
|
output.WriteByte(0x9C);
|
||||||
|
|
||||||
|
using (var deflate = new DeflateStream(output, CompressionLevel.Optimal, leaveOpen: true))
|
||||||
|
{
|
||||||
|
deflate.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Adler-32 checksum in big-endian byte order
|
||||||
|
uint adler = ComputeAdler32(data);
|
||||||
|
output.WriteByte((byte)(adler >> 24));
|
||||||
|
output.WriteByte((byte)(adler >> 16));
|
||||||
|
output.WriteByte((byte)(adler >> 8));
|
||||||
|
output.WriteByte((byte)adler);
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the Adler-32 checksum of the given data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to checksum.</param>
|
||||||
|
/// <returns>The 32-bit Adler-32 checksum value.</returns>
|
||||||
|
private static uint ComputeAdler32(byte[] data)
|
||||||
|
{
|
||||||
|
const uint MOD_ADLER = 65521;
|
||||||
|
uint a = 1, b = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
a = (a + data[i]) % MOD_ADLER;
|
||||||
|
b = (b + a) % MOD_ADLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (b << 16) | a;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
GsaEditor.Core/GsaEditor.Core.csproj
Normal file
8
GsaEditor.Core/GsaEditor.Core.csproj
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
70
GsaEditor.Core/IO/GsaIndexReader.cs
Normal file
70
GsaEditor.Core/IO/GsaIndexReader.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace GsaEditor.Core.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single entry parsed from a .idx NML index file.
|
||||||
|
/// </summary>
|
||||||
|
public class GsaIndexEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The file alias (relative path) of the entry.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compression method: 0 = raw, 1 = zlib.
|
||||||
|
/// </summary>
|
||||||
|
public int Method { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Original (decompressed) size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public uint Length { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compressed size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public uint CompressedLength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Byte offset of the data block within the archive.
|
||||||
|
/// </summary>
|
||||||
|
public long Offset { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses .idx NML index files (XML-compatible format) into a list of <see cref="GsaIndexEntry"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class GsaIndexReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads index entries from the specified .idx file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The path to the .idx file.</param>
|
||||||
|
/// <returns>A list of parsed index entries.</returns>
|
||||||
|
public static List<GsaIndexEntry> Read(string filePath)
|
||||||
|
{
|
||||||
|
var doc = XDocument.Load(filePath);
|
||||||
|
var entries = new List<GsaIndexEntry>();
|
||||||
|
|
||||||
|
var root = doc.Root;
|
||||||
|
if (root == null || root.Name.LocalName != "Index")
|
||||||
|
return entries;
|
||||||
|
|
||||||
|
foreach (var entryEl in root.Elements("Entry"))
|
||||||
|
{
|
||||||
|
var entry = new GsaIndexEntry
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
entries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
GsaEditor.Core/IO/GsaIndexWriter.cs
Normal file
36
GsaEditor.Core/IO/GsaIndexWriter.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Xml.Linq;
|
||||||
|
using GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
namespace GsaEditor.Core.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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,
|
||||||
|
/// sizes, and data offset for fast lookup without a full sequential scan.
|
||||||
|
/// </summary>
|
||||||
|
public static class GsaIndexWriter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the index file for the given archive to the specified path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">The archive whose entries will be indexed.</param>
|
||||||
|
/// <param name="filePath">The output .idx file path.</param>
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
doc.Save(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
163
GsaEditor.Core/IO/GsaReader.cs
Normal file
163
GsaEditor.Core/IO/GsaReader.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
using System.Text;
|
||||||
|
using GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
namespace GsaEditor.Core.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a .gsa binary archive from a stream or file and produces a <see cref="GsaArchive"/>.
|
||||||
|
/// Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
/// </summary>
|
||||||
|
public static class GsaReader
|
||||||
|
{
|
||||||
|
/// <summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
private const uint MAGIC_NARC = 0x4E415243;
|
||||||
|
|
||||||
|
/// <summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
private const uint MAGIC_NARD = 0x4E415244;
|
||||||
|
|
||||||
|
/// <summary>Maximum allowed alias length in characters.</summary>
|
||||||
|
private const int MAX_ALIAS_LENGTH = 511;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a GSA archive from the specified file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The path to the .gsa archive file.</param>
|
||||||
|
/// <returns>A fully populated <see cref="GsaArchive"/>.</returns>
|
||||||
|
/// <exception cref="InvalidDataException">Thrown if the file has an invalid magic word.</exception>
|
||||||
|
public static GsaArchive Read(string filePath)
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(filePath);
|
||||||
|
return Read(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a GSA archive from a stream by performing a sequential scan of all entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A readable, seekable stream positioned at the start of the archive.</param>
|
||||||
|
/// <returns>A fully populated <see cref="GsaArchive"/>.</returns>
|
||||||
|
/// <exception cref="InvalidDataException">Thrown if the stream has an invalid magic word.</exception>
|
||||||
|
public static GsaArchive Read(Stream stream)
|
||||||
|
{
|
||||||
|
using var reader = new BinaryReader(stream, Encoding.ASCII, leaveOpen: true);
|
||||||
|
var archive = new GsaArchive();
|
||||||
|
|
||||||
|
// Read global header
|
||||||
|
uint magic = reader.ReadUInt32();
|
||||||
|
if (magic == MAGIC_NARC)
|
||||||
|
{
|
||||||
|
archive.Format = GsaFormat.NARC;
|
||||||
|
}
|
||||||
|
else if (magic == MAGIC_NARD)
|
||||||
|
{
|
||||||
|
archive.Format = GsaFormat.NARD;
|
||||||
|
archive.OffsetPadding = reader.ReadInt32();
|
||||||
|
archive.SizePadding = reader.ReadInt32();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidDataException(
|
||||||
|
$"Invalid GSA magic word: 0x{magic:X8}. Expected NARC (0x{MAGIC_NARC:X8}) or NARD (0x{MAGIC_NARD:X8}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequentially read all entries until EOF
|
||||||
|
while (stream.Position < stream.Length)
|
||||||
|
{
|
||||||
|
var entry = ReadEntry(reader, archive);
|
||||||
|
if (entry == null)
|
||||||
|
break;
|
||||||
|
archive.Entries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return archive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a single file entry from the current stream position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">The binary reader positioned at the start of an entry.</param>
|
||||||
|
/// <param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
/// <returns>A populated <see cref="GsaEntry"/>, or null if no more entries can be read.</returns>
|
||||||
|
private static GsaEntry? ReadEntry(BinaryReader reader, GsaArchive archive)
|
||||||
|
{
|
||||||
|
var stream = reader.BaseStream;
|
||||||
|
|
||||||
|
if (stream.Position >= stream.Length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// NARD: align stream position before reading entry header
|
||||||
|
if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
|
||||||
|
AlignStream(stream, archive.OffsetPadding);
|
||||||
|
|
||||||
|
// Check if enough bytes remain for the alias length field
|
||||||
|
if (stream.Length - stream.Position < 4)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Read alias
|
||||||
|
uint aliasLength = reader.ReadUInt32();
|
||||||
|
if (aliasLength == 0 || aliasLength > MAX_ALIAS_LENGTH)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (stream.Length - stream.Position < aliasLength)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
byte[] aliasBytes = reader.ReadBytes((int)aliasLength);
|
||||||
|
string alias = Encoding.ASCII.GetString(aliasBytes);
|
||||||
|
|
||||||
|
// Read compression flag (bit 0: 1=compressed, 0=raw)
|
||||||
|
byte compressionByte = reader.ReadByte();
|
||||||
|
bool isCompressed = (compressionByte & 0x01) != 0;
|
||||||
|
|
||||||
|
// Read original (decompressed) length
|
||||||
|
uint originalLength = reader.ReadUInt32();
|
||||||
|
|
||||||
|
// Read compressed length (only present if compressed)
|
||||||
|
uint compressedLength;
|
||||||
|
if (isCompressed)
|
||||||
|
{
|
||||||
|
compressedLength = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
compressedLength = originalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NARD: align stream position before reading the data block
|
||||||
|
if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
|
||||||
|
AlignStream(stream, archive.OffsetPadding);
|
||||||
|
|
||||||
|
// Read raw data
|
||||||
|
long dataOffset = stream.Position;
|
||||||
|
uint dataSize = isCompressed ? compressedLength : originalLength;
|
||||||
|
|
||||||
|
if (stream.Length - stream.Position < dataSize)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
byte[] rawData = reader.ReadBytes((int)dataSize);
|
||||||
|
|
||||||
|
return new GsaEntry
|
||||||
|
{
|
||||||
|
Alias = alias,
|
||||||
|
IsCompressed = isCompressed,
|
||||||
|
OriginalLength = originalLength,
|
||||||
|
CompressedLength = compressedLength,
|
||||||
|
RawData = rawData,
|
||||||
|
DataOffset = dataOffset
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances the stream position to the next aligned boundary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to align.</param>
|
||||||
|
/// <param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
private static void AlignStream(Stream stream, int alignment)
|
||||||
|
{
|
||||||
|
if (alignment <= 1) return;
|
||||||
|
long pos = stream.Position;
|
||||||
|
long remainder = pos % alignment;
|
||||||
|
if (remainder != 0)
|
||||||
|
{
|
||||||
|
stream.Position = pos + (alignment - remainder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
GsaEditor.Core/IO/GsaWriter.cs
Normal file
114
GsaEditor.Core/IO/GsaWriter.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using System.Text;
|
||||||
|
using GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
namespace GsaEditor.Core.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a <see cref="GsaArchive"/> to a binary .gsa stream or file.
|
||||||
|
/// Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
/// </summary>
|
||||||
|
public static class GsaWriter
|
||||||
|
{
|
||||||
|
/// <summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
private const uint MAGIC_NARC = 0x4E415243;
|
||||||
|
|
||||||
|
/// <summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
private const uint MAGIC_NARD = 0x4E415244;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the archive to the specified file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">The archive to write.</param>
|
||||||
|
/// <param name="filePath">The output file path.</param>
|
||||||
|
public static void Write(GsaArchive archive, string filePath)
|
||||||
|
{
|
||||||
|
using var stream = File.Create(filePath);
|
||||||
|
Write(archive, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the archive to the given stream.
|
||||||
|
/// Each entry's <see cref="GsaEntry.DataOffset"/> is updated to reflect the new position in the output.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">The archive to write.</param>
|
||||||
|
/// <param name="stream">A writable stream.</param>
|
||||||
|
public static void Write(GsaArchive archive, Stream stream)
|
||||||
|
{
|
||||||
|
using var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true);
|
||||||
|
|
||||||
|
// Write global header
|
||||||
|
if (archive.Format == GsaFormat.NARC)
|
||||||
|
{
|
||||||
|
writer.Write(MAGIC_NARC);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(MAGIC_NARD);
|
||||||
|
writer.Write(archive.OffsetPadding);
|
||||||
|
writer.Write(archive.SizePadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write each entry sequentially
|
||||||
|
foreach (var entry in archive.Entries)
|
||||||
|
{
|
||||||
|
WriteEntry(writer, entry, archive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a single file entry at the current stream position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The binary writer.</param>
|
||||||
|
/// <param name="entry">The entry to write.</param>
|
||||||
|
/// <param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
private static void WriteEntry(BinaryWriter writer, GsaEntry entry, GsaArchive archive)
|
||||||
|
{
|
||||||
|
// NARD: align stream position before writing entry header
|
||||||
|
if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
|
||||||
|
WritePadding(writer, archive.OffsetPadding);
|
||||||
|
|
||||||
|
// Write alias
|
||||||
|
byte[] aliasBytes = Encoding.ASCII.GetBytes(entry.Alias);
|
||||||
|
writer.Write((uint)aliasBytes.Length);
|
||||||
|
writer.Write(aliasBytes);
|
||||||
|
|
||||||
|
// Write compression flag
|
||||||
|
writer.Write((byte)(entry.IsCompressed ? 1 : 0));
|
||||||
|
|
||||||
|
// Write original (decompressed) length
|
||||||
|
writer.Write(entry.OriginalLength);
|
||||||
|
|
||||||
|
// Write compressed length (only if compressed)
|
||||||
|
if (entry.IsCompressed)
|
||||||
|
{
|
||||||
|
writer.Write(entry.CompressedLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NARD: align stream position before writing the data block
|
||||||
|
if (archive.Format == GsaFormat.NARD && archive.OffsetPadding > 0)
|
||||||
|
WritePadding(writer, archive.OffsetPadding);
|
||||||
|
|
||||||
|
// Update the entry's data offset to reflect the new position
|
||||||
|
entry.DataOffset = writer.BaseStream.Position;
|
||||||
|
|
||||||
|
// Write raw data
|
||||||
|
writer.Write(entry.RawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes zero-bytes to pad the stream to the next aligned boundary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The binary writer.</param>
|
||||||
|
/// <param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
private static void WritePadding(BinaryWriter writer, int alignment)
|
||||||
|
{
|
||||||
|
if (alignment <= 1) return;
|
||||||
|
long pos = writer.BaseStream.Position;
|
||||||
|
long remainder = pos % alignment;
|
||||||
|
if (remainder != 0)
|
||||||
|
{
|
||||||
|
int paddingBytes = (int)(alignment - remainder);
|
||||||
|
writer.Write(new byte[paddingBytes]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
GsaEditor.Core/Models/GsaArchive.cs
Normal file
27
GsaEditor.Core/Models/GsaArchive.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a loaded GSA archive with its global header information and file entries.
|
||||||
|
/// </summary>
|
||||||
|
public class GsaArchive
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The format variant of this archive (NARC or NARD).
|
||||||
|
/// </summary>
|
||||||
|
public GsaFormat Format { get; set; } = GsaFormat.NARC;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (NARD only) Byte alignment for data offsets. Zero or unused for NARC.
|
||||||
|
/// </summary>
|
||||||
|
public int OffsetPadding { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (NARD only) Byte alignment for data sizes. Zero or unused for NARC.
|
||||||
|
/// </summary>
|
||||||
|
public int SizePadding { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ordered list of file entries in the archive.
|
||||||
|
/// </summary>
|
||||||
|
public List<GsaEntry> Entries { get; set; } = new();
|
||||||
|
}
|
||||||
77
GsaEditor.Core/Models/GsaEntry.cs
Normal file
77
GsaEditor.Core/Models/GsaEntry.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using GsaEditor.Core.Compression;
|
||||||
|
|
||||||
|
namespace GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single file entry within a GSA archive.
|
||||||
|
/// </summary>
|
||||||
|
public class GsaEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Relative path (alias) of the file within the archive, using '/' as separator.
|
||||||
|
/// Maximum 511 characters.
|
||||||
|
/// </summary>
|
||||||
|
public string Alias { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this entry's data is zlib-compressed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompressed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Original (decompressed) size of the file in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public uint OriginalLength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compressed size of the file in bytes. Equal to <see cref="OriginalLength"/> if not compressed.
|
||||||
|
/// </summary>
|
||||||
|
public uint CompressedLength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raw data as stored in the archive (compressed bytes if <see cref="IsCompressed"/> is true).
|
||||||
|
/// </summary>
|
||||||
|
public byte[] RawData { get; set; } = Array.Empty<byte>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Byte offset of this entry's data block within the archive file.
|
||||||
|
/// </summary>
|
||||||
|
public long DataOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the decompressed data for this entry.
|
||||||
|
/// If the entry is not compressed, returns <see cref="RawData"/> directly.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The decompressed file content.</returns>
|
||||||
|
public byte[] GetDecompressedData()
|
||||||
|
{
|
||||||
|
if (!IsCompressed)
|
||||||
|
return RawData;
|
||||||
|
|
||||||
|
return ZlibHelper.Decompress(RawData, OriginalLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the entry data from uncompressed bytes, optionally compressing with zlib.
|
||||||
|
/// Updates <see cref="OriginalLength"/>, <see cref="CompressedLength"/>,
|
||||||
|
/// <see cref="IsCompressed"/>, and <see cref="RawData"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uncompressedData">The uncompressed file content.</param>
|
||||||
|
/// <param name="compress">Whether to compress the data with zlib.</param>
|
||||||
|
public void SetData(byte[] uncompressedData, bool compress)
|
||||||
|
{
|
||||||
|
OriginalLength = (uint)uncompressedData.Length;
|
||||||
|
IsCompressed = compress;
|
||||||
|
|
||||||
|
if (compress)
|
||||||
|
{
|
||||||
|
RawData = ZlibHelper.Compress(uncompressedData);
|
||||||
|
CompressedLength = (uint)RawData.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RawData = uncompressedData;
|
||||||
|
CompressedLength = OriginalLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
GsaEditor.Core/Models/GsaFormat.cs
Normal file
18
GsaEditor.Core/Models/GsaFormat.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies the archive format variant.
|
||||||
|
/// </summary>
|
||||||
|
public enum GsaFormat
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy format with no padding support. Magic word = 0x4E415243 ("NARC").
|
||||||
|
/// </summary>
|
||||||
|
NARC,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enhanced format with offset and size padding for console DVD alignment.
|
||||||
|
/// Magic word = 0x4E415244 ("NARD").
|
||||||
|
/// </summary>
|
||||||
|
NARD
|
||||||
|
}
|
||||||
23
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.deps.json
Normal file
23
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.deps.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v8.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v8.0": {
|
||||||
|
"GsaEditor.Core/1.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"GsaEditor.Core.dll": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"GsaEditor.Core/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.dll
Normal file
BIN
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.dll
Normal file
Binary file not shown.
BIN
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.pdb
Normal file
BIN
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.pdb
Normal file
Binary file not shown.
276
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.xml
Normal file
276
GsaEditor.Core/bin/Debug/net8.0/GsaEditor.Core.xml
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<doc>
|
||||||
|
<assembly>
|
||||||
|
<name>GsaEditor.Core</name>
|
||||||
|
</assembly>
|
||||||
|
<members>
|
||||||
|
<member name="T:GsaEditor.Core.Compression.ZlibHelper">
|
||||||
|
<summary>
|
||||||
|
Provides zlib compression and decompression utilities for GSA archive entries.
|
||||||
|
The GSA format stores zlib-wrapped data: 2-byte header (CMF+FLG) + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
This helper skips the 2-byte zlib header and uses <see cref="T:System.IO.Compression.DeflateStream"/> for the core deflate data.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.Decompress(System.Byte[],System.UInt32)">
|
||||||
|
<summary>
|
||||||
|
Decompresses zlib-wrapped data into a byte array of the specified original size.
|
||||||
|
Skips the 2-byte zlib header before feeding data to <see cref="T:System.IO.Compression.DeflateStream"/>.
|
||||||
|
</summary>
|
||||||
|
<param name="compressedData">The zlib-wrapped compressed data (header + deflate + checksum).</param>
|
||||||
|
<param name="originalLength">The expected decompressed size in bytes.</param>
|
||||||
|
<returns>The decompressed byte array.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown when decompression fails or data is corrupt.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.Compress(System.Byte[])">
|
||||||
|
<summary>
|
||||||
|
Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
</summary>
|
||||||
|
<param name="data">The uncompressed data to compress.</param>
|
||||||
|
<returns>The zlib-wrapped compressed byte array.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.ComputeAdler32(System.Byte[])">
|
||||||
|
<summary>
|
||||||
|
Computes the Adler-32 checksum of the given data.
|
||||||
|
</summary>
|
||||||
|
<param name="data">The data to checksum.</param>
|
||||||
|
<returns>The 32-bit Adler-32 checksum value.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexEntry">
|
||||||
|
<summary>
|
||||||
|
Represents a single entry parsed from a .idx NML index file.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Id">
|
||||||
|
<summary>
|
||||||
|
The file alias (relative path) of the entry.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Method">
|
||||||
|
<summary>
|
||||||
|
Compression method: 0 = raw, 1 = zlib.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Length">
|
||||||
|
<summary>
|
||||||
|
Original (decompressed) size in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.CompressedLength">
|
||||||
|
<summary>
|
||||||
|
Compressed size in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Offset">
|
||||||
|
<summary>
|
||||||
|
Byte offset of the data block within the archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
||||||
|
<summary>
|
||||||
|
Parses .idx NML index files (XML-compatible format) into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
||||||
|
<summary>
|
||||||
|
Reads index entries from the specified .idx file.
|
||||||
|
</summary>
|
||||||
|
<param name="filePath">The path to the .idx file.</param>
|
||||||
|
<returns>A list of parsed index entries.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
||||||
|
<summary>
|
||||||
|
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,
|
||||||
|
sizes, and data offset for fast lookup without a full sequential scan.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
|
<summary>
|
||||||
|
Writes the index file for the given archive to the specified path.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive whose entries will be indexed.</param>
|
||||||
|
<param name="filePath">The output .idx file path.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaReader">
|
||||||
|
<summary>
|
||||||
|
Reads a .gsa binary archive from a stream or file and produces a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
||||||
|
Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAGIC_NARC">
|
||||||
|
<summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAGIC_NARD">
|
||||||
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAX_ALIAS_LENGTH">
|
||||||
|
<summary>Maximum allowed alias length in characters.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.Read(System.String)">
|
||||||
|
<summary>
|
||||||
|
Reads a GSA archive from the specified file path.
|
||||||
|
</summary>
|
||||||
|
<param name="filePath">The path to the .gsa archive file.</param>
|
||||||
|
<returns>A fully populated <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown if the file has an invalid magic word.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.Read(System.IO.Stream)">
|
||||||
|
<summary>
|
||||||
|
Reads a GSA archive from a stream by performing a sequential scan of all entries.
|
||||||
|
</summary>
|
||||||
|
<param name="stream">A readable, seekable stream positioned at the start of the archive.</param>
|
||||||
|
<returns>A fully populated <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown if the stream has an invalid magic word.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.ReadEntry(System.IO.BinaryReader,GsaEditor.Core.Models.GsaArchive)">
|
||||||
|
<summary>
|
||||||
|
Reads a single file entry from the current stream position.
|
||||||
|
</summary>
|
||||||
|
<param name="reader">The binary reader positioned at the start of an entry.</param>
|
||||||
|
<param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
<returns>A populated <see cref="T:GsaEditor.Core.Models.GsaEntry"/>, or null if no more entries can be read.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.AlignStream(System.IO.Stream,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Advances the stream position to the next aligned boundary.
|
||||||
|
</summary>
|
||||||
|
<param name="stream">The stream to align.</param>
|
||||||
|
<param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaWriter">
|
||||||
|
<summary>
|
||||||
|
Writes a <see cref="T:GsaEditor.Core.Models.GsaArchive"/> to a binary .gsa stream or file.
|
||||||
|
Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARC">
|
||||||
|
<summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
||||||
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
|
<summary>
|
||||||
|
Writes the archive to the specified file path.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive to write.</param>
|
||||||
|
<param name="filePath">The output file path.</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.IO.Stream)">
|
||||||
|
<summary>
|
||||||
|
Writes the archive to the given stream.
|
||||||
|
Each entry's <see cref="P:GsaEditor.Core.Models.GsaEntry.DataOffset"/> is updated to reflect the new position in the output.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive to write.</param>
|
||||||
|
<param name="stream">A writable stream.</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.WriteEntry(System.IO.BinaryWriter,GsaEditor.Core.Models.GsaEntry,GsaEditor.Core.Models.GsaArchive)">
|
||||||
|
<summary>
|
||||||
|
Writes a single file entry at the current stream position.
|
||||||
|
</summary>
|
||||||
|
<param name="writer">The binary writer.</param>
|
||||||
|
<param name="entry">The entry to write.</param>
|
||||||
|
<param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.WritePadding(System.IO.BinaryWriter,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Writes zero-bytes to pad the stream to the next aligned boundary.
|
||||||
|
</summary>
|
||||||
|
<param name="writer">The binary writer.</param>
|
||||||
|
<param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaArchive">
|
||||||
|
<summary>
|
||||||
|
Represents a loaded GSA archive with its global header information and file entries.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.Format">
|
||||||
|
<summary>
|
||||||
|
The format variant of this archive (NARC or NARD).
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.OffsetPadding">
|
||||||
|
<summary>
|
||||||
|
(NARD only) Byte alignment for data offsets. Zero or unused for NARC.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.SizePadding">
|
||||||
|
<summary>
|
||||||
|
(NARD only) Byte alignment for data sizes. Zero or unused for NARC.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.Entries">
|
||||||
|
<summary>
|
||||||
|
The ordered list of file entries in the archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaEntry">
|
||||||
|
<summary>
|
||||||
|
Represents a single file entry within a GSA archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.Alias">
|
||||||
|
<summary>
|
||||||
|
Relative path (alias) of the file within the archive, using '/' as separator.
|
||||||
|
Maximum 511 characters.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.IsCompressed">
|
||||||
|
<summary>
|
||||||
|
Whether this entry's data is zlib-compressed.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.OriginalLength">
|
||||||
|
<summary>
|
||||||
|
Original (decompressed) size of the file in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.CompressedLength">
|
||||||
|
<summary>
|
||||||
|
Compressed size of the file in bytes. Equal to <see cref="P:GsaEditor.Core.Models.GsaEntry.OriginalLength"/> if not compressed.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.RawData">
|
||||||
|
<summary>
|
||||||
|
Raw data as stored in the archive (compressed bytes if <see cref="P:GsaEditor.Core.Models.GsaEntry.IsCompressed"/> is true).
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.DataOffset">
|
||||||
|
<summary>
|
||||||
|
Byte offset of this entry's data block within the archive file.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Models.GsaEntry.GetDecompressedData">
|
||||||
|
<summary>
|
||||||
|
Returns the decompressed data for this entry.
|
||||||
|
If the entry is not compressed, returns <see cref="P:GsaEditor.Core.Models.GsaEntry.RawData"/> directly.
|
||||||
|
</summary>
|
||||||
|
<returns>The decompressed file content.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Models.GsaEntry.SetData(System.Byte[],System.Boolean)">
|
||||||
|
<summary>
|
||||||
|
Sets the entry data from uncompressed bytes, optionally compressing with zlib.
|
||||||
|
Updates <see cref="P:GsaEditor.Core.Models.GsaEntry.OriginalLength"/>, <see cref="P:GsaEditor.Core.Models.GsaEntry.CompressedLength"/>,
|
||||||
|
<see cref="P:GsaEditor.Core.Models.GsaEntry.IsCompressed"/>, and <see cref="P:GsaEditor.Core.Models.GsaEntry.RawData"/>.
|
||||||
|
</summary>
|
||||||
|
<param name="uncompressedData">The uncompressed file content.</param>
|
||||||
|
<param name="compress">Whether to compress the data with zlib.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaFormat">
|
||||||
|
<summary>
|
||||||
|
Identifies the archive format variant.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.Models.GsaFormat.NARC">
|
||||||
|
<summary>
|
||||||
|
Legacy format with no padding support. Magic word = 0x4E415243 ("NARC").
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.Models.GsaFormat.NARD">
|
||||||
|
<summary>
|
||||||
|
Enhanced format with offset and size padding for console DVD alignment.
|
||||||
|
Magic word = 0x4E415244 ("NARD").
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
</doc>
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System;
|
||||||
|
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")]
|
||||||
|
[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.
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
68387796343863a0e5c11cd0f612a1b364816551cf5816c6d556d410439a3710
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
is_global = true
|
||||||
|
build_property.TargetFramework = net8.0
|
||||||
|
build_property.TargetPlatformMinVersion =
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb =
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property.RootNamespace = GsaEditor.Core
|
||||||
|
build_property.ProjectDir = C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\
|
||||||
|
build_property.EnableComHosting =
|
||||||
|
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||||
|
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||||
|
build_property.EnableCodeStyleSeverity =
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
global using global::System;
|
||||||
|
global using global::System.Collections.Generic;
|
||||||
|
global using global::System.IO;
|
||||||
|
global using global::System.Linq;
|
||||||
|
global using global::System.Net.Http;
|
||||||
|
global using global::System.Threading;
|
||||||
|
global using global::System.Threading.Tasks;
|
||||||
BIN
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.assets.cache
Normal file
BIN
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.assets.cache
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
f57ab49a8539dcd5eaae9903c06c691e87a9ead2bd6a913638151d11a0b5901a
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\bin\Debug\net8.0\GsaEditor.Core.deps.json
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\bin\Debug\net8.0\GsaEditor.Core.dll
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\bin\Debug\net8.0\GsaEditor.Core.pdb
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\bin\Debug\net8.0\GsaEditor.Core.xml
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.GeneratedMSBuildEditorConfig.editorconfig
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.AssemblyInfoInputs.cache
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.AssemblyInfo.cs
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.csproj.CoreCompileInputs.cache
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.dll
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\refint\GsaEditor.Core.dll
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.xml
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\GsaEditor.Core.pdb
|
||||||
|
C:\Users\simulateur\Desktop\GsaViewer\GsaEditor.Core\obj\Debug\net8.0\ref\GsaEditor.Core.dll
|
||||||
BIN
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.dll
Normal file
BIN
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.dll
Normal file
Binary file not shown.
BIN
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.pdb
Normal file
BIN
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.pdb
Normal file
Binary file not shown.
276
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.xml
Normal file
276
GsaEditor.Core/obj/Debug/net8.0/GsaEditor.Core.xml
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<doc>
|
||||||
|
<assembly>
|
||||||
|
<name>GsaEditor.Core</name>
|
||||||
|
</assembly>
|
||||||
|
<members>
|
||||||
|
<member name="T:GsaEditor.Core.Compression.ZlibHelper">
|
||||||
|
<summary>
|
||||||
|
Provides zlib compression and decompression utilities for GSA archive entries.
|
||||||
|
The GSA format stores zlib-wrapped data: 2-byte header (CMF+FLG) + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
This helper skips the 2-byte zlib header and uses <see cref="T:System.IO.Compression.DeflateStream"/> for the core deflate data.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.Decompress(System.Byte[],System.UInt32)">
|
||||||
|
<summary>
|
||||||
|
Decompresses zlib-wrapped data into a byte array of the specified original size.
|
||||||
|
Skips the 2-byte zlib header before feeding data to <see cref="T:System.IO.Compression.DeflateStream"/>.
|
||||||
|
</summary>
|
||||||
|
<param name="compressedData">The zlib-wrapped compressed data (header + deflate + checksum).</param>
|
||||||
|
<param name="originalLength">The expected decompressed size in bytes.</param>
|
||||||
|
<returns>The decompressed byte array.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown when decompression fails or data is corrupt.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.Compress(System.Byte[])">
|
||||||
|
<summary>
|
||||||
|
Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
</summary>
|
||||||
|
<param name="data">The uncompressed data to compress.</param>
|
||||||
|
<returns>The zlib-wrapped compressed byte array.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.ComputeAdler32(System.Byte[])">
|
||||||
|
<summary>
|
||||||
|
Computes the Adler-32 checksum of the given data.
|
||||||
|
</summary>
|
||||||
|
<param name="data">The data to checksum.</param>
|
||||||
|
<returns>The 32-bit Adler-32 checksum value.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexEntry">
|
||||||
|
<summary>
|
||||||
|
Represents a single entry parsed from a .idx NML index file.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Id">
|
||||||
|
<summary>
|
||||||
|
The file alias (relative path) of the entry.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Method">
|
||||||
|
<summary>
|
||||||
|
Compression method: 0 = raw, 1 = zlib.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Length">
|
||||||
|
<summary>
|
||||||
|
Original (decompressed) size in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.CompressedLength">
|
||||||
|
<summary>
|
||||||
|
Compressed size in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Offset">
|
||||||
|
<summary>
|
||||||
|
Byte offset of the data block within the archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
||||||
|
<summary>
|
||||||
|
Parses .idx NML index files (XML-compatible format) into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
||||||
|
<summary>
|
||||||
|
Reads index entries from the specified .idx file.
|
||||||
|
</summary>
|
||||||
|
<param name="filePath">The path to the .idx file.</param>
|
||||||
|
<returns>A list of parsed index entries.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
||||||
|
<summary>
|
||||||
|
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,
|
||||||
|
sizes, and data offset for fast lookup without a full sequential scan.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
|
<summary>
|
||||||
|
Writes the index file for the given archive to the specified path.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive whose entries will be indexed.</param>
|
||||||
|
<param name="filePath">The output .idx file path.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaReader">
|
||||||
|
<summary>
|
||||||
|
Reads a .gsa binary archive from a stream or file and produces a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
||||||
|
Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAGIC_NARC">
|
||||||
|
<summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAGIC_NARD">
|
||||||
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAX_ALIAS_LENGTH">
|
||||||
|
<summary>Maximum allowed alias length in characters.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.Read(System.String)">
|
||||||
|
<summary>
|
||||||
|
Reads a GSA archive from the specified file path.
|
||||||
|
</summary>
|
||||||
|
<param name="filePath">The path to the .gsa archive file.</param>
|
||||||
|
<returns>A fully populated <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown if the file has an invalid magic word.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.Read(System.IO.Stream)">
|
||||||
|
<summary>
|
||||||
|
Reads a GSA archive from a stream by performing a sequential scan of all entries.
|
||||||
|
</summary>
|
||||||
|
<param name="stream">A readable, seekable stream positioned at the start of the archive.</param>
|
||||||
|
<returns>A fully populated <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown if the stream has an invalid magic word.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.ReadEntry(System.IO.BinaryReader,GsaEditor.Core.Models.GsaArchive)">
|
||||||
|
<summary>
|
||||||
|
Reads a single file entry from the current stream position.
|
||||||
|
</summary>
|
||||||
|
<param name="reader">The binary reader positioned at the start of an entry.</param>
|
||||||
|
<param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
<returns>A populated <see cref="T:GsaEditor.Core.Models.GsaEntry"/>, or null if no more entries can be read.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.AlignStream(System.IO.Stream,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Advances the stream position to the next aligned boundary.
|
||||||
|
</summary>
|
||||||
|
<param name="stream">The stream to align.</param>
|
||||||
|
<param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaWriter">
|
||||||
|
<summary>
|
||||||
|
Writes a <see cref="T:GsaEditor.Core.Models.GsaArchive"/> to a binary .gsa stream or file.
|
||||||
|
Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARC">
|
||||||
|
<summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
||||||
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
|
<summary>
|
||||||
|
Writes the archive to the specified file path.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive to write.</param>
|
||||||
|
<param name="filePath">The output file path.</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.IO.Stream)">
|
||||||
|
<summary>
|
||||||
|
Writes the archive to the given stream.
|
||||||
|
Each entry's <see cref="P:GsaEditor.Core.Models.GsaEntry.DataOffset"/> is updated to reflect the new position in the output.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive to write.</param>
|
||||||
|
<param name="stream">A writable stream.</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.WriteEntry(System.IO.BinaryWriter,GsaEditor.Core.Models.GsaEntry,GsaEditor.Core.Models.GsaArchive)">
|
||||||
|
<summary>
|
||||||
|
Writes a single file entry at the current stream position.
|
||||||
|
</summary>
|
||||||
|
<param name="writer">The binary writer.</param>
|
||||||
|
<param name="entry">The entry to write.</param>
|
||||||
|
<param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.WritePadding(System.IO.BinaryWriter,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Writes zero-bytes to pad the stream to the next aligned boundary.
|
||||||
|
</summary>
|
||||||
|
<param name="writer">The binary writer.</param>
|
||||||
|
<param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaArchive">
|
||||||
|
<summary>
|
||||||
|
Represents a loaded GSA archive with its global header information and file entries.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.Format">
|
||||||
|
<summary>
|
||||||
|
The format variant of this archive (NARC or NARD).
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.OffsetPadding">
|
||||||
|
<summary>
|
||||||
|
(NARD only) Byte alignment for data offsets. Zero or unused for NARC.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.SizePadding">
|
||||||
|
<summary>
|
||||||
|
(NARD only) Byte alignment for data sizes. Zero or unused for NARC.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.Entries">
|
||||||
|
<summary>
|
||||||
|
The ordered list of file entries in the archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaEntry">
|
||||||
|
<summary>
|
||||||
|
Represents a single file entry within a GSA archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.Alias">
|
||||||
|
<summary>
|
||||||
|
Relative path (alias) of the file within the archive, using '/' as separator.
|
||||||
|
Maximum 511 characters.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.IsCompressed">
|
||||||
|
<summary>
|
||||||
|
Whether this entry's data is zlib-compressed.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.OriginalLength">
|
||||||
|
<summary>
|
||||||
|
Original (decompressed) size of the file in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.CompressedLength">
|
||||||
|
<summary>
|
||||||
|
Compressed size of the file in bytes. Equal to <see cref="P:GsaEditor.Core.Models.GsaEntry.OriginalLength"/> if not compressed.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.RawData">
|
||||||
|
<summary>
|
||||||
|
Raw data as stored in the archive (compressed bytes if <see cref="P:GsaEditor.Core.Models.GsaEntry.IsCompressed"/> is true).
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.DataOffset">
|
||||||
|
<summary>
|
||||||
|
Byte offset of this entry's data block within the archive file.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Models.GsaEntry.GetDecompressedData">
|
||||||
|
<summary>
|
||||||
|
Returns the decompressed data for this entry.
|
||||||
|
If the entry is not compressed, returns <see cref="P:GsaEditor.Core.Models.GsaEntry.RawData"/> directly.
|
||||||
|
</summary>
|
||||||
|
<returns>The decompressed file content.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Models.GsaEntry.SetData(System.Byte[],System.Boolean)">
|
||||||
|
<summary>
|
||||||
|
Sets the entry data from uncompressed bytes, optionally compressing with zlib.
|
||||||
|
Updates <see cref="P:GsaEditor.Core.Models.GsaEntry.OriginalLength"/>, <see cref="P:GsaEditor.Core.Models.GsaEntry.CompressedLength"/>,
|
||||||
|
<see cref="P:GsaEditor.Core.Models.GsaEntry.IsCompressed"/>, and <see cref="P:GsaEditor.Core.Models.GsaEntry.RawData"/>.
|
||||||
|
</summary>
|
||||||
|
<param name="uncompressedData">The uncompressed file content.</param>
|
||||||
|
<param name="compress">Whether to compress the data with zlib.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaFormat">
|
||||||
|
<summary>
|
||||||
|
Identifies the archive format variant.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.Models.GsaFormat.NARC">
|
||||||
|
<summary>
|
||||||
|
Legacy format with no padding support. Magic word = 0x4E415243 ("NARC").
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.Models.GsaFormat.NARD">
|
||||||
|
<summary>
|
||||||
|
Enhanced format with offset and size padding for console DVD alignment.
|
||||||
|
Magic word = 0x4E415244 ("NARD").
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
</doc>
|
||||||
BIN
GsaEditor.Core/obj/Debug/net8.0/ref/GsaEditor.Core.dll
Normal file
BIN
GsaEditor.Core/obj/Debug/net8.0/ref/GsaEditor.Core.dll
Normal file
Binary file not shown.
BIN
GsaEditor.Core/obj/Debug/net8.0/refint/GsaEditor.Core.dll
Normal file
BIN
GsaEditor.Core/obj/Debug/net8.0/refint/GsaEditor.Core.dll
Normal file
Binary file not shown.
81
GsaEditor.Core/obj/GsaEditor.Core.csproj.nuget.dgspec.json
Normal file
81
GsaEditor.Core/obj/GsaEditor.Core.csproj.nuget.dgspec.json
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"format": 1,
|
||||||
|
"restore": {
|
||||||
|
"C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj": {}
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj",
|
||||||
|
"projectName": "GsaEditor.Core",
|
||||||
|
"projectPath": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\simulateur\\.nuget\\packages\\",
|
||||||
|
"outputPath": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\simulateur\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net8.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net8.0": {
|
||||||
|
"targetAlias": "net8.0",
|
||||||
|
"projectReferences": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net8.0": {
|
||||||
|
"targetAlias": "net8.0",
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"downloadDependencies": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.AspNetCore.App.Ref",
|
||||||
|
"version": "[8.0.21, 8.0.21]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.NETCore.App.Ref",
|
||||||
|
"version": "[8.0.21, 8.0.21]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.WindowsDesktop.App.Ref",
|
||||||
|
"version": "[8.0.21, 8.0.21]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.306/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
GsaEditor.Core/obj/GsaEditor.Core.csproj.nuget.g.props
Normal file
15
GsaEditor.Core/obj/GsaEditor.Core.csproj.nuget.g.props
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||||
|
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||||
|
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||||
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||||
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\simulateur\.nuget\packages\</NuGetPackageFolders>
|
||||||
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<SourceRoot Include="C:\Users\simulateur\.nuget\packages\" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
2
GsaEditor.Core/obj/GsaEditor.Core.csproj.nuget.g.targets
Normal file
2
GsaEditor.Core/obj/GsaEditor.Core.csproj.nuget.g.targets
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||||
86
GsaEditor.Core/obj/project.assets.json
Normal file
86
GsaEditor.Core/obj/project.assets.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"targets": {
|
||||||
|
"net8.0": {}
|
||||||
|
},
|
||||||
|
"libraries": {},
|
||||||
|
"projectFileDependencyGroups": {
|
||||||
|
"net8.0": []
|
||||||
|
},
|
||||||
|
"packageFolders": {
|
||||||
|
"C:\\Users\\simulateur\\.nuget\\packages\\": {}
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj",
|
||||||
|
"projectName": "GsaEditor.Core",
|
||||||
|
"projectPath": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\simulateur\\.nuget\\packages\\",
|
||||||
|
"outputPath": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\simulateur\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net8.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net8.0": {
|
||||||
|
"targetAlias": "net8.0",
|
||||||
|
"projectReferences": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net8.0": {
|
||||||
|
"targetAlias": "net8.0",
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"downloadDependencies": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.AspNetCore.App.Ref",
|
||||||
|
"version": "[8.0.21, 8.0.21]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.NETCore.App.Ref",
|
||||||
|
"version": "[8.0.21, 8.0.21]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.WindowsDesktop.App.Ref",
|
||||||
|
"version": "[8.0.21, 8.0.21]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.306/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
GsaEditor.Core/obj/project.nuget.cache
Normal file
12
GsaEditor.Core/obj/project.nuget.cache
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"dgSpecHash": "8Lf0I0TQO2o=",
|
||||||
|
"success": true,
|
||||||
|
"projectFilePath": "C:\\Users\\simulateur\\Desktop\\GsaViewer\\GsaEditor.Core\\GsaEditor.Core.csproj",
|
||||||
|
"expectedPackageFiles": [
|
||||||
|
"C:\\Users\\simulateur\\.nuget\\packages\\microsoft.netcore.app.ref\\8.0.21\\microsoft.netcore.app.ref.8.0.21.nupkg.sha512",
|
||||||
|
"C:\\Users\\simulateur\\.nuget\\packages\\microsoft.windowsdesktop.app.ref\\8.0.21\\microsoft.windowsdesktop.app.ref.8.0.21.nupkg.sha512",
|
||||||
|
"C:\\Users\\simulateur\\.nuget\\packages\\microsoft.aspnetcore.app.ref\\8.0.21\\microsoft.aspnetcore.app.ref.8.0.21.nupkg.sha512"
|
||||||
|
],
|
||||||
|
"logs": []
|
||||||
|
}
|
||||||
25
GsaEditor.sln
Normal file
25
GsaEditor.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GsaEditor", "GsaEditor\GsaEditor.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GsaEditor.Core", "GsaEditor.Core\GsaEditor.Core.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
9
GsaEditor/App.axaml
Normal file
9
GsaEditor/App.axaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="GsaEditor.App"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
||||||
35
GsaEditor/App.axaml.cs
Normal file
35
GsaEditor/App.axaml.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Data.Core.Plugins;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using GsaEditor.ViewModels;
|
||||||
|
using GsaEditor.Views;
|
||||||
|
|
||||||
|
namespace GsaEditor;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
|
||||||
|
var dataValidationPluginsToRemove =
|
||||||
|
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
|
||||||
|
foreach (var plugin in dataValidationPluginsToRemove)
|
||||||
|
BindingPlugins.DataValidators.Remove(plugin);
|
||||||
|
|
||||||
|
desktop.MainWindow = new MainWindow
|
||||||
|
{
|
||||||
|
DataContext = new MainWindowViewModel()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
31
GsaEditor/GsaEditor.csproj
Normal file
31
GsaEditor/GsaEditor.csproj
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaResource Include="Assets\**"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.6">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\GsaEditor.Core\GsaEditor.Core.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
204
GsaEditor/Helpers/Dialogs.cs
Normal file
204
GsaEditor/Helpers/Dialogs.cs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
|
||||||
|
namespace GsaEditor.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides simple dialog utilities (confirm, message, input) using programmatically-created Avalonia windows.
|
||||||
|
/// No third-party controls or custom themes are used.
|
||||||
|
/// </summary>
|
||||||
|
public static class Dialogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a confirmation dialog with Yes/No buttons.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">The parent window.</param>
|
||||||
|
/// <param name="title">Dialog title.</param>
|
||||||
|
/// <param name="message">Message to display.</param>
|
||||||
|
/// <returns>True if the user clicked Yes, false otherwise.</returns>
|
||||||
|
public static async Task<bool> ConfirmAsync(Window parent, string title, string message)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
var dialog = new Window
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Width = 420,
|
||||||
|
Height = 170,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||||
|
CanResize = false,
|
||||||
|
ShowInTaskbar = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var messageBlock = new TextBlock
|
||||||
|
{
|
||||||
|
Text = message,
|
||||||
|
TextWrapping = Avalonia.Media.TextWrapping.Wrap,
|
||||||
|
Margin = new Thickness(0, 0, 0, 15)
|
||||||
|
};
|
||||||
|
|
||||||
|
var yesButton = new Button
|
||||||
|
{
|
||||||
|
Content = "Yes",
|
||||||
|
Width = 80,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
yesButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
dialog.Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
var noButton = new Button
|
||||||
|
{
|
||||||
|
Content = "No",
|
||||||
|
Width = 80,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
noButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
dialog.Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttonPanel = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Spacing = 10,
|
||||||
|
Children = { yesButton, noButton }
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Content = new StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Thickness(20),
|
||||||
|
Spacing = 5,
|
||||||
|
Children = { messageBlock, buttonPanel }
|
||||||
|
};
|
||||||
|
|
||||||
|
await dialog.ShowDialog(parent);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a simple message dialog with an OK button.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">The parent window.</param>
|
||||||
|
/// <param name="title">Dialog title.</param>
|
||||||
|
/// <param name="message">Message to display.</param>
|
||||||
|
public static async Task ShowMessageAsync(Window parent, string title, string message)
|
||||||
|
{
|
||||||
|
var dialog = new Window
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Width = 420,
|
||||||
|
Height = 180,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||||
|
CanResize = false,
|
||||||
|
ShowInTaskbar = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var messageBlock = new TextBlock
|
||||||
|
{
|
||||||
|
Text = message,
|
||||||
|
TextWrapping = Avalonia.Media.TextWrapping.Wrap,
|
||||||
|
Margin = new Thickness(0, 0, 0, 15)
|
||||||
|
};
|
||||||
|
|
||||||
|
var okButton = new Button
|
||||||
|
{
|
||||||
|
Content = "OK",
|
||||||
|
Width = 80,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
okButton.Click += (_, _) => dialog.Close();
|
||||||
|
|
||||||
|
dialog.Content = new StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Thickness(20),
|
||||||
|
Spacing = 5,
|
||||||
|
Children = { messageBlock, okButton }
|
||||||
|
};
|
||||||
|
|
||||||
|
await dialog.ShowDialog(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows an input dialog with a text box, OK and Cancel buttons.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">The parent window.</param>
|
||||||
|
/// <param name="title">Dialog title.</param>
|
||||||
|
/// <param name="prompt">Prompt text shown above the input.</param>
|
||||||
|
/// <param name="defaultValue">Default value in the text box.</param>
|
||||||
|
/// <returns>The entered string, or null if cancelled.</returns>
|
||||||
|
public static async Task<string?> InputAsync(Window parent, string title, string prompt, string defaultValue = "")
|
||||||
|
{
|
||||||
|
string? result = null;
|
||||||
|
|
||||||
|
var dialog = new Window
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Width = 450,
|
||||||
|
Height = 180,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||||
|
CanResize = false,
|
||||||
|
ShowInTaskbar = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var promptBlock = new TextBlock
|
||||||
|
{
|
||||||
|
Text = prompt,
|
||||||
|
Margin = new Thickness(0, 0, 0, 5)
|
||||||
|
};
|
||||||
|
|
||||||
|
var textBox = new TextBox
|
||||||
|
{
|
||||||
|
Text = defaultValue,
|
||||||
|
Margin = new Thickness(0, 0, 0, 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
var okButton = new Button
|
||||||
|
{
|
||||||
|
Content = "OK",
|
||||||
|
Width = 80,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
okButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
result = textBox.Text;
|
||||||
|
dialog.Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
var cancelButton = new Button
|
||||||
|
{
|
||||||
|
Content = "Cancel",
|
||||||
|
Width = 80,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
cancelButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
dialog.Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttonPanel = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Spacing = 10,
|
||||||
|
Children = { okButton, cancelButton }
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Content = new StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Thickness(20),
|
||||||
|
Spacing = 5,
|
||||||
|
Children = { promptBlock, textBox, buttonPanel }
|
||||||
|
};
|
||||||
|
|
||||||
|
await dialog.ShowDialog(parent);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
GsaEditor/Helpers/HexDumper.cs
Normal file
59
GsaEditor/Helpers/HexDumper.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GsaEditor.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Produces a classic hex + ASCII dump string from a byte array.
|
||||||
|
/// </summary>
|
||||||
|
public static class HexDumper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Formats a byte array as a hex dump with offset, hex values, and printable ASCII characters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to dump.</param>
|
||||||
|
/// <param name="maxBytes">Maximum number of bytes to include in the dump.</param>
|
||||||
|
/// <returns>A formatted hex dump string.</returns>
|
||||||
|
public static string Dump(byte[] data, int maxBytes = 512)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
int length = Math.Min(data.Length, maxBytes);
|
||||||
|
const int bytesPerLine = 16;
|
||||||
|
|
||||||
|
for (int offset = 0; offset < length; offset += bytesPerLine)
|
||||||
|
{
|
||||||
|
// Offset column
|
||||||
|
sb.Append($"{offset:X8} ");
|
||||||
|
|
||||||
|
int lineLen = Math.Min(bytesPerLine, length - offset);
|
||||||
|
|
||||||
|
// Hex columns
|
||||||
|
for (int i = 0; i < bytesPerLine; i++)
|
||||||
|
{
|
||||||
|
if (i < lineLen)
|
||||||
|
sb.Append($"{data[offset + i]:X2} ");
|
||||||
|
else
|
||||||
|
sb.Append(" ");
|
||||||
|
|
||||||
|
if (i == 7) sb.Append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(' ');
|
||||||
|
|
||||||
|
// ASCII column
|
||||||
|
for (int i = 0; i < lineLen; i++)
|
||||||
|
{
|
||||||
|
byte b = data[offset + i];
|
||||||
|
sb.Append(b >= 0x20 && b < 0x7F ? (char)b : '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.Length > maxBytes)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"... ({data.Length - maxBytes} more bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
17
GsaEditor/Program.cs
Normal file
17
GsaEditor/Program.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GsaEditor;
|
||||||
|
|
||||||
|
sealed class Program
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
||||||
40
GsaEditor/ViewModels/EntryTreeNodeViewModel.cs
Normal file
40
GsaEditor/ViewModels/EntryTreeNodeViewModel.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
namespace GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node in the archive tree view.
|
||||||
|
/// Can be a folder (with children) or a leaf file node (with an associated <see cref="GsaEntry"/>).
|
||||||
|
/// </summary>
|
||||||
|
public partial class EntryTreeNodeViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _name = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full relative path of this node within the archive (using '/' separator).
|
||||||
|
/// </summary>
|
||||||
|
public string FullPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this node represents a folder (true) or a file (false).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFolder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this node represents a file.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFile => !IsFolder;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The archive entry this node represents. Null for folder nodes.
|
||||||
|
/// </summary>
|
||||||
|
public GsaEntry? Entry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Child nodes (sub-folders and files within this folder).
|
||||||
|
/// </summary>
|
||||||
|
public ObservableCollection<EntryTreeNodeViewModel> Children { get; } = new();
|
||||||
|
}
|
||||||
93
GsaEditor/ViewModels/EntryViewModel.cs
Normal file
93
GsaEditor/ViewModels/EntryViewModel.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using GsaEditor.Core.Models;
|
||||||
|
|
||||||
|
namespace GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps a <see cref="GsaEntry"/> for display in the details panel.
|
||||||
|
/// </summary>
|
||||||
|
public partial class EntryViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly GsaEntry _entry;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _alias = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isCompressed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback invoked when entry data is modified (compression toggle, alias change).
|
||||||
|
/// </summary>
|
||||||
|
public Action? OnModified { get; set; }
|
||||||
|
|
||||||
|
public EntryViewModel(GsaEntry entry)
|
||||||
|
{
|
||||||
|
_entry = entry;
|
||||||
|
_alias = entry.Alias;
|
||||||
|
_isCompressed = entry.IsCompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The underlying <see cref="GsaEntry"/>.
|
||||||
|
/// </summary>
|
||||||
|
public GsaEntry Entry => _entry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Original (decompressed) file size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public uint OriginalSize => _entry.OriginalLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compressed file size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public uint CompressedSize => _entry.CompressedLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compression ratio as a percentage (0–100). Higher means more compression.
|
||||||
|
/// </summary>
|
||||||
|
public string CompressionRatio => OriginalSize > 0
|
||||||
|
? $"{(1.0 - (double)CompressedSize / OriginalSize) * 100.0:F1}%"
|
||||||
|
: "0.0%";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Human-readable original size string.
|
||||||
|
/// </summary>
|
||||||
|
public string OriginalSizeText => FormatSize(OriginalSize);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Human-readable compressed size string.
|
||||||
|
/// </summary>
|
||||||
|
public string CompressedSizeText => FormatSize(CompressedSize);
|
||||||
|
|
||||||
|
partial void OnAliasChanged(string value)
|
||||||
|
{
|
||||||
|
if (_entry.Alias != value)
|
||||||
|
{
|
||||||
|
_entry.Alias = value;
|
||||||
|
OnModified?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnIsCompressedChanged(bool value)
|
||||||
|
{
|
||||||
|
if (_entry.IsCompressed != value)
|
||||||
|
{
|
||||||
|
var decompressed = _entry.GetDecompressedData();
|
||||||
|
_entry.SetData(decompressed, value);
|
||||||
|
OnPropertyChanged(nameof(OriginalSize));
|
||||||
|
OnPropertyChanged(nameof(CompressedSize));
|
||||||
|
OnPropertyChanged(nameof(CompressionRatio));
|
||||||
|
OnPropertyChanged(nameof(OriginalSizeText));
|
||||||
|
OnPropertyChanged(nameof(CompressedSizeText));
|
||||||
|
OnModified?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatSize(long bytes)
|
||||||
|
{
|
||||||
|
if (bytes < 1024) return $"{bytes} B";
|
||||||
|
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
|
||||||
|
return $"{bytes / (1024.0 * 1024.0):F1} MB";
|
||||||
|
}
|
||||||
|
}
|
||||||
726
GsaEditor/ViewModels/MainWindowViewModel.cs
Normal file
726
GsaEditor/ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,726 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using GsaEditor.Core.Compression;
|
||||||
|
using GsaEditor.Core.IO;
|
||||||
|
using GsaEditor.Core.Models;
|
||||||
|
using GsaEditor.Helpers;
|
||||||
|
|
||||||
|
namespace GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
public partial class MainWindowViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private static readonly HashSet<string> TextExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
".txt", ".nml", ".xml", ".json", ".csv", ".ini", ".cfg", ".log", ".nut"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly HashSet<string> ImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
".png", ".jpg", ".jpeg", ".bmp"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly HashSet<string> UnsupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
".tga", ".dds"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly FilePickerFileType GsaFileType = new("GSA Archives")
|
||||||
|
{
|
||||||
|
Patterns = new[] { "*.gsa" }
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly FilePickerFileType AllFilesType = new("All Files")
|
||||||
|
{
|
||||||
|
Patterns = new[] { "*" }
|
||||||
|
};
|
||||||
|
|
||||||
|
private Window? _window;
|
||||||
|
private GsaArchive? _archive;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(Title))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(StatusArchivePath))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasArchive))]
|
||||||
|
private string? _archivePath;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(Title))]
|
||||||
|
private bool _isDirty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isLoading;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasSelectedFile))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasSelectedFolder))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasSelectedEntry))]
|
||||||
|
private EntryTreeNodeViewModel? _selectedNode;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private EntryViewModel? _selectedEntry;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsTextPreview))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsImagePreview))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsHexPreview))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasPreview))]
|
||||||
|
private PreviewMode _previewMode = PreviewMode.None;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? _previewText;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Bitmap? _previewImage;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? _hexDumpText;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isEditingText;
|
||||||
|
|
||||||
|
public ObservableCollection<EntryTreeNodeViewModel> TreeRoots { get; } = new();
|
||||||
|
|
||||||
|
// --- Derived properties ---
|
||||||
|
|
||||||
|
public string Title => ArchivePath != null
|
||||||
|
? $"GsaEditor - {Path.GetFileName(ArchivePath)}{(IsDirty ? " *" : "")}"
|
||||||
|
: "GsaEditor";
|
||||||
|
|
||||||
|
public bool HasArchive => _archive != null;
|
||||||
|
public bool HasSelectedEntry => SelectedNode?.Entry != null;
|
||||||
|
public bool HasSelectedFile => SelectedNode != null && !SelectedNode.IsFolder;
|
||||||
|
public bool HasSelectedFolder => SelectedNode != null && SelectedNode.IsFolder;
|
||||||
|
public bool IsTextPreview => PreviewMode == PreviewMode.Text;
|
||||||
|
public bool IsImagePreview => PreviewMode == PreviewMode.Image;
|
||||||
|
public bool IsHexPreview => PreviewMode == PreviewMode.HexDump;
|
||||||
|
public bool HasPreview => PreviewMode != PreviewMode.None;
|
||||||
|
|
||||||
|
// --- Status bar ---
|
||||||
|
|
||||||
|
public string StatusArchivePath => ArchivePath ?? "No archive loaded";
|
||||||
|
|
||||||
|
public string StatusEntryCount => _archive != null
|
||||||
|
? $"{_archive.Entries.Count} entries"
|
||||||
|
: "";
|
||||||
|
|
||||||
|
public string StatusSizeInfo
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_archive == null) return "";
|
||||||
|
long totalOriginal = _archive.Entries.Sum(e => (long)e.OriginalLength);
|
||||||
|
long totalCompressed = _archive.Entries.Sum(e => (long)e.CompressedLength);
|
||||||
|
return $"Total: {FormatSize(totalOriginal)} / Compressed: {FormatSize(totalCompressed)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the parent window reference for dialog display.
|
||||||
|
/// </summary>
|
||||||
|
public void SetWindow(Window window)
|
||||||
|
{
|
||||||
|
_window = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Selection changed
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
partial void OnSelectedNodeChanged(EntryTreeNodeViewModel? value)
|
||||||
|
{
|
||||||
|
IsEditingText = false;
|
||||||
|
|
||||||
|
if (value?.Entry != null)
|
||||||
|
{
|
||||||
|
var entryVm = new EntryViewModel(value.Entry);
|
||||||
|
entryVm.OnModified = () =>
|
||||||
|
{
|
||||||
|
IsDirty = true;
|
||||||
|
NotifyStatusChanged();
|
||||||
|
};
|
||||||
|
SelectedEntry = entryVm;
|
||||||
|
UpdatePreview(value.Entry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedEntry = null;
|
||||||
|
ClearPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePreview(GsaEntry entry)
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(entry.Alias).ToLowerInvariant();
|
||||||
|
|
||||||
|
if (TextExtensions.Contains(ext))
|
||||||
|
{
|
||||||
|
PreviewMode = PreviewMode.Text;
|
||||||
|
var data = entry.GetDecompressedData();
|
||||||
|
PreviewText = Encoding.UTF8.GetString(data);
|
||||||
|
PreviewImage?.Dispose();
|
||||||
|
PreviewImage = null;
|
||||||
|
HexDumpText = null;
|
||||||
|
}
|
||||||
|
else if (ImageExtensions.Contains(ext))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = entry.GetDecompressedData();
|
||||||
|
using var ms = new MemoryStream(data);
|
||||||
|
PreviewImage?.Dispose();
|
||||||
|
PreviewImage = new Bitmap(ms);
|
||||||
|
PreviewMode = PreviewMode.Image;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
PreviewImage = null;
|
||||||
|
PreviewMode = PreviewMode.HexDump;
|
||||||
|
HexDumpText = "Preview not available for this image.";
|
||||||
|
}
|
||||||
|
PreviewText = null;
|
||||||
|
}
|
||||||
|
else if (UnsupportedImageExtensions.Contains(ext))
|
||||||
|
{
|
||||||
|
PreviewMode = PreviewMode.HexDump;
|
||||||
|
HexDumpText = $"Preview not available for {ext.ToUpperInvariant()} files.";
|
||||||
|
PreviewText = null;
|
||||||
|
PreviewImage?.Dispose();
|
||||||
|
PreviewImage = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PreviewMode = PreviewMode.HexDump;
|
||||||
|
var data = entry.GetDecompressedData();
|
||||||
|
HexDumpText = HexDumper.Dump(data, 512);
|
||||||
|
PreviewText = null;
|
||||||
|
PreviewImage?.Dispose();
|
||||||
|
PreviewImage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearPreview()
|
||||||
|
{
|
||||||
|
PreviewMode = PreviewMode.None;
|
||||||
|
PreviewText = null;
|
||||||
|
PreviewImage?.Dispose();
|
||||||
|
PreviewImage = null;
|
||||||
|
HexDumpText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// File menu commands
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task OpenArchive()
|
||||||
|
{
|
||||||
|
if (_window == null) return;
|
||||||
|
|
||||||
|
if (IsDirty)
|
||||||
|
{
|
||||||
|
var confirmed = await Dialogs.ConfirmAsync(_window, "Unsaved Changes",
|
||||||
|
"You have unsaved changes. Open a new archive without saving?");
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = await _window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Open GSA Archive",
|
||||||
|
FileTypeFilter = new[] { GsaFileType, AllFilesType },
|
||||||
|
AllowMultiple = false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count == 0) return;
|
||||||
|
|
||||||
|
var path = files[0].Path.LocalPath;
|
||||||
|
await LoadArchiveAsync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadArchiveAsync(string path)
|
||||||
|
{
|
||||||
|
IsLoading = true;
|
||||||
|
ClearPreview();
|
||||||
|
SelectedEntry = null;
|
||||||
|
SelectedNode = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var archive = await Task.Run(() => GsaReader.Read(path));
|
||||||
|
_archive = archive;
|
||||||
|
ArchivePath = path;
|
||||||
|
IsDirty = false;
|
||||||
|
BuildTree();
|
||||||
|
NotifyStatusChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (_window != null)
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Error", $"Failed to open archive:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
if (_archive == null || _window == null) return;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ArchivePath))
|
||||||
|
{
|
||||||
|
await SaveAs();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() => GsaWriter.Write(_archive, ArchivePath!));
|
||||||
|
|
||||||
|
// Generate .idx alongside
|
||||||
|
var idxPath = Path.ChangeExtension(ArchivePath, ".idx");
|
||||||
|
await Task.Run(() => GsaIndexWriter.Write(_archive, idxPath));
|
||||||
|
|
||||||
|
IsDirty = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Error", $"Failed to save archive:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SaveAs()
|
||||||
|
{
|
||||||
|
if (_archive == null || _window == null) return;
|
||||||
|
|
||||||
|
var file = await _window.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||||
|
{
|
||||||
|
Title = "Save GSA Archive As",
|
||||||
|
FileTypeChoices = new[] { GsaFileType },
|
||||||
|
DefaultExtension = "gsa",
|
||||||
|
SuggestedFileName = ArchivePath != null ? Path.GetFileName(ArchivePath) : "archive.gsa"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file == null) return;
|
||||||
|
|
||||||
|
var path = file.Path.LocalPath;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() => GsaWriter.Write(_archive, path));
|
||||||
|
|
||||||
|
var idxPath = Path.ChangeExtension(path, ".idx");
|
||||||
|
await Task.Run(() => GsaIndexWriter.Write(_archive, idxPath));
|
||||||
|
|
||||||
|
ArchivePath = path;
|
||||||
|
IsDirty = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Error", $"Failed to save archive:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task CloseArchive()
|
||||||
|
{
|
||||||
|
if (_window == null) return;
|
||||||
|
|
||||||
|
if (IsDirty)
|
||||||
|
{
|
||||||
|
var confirmed = await Dialogs.ConfirmAsync(_window, "Unsaved Changes",
|
||||||
|
"You have unsaved changes. Close without saving?");
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_archive = null;
|
||||||
|
ArchivePath = null;
|
||||||
|
IsDirty = false;
|
||||||
|
TreeRoots.Clear();
|
||||||
|
SelectedEntry = null;
|
||||||
|
SelectedNode = null;
|
||||||
|
ClearPreview();
|
||||||
|
NotifyStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Exit()
|
||||||
|
{
|
||||||
|
_window?.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Edit menu commands
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task AddFiles()
|
||||||
|
{
|
||||||
|
if (_archive == null || _window == null) return;
|
||||||
|
|
||||||
|
var files = await _window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Add Files to Archive",
|
||||||
|
AllowMultiple = true,
|
||||||
|
FileTypeFilter = new[] { AllFilesType }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count == 0) return;
|
||||||
|
|
||||||
|
string prefix = "";
|
||||||
|
if (SelectedNode != null && SelectedNode.IsFolder)
|
||||||
|
{
|
||||||
|
prefix = SelectedNode.FullPath;
|
||||||
|
if (!prefix.EndsWith("/")) prefix += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var filePath = file.Path.LocalPath;
|
||||||
|
var fileName = Path.GetFileName(filePath);
|
||||||
|
var alias = prefix + fileName;
|
||||||
|
|
||||||
|
var data = await Task.Run(() => File.ReadAllBytes(filePath));
|
||||||
|
var entry = new GsaEntry();
|
||||||
|
entry.Alias = alias;
|
||||||
|
entry.SetData(data, compress: true);
|
||||||
|
|
||||||
|
_archive.Entries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsDirty = true;
|
||||||
|
BuildTree();
|
||||||
|
NotifyStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ExtractAll()
|
||||||
|
{
|
||||||
|
if (_archive == null || _window == null) return;
|
||||||
|
|
||||||
|
var folders = await _window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Select Extraction Folder"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (folders.Count == 0) return;
|
||||||
|
|
||||||
|
var basePath = folders[0].Path.LocalPath;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
foreach (var entry in _archive.Entries)
|
||||||
|
{
|
||||||
|
var outputPath = Path.Combine(basePath, entry.Alias.Replace('/', Path.DirectorySeparatorChar));
|
||||||
|
var dir = Path.GetDirectoryName(outputPath);
|
||||||
|
if (dir != null) Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
var data = entry.GetDecompressedData();
|
||||||
|
File.WriteAllBytes(outputPath, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Extract All",
|
||||||
|
$"Successfully extracted {_archive.Entries.Count} files to:\n{basePath}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Error", $"Extraction failed:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Repack()
|
||||||
|
{
|
||||||
|
if (_archive == null || _window == null) return;
|
||||||
|
|
||||||
|
IsLoading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
foreach (var entry in _archive.Entries)
|
||||||
|
{
|
||||||
|
var decompressed = entry.GetDecompressedData();
|
||||||
|
entry.SetData(decompressed, entry.IsCompressed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IsDirty = true;
|
||||||
|
BuildTree();
|
||||||
|
NotifyStatusChanged();
|
||||||
|
|
||||||
|
if (SelectedNode?.Entry != null)
|
||||||
|
{
|
||||||
|
var entryVm = new EntryViewModel(SelectedNode.Entry);
|
||||||
|
entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
|
||||||
|
SelectedEntry = entryVm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Context menu commands
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ExtractEntry()
|
||||||
|
{
|
||||||
|
if (SelectedNode?.Entry == null || _window == null) return;
|
||||||
|
|
||||||
|
var entry = SelectedNode.Entry;
|
||||||
|
var file = await _window.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||||
|
{
|
||||||
|
Title = "Extract File",
|
||||||
|
SuggestedFileName = Path.GetFileName(entry.Alias),
|
||||||
|
FileTypeChoices = new[] { AllFilesType }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = entry.GetDecompressedData();
|
||||||
|
var path = file.Path.LocalPath;
|
||||||
|
await File.WriteAllBytesAsync(path, data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Error", $"Extraction failed:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ReplaceEntry()
|
||||||
|
{
|
||||||
|
if (SelectedNode?.Entry == null || _archive == null || _window == null) return;
|
||||||
|
|
||||||
|
var files = await _window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Replace Entry With",
|
||||||
|
AllowMultiple = false,
|
||||||
|
FileTypeFilter = new[] { AllFilesType }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files.Count == 0) return;
|
||||||
|
|
||||||
|
var filePath = files[0].Path.LocalPath;
|
||||||
|
var data = await Task.Run(() => File.ReadAllBytes(filePath));
|
||||||
|
|
||||||
|
var entry = SelectedNode.Entry;
|
||||||
|
entry.SetData(data, entry.IsCompressed);
|
||||||
|
|
||||||
|
IsDirty = true;
|
||||||
|
NotifyStatusChanged();
|
||||||
|
|
||||||
|
// Refresh the selected entry view
|
||||||
|
var entryVm = new EntryViewModel(entry);
|
||||||
|
entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
|
||||||
|
SelectedEntry = entryVm;
|
||||||
|
UpdatePreview(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void DeleteEntry()
|
||||||
|
{
|
||||||
|
if (SelectedNode?.Entry == null || _archive == null) return;
|
||||||
|
|
||||||
|
_archive.Entries.Remove(SelectedNode.Entry);
|
||||||
|
IsDirty = true;
|
||||||
|
SelectedNode = null;
|
||||||
|
SelectedEntry = null;
|
||||||
|
ClearPreview();
|
||||||
|
BuildTree();
|
||||||
|
NotifyStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RenameEntry()
|
||||||
|
{
|
||||||
|
if (SelectedNode?.Entry == null || _window == null) return;
|
||||||
|
|
||||||
|
var entry = SelectedNode.Entry;
|
||||||
|
var result = await Dialogs.InputAsync(_window, "Rename Entry", "New alias:", entry.Alias);
|
||||||
|
|
||||||
|
if (result != null && result != entry.Alias && result.Length > 0)
|
||||||
|
{
|
||||||
|
entry.Alias = result;
|
||||||
|
IsDirty = true;
|
||||||
|
BuildTree();
|
||||||
|
NotifyStatusChanged();
|
||||||
|
|
||||||
|
if (SelectedEntry != null)
|
||||||
|
{
|
||||||
|
var entryVm = new EntryViewModel(entry);
|
||||||
|
entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
|
||||||
|
SelectedEntry = entryVm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ExtractFolder()
|
||||||
|
{
|
||||||
|
if (SelectedNode == null || !SelectedNode.IsFolder || _archive == null || _window == null) return;
|
||||||
|
|
||||||
|
var folders = await _window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = "Select Extraction Folder"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (folders.Count == 0) return;
|
||||||
|
|
||||||
|
var basePath = folders[0].Path.LocalPath;
|
||||||
|
var prefix = SelectedNode.FullPath;
|
||||||
|
if (!prefix.EndsWith("/")) prefix += "/";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var matching = _archive.Entries.Where(e => e.Alias.StartsWith(prefix, StringComparison.Ordinal)).ToList();
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
foreach (var entry in matching)
|
||||||
|
{
|
||||||
|
var relativePath = entry.Alias.Substring(prefix.Length);
|
||||||
|
var outputPath = Path.Combine(basePath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||||
|
var dir = Path.GetDirectoryName(outputPath);
|
||||||
|
if (dir != null) Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
var data = entry.GetDecompressedData();
|
||||||
|
File.WriteAllBytes(outputPath, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Extract Folder",
|
||||||
|
$"Successfully extracted files to:\n{basePath}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "Error", $"Extraction failed:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Text editing commands
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void StartEdit()
|
||||||
|
{
|
||||||
|
IsEditingText = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void SaveEdit()
|
||||||
|
{
|
||||||
|
if (SelectedNode?.Entry == null || PreviewText == null) return;
|
||||||
|
|
||||||
|
var entry = SelectedNode.Entry;
|
||||||
|
var newData = Encoding.UTF8.GetBytes(PreviewText);
|
||||||
|
entry.SetData(newData, entry.IsCompressed);
|
||||||
|
|
||||||
|
IsDirty = true;
|
||||||
|
IsEditingText = false;
|
||||||
|
|
||||||
|
var entryVm = new EntryViewModel(entry);
|
||||||
|
entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
|
||||||
|
SelectedEntry = entryVm;
|
||||||
|
NotifyStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CancelEdit()
|
||||||
|
{
|
||||||
|
IsEditingText = false;
|
||||||
|
if (SelectedNode?.Entry != null)
|
||||||
|
{
|
||||||
|
PreviewText = Encoding.UTF8.GetString(SelectedNode.Entry.GetDecompressedData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// About
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ShowAbout()
|
||||||
|
{
|
||||||
|
if (_window == null) return;
|
||||||
|
await Dialogs.ShowMessageAsync(_window, "About GsaEditor",
|
||||||
|
"GsaEditor v1.0\n\nGSA Archive Viewer & Editor\nBuilt with Avalonia UI");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Tree building
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private void BuildTree()
|
||||||
|
{
|
||||||
|
TreeRoots.Clear();
|
||||||
|
if (_archive == null) return;
|
||||||
|
|
||||||
|
foreach (var entry in _archive.Entries)
|
||||||
|
{
|
||||||
|
var parts = entry.Alias.Split('/');
|
||||||
|
var currentLevel = TreeRoots;
|
||||||
|
var currentPath = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
var part = parts[i];
|
||||||
|
currentPath = i == 0 ? part : currentPath + "/" + part;
|
||||||
|
bool isLeaf = (i == parts.Length - 1);
|
||||||
|
|
||||||
|
var existing = currentLevel.FirstOrDefault(n => n.Name == part);
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
existing = new EntryTreeNodeViewModel
|
||||||
|
{
|
||||||
|
Name = part,
|
||||||
|
FullPath = currentPath,
|
||||||
|
IsFolder = !isLeaf,
|
||||||
|
Entry = isLeaf ? entry : null
|
||||||
|
};
|
||||||
|
currentLevel.Add(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLeaf)
|
||||||
|
{
|
||||||
|
currentLevel = existing.Children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Helpers
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private void NotifyStatusChanged()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(StatusEntryCount));
|
||||||
|
OnPropertyChanged(nameof(StatusSizeInfo));
|
||||||
|
OnPropertyChanged(nameof(HasArchive));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatSize(long bytes)
|
||||||
|
{
|
||||||
|
if (bytes < 1024) return $"{bytes} B";
|
||||||
|
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
|
||||||
|
return $"{bytes / (1024.0 * 1024.0):F1} MB";
|
||||||
|
}
|
||||||
|
}
|
||||||
12
GsaEditor/ViewModels/PreviewMode.cs
Normal file
12
GsaEditor/ViewModels/PreviewMode.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines which preview panel to show for the selected archive entry.
|
||||||
|
/// </summary>
|
||||||
|
public enum PreviewMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Text,
|
||||||
|
Image,
|
||||||
|
HexDump
|
||||||
|
}
|
||||||
7
GsaEditor/ViewModels/ViewModelBase.cs
Normal file
7
GsaEditor/ViewModels/ViewModelBase.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
public class ViewModelBase : ObservableObject
|
||||||
|
{
|
||||||
|
}
|
||||||
240
GsaEditor/Views/MainWindow.axaml
Normal file
240
GsaEditor/Views/MainWindow.axaml
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:GsaEditor.ViewModels"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
|
||||||
|
x:Class="GsaEditor.Views.MainWindow"
|
||||||
|
x:CompileBindings="False"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
Width="1100" Height="700"
|
||||||
|
MinWidth="800" MinHeight="500">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:MainWindowViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<DockPanel>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- Menu Bar -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Menu DockPanel.Dock="Top">
|
||||||
|
<MenuItem Header="_File">
|
||||||
|
<MenuItem Header="_Open Archive..." Command="{Binding OpenArchiveCommand}"
|
||||||
|
InputGesture="Ctrl+O"/>
|
||||||
|
<MenuItem Header="_Save" Command="{Binding SaveCommand}"
|
||||||
|
InputGesture="Ctrl+S"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
<MenuItem Header="Save _As..." Command="{Binding SaveAsCommand}"
|
||||||
|
InputGesture="Ctrl+Shift+S"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="_Close" Command="{Binding CloseArchiveCommand}"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
<MenuItem Header="E_xit" Command="{Binding ExitCommand}"
|
||||||
|
InputGesture="Alt+F4"/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="_Edit">
|
||||||
|
<MenuItem Header="_Add File(s)..." Command="{Binding AddFilesCommand}"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
<MenuItem Header="_Extract All..." Command="{Binding ExtractAllCommand}"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="_Repack" Command="{Binding RepackCommand}"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="_About">
|
||||||
|
<MenuItem Header="About GsaEditor" Command="{Binding ShowAboutCommand}"/>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- Status Bar -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Border DockPanel.Dock="Bottom" Padding="8,4"
|
||||||
|
BorderThickness="0,1,0,0" BorderBrush="#CCCCCC"
|
||||||
|
Background="#F5F5F5">
|
||||||
|
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding StatusArchivePath}"
|
||||||
|
VerticalAlignment="Center" FontSize="12"
|
||||||
|
Foreground="#555555"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding StatusEntryCount}"
|
||||||
|
VerticalAlignment="Center" FontSize="12"
|
||||||
|
Foreground="#555555" Margin="20,0,0,0"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="{Binding StatusSizeInfo}"
|
||||||
|
VerticalAlignment="Center" FontSize="12"
|
||||||
|
Foreground="#555555" Margin="20,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Border IsVisible="{Binding IsLoading}" Background="#80000000"
|
||||||
|
ZIndex="100" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
|
<TextBlock Text="Loading..." FontSize="20"
|
||||||
|
Foreground="White"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- Main Content: Left Tree + Right Details -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Grid ColumnDefinitions="300,5,*">
|
||||||
|
|
||||||
|
<!-- ======================================================== -->
|
||||||
|
<!-- Left Panel: Archive Tree -->
|
||||||
|
<!-- ======================================================== -->
|
||||||
|
<Border Grid.Column="0" BorderThickness="0,0,1,0" BorderBrush="#DDDDDD">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Top" Text="Archive Contents"
|
||||||
|
FontWeight="SemiBold" Padding="8,6" FontSize="13"
|
||||||
|
Background="#EEEEEE"/>
|
||||||
|
<TreeView Name="ArchiveTree"
|
||||||
|
ItemsSource="{Binding TreeRoots}"
|
||||||
|
SelectionChanged="TreeView_SelectionChanged">
|
||||||
|
|
||||||
|
<TreeView.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<!-- File node operations -->
|
||||||
|
<MenuItem Header="Extract" Command="{Binding ExtractEntryCommand}"
|
||||||
|
IsVisible="{Binding HasSelectedFile}"/>
|
||||||
|
<MenuItem Header="Replace" Command="{Binding ReplaceEntryCommand}"
|
||||||
|
IsVisible="{Binding HasSelectedFile}"/>
|
||||||
|
<MenuItem Header="Delete" Command="{Binding DeleteEntryCommand}"
|
||||||
|
IsVisible="{Binding HasSelectedFile}"/>
|
||||||
|
<MenuItem Header="Rename" Command="{Binding RenameEntryCommand}"
|
||||||
|
IsVisible="{Binding HasSelectedFile}"/>
|
||||||
|
<!-- Folder node operations -->
|
||||||
|
<MenuItem Header="Extract Folder" Command="{Binding ExtractFolderCommand}"
|
||||||
|
IsVisible="{Binding HasSelectedFolder}"/>
|
||||||
|
<!-- Always available -->
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="Add File(s)..." Command="{Binding AddFilesCommand}"
|
||||||
|
IsEnabled="{Binding HasArchive}"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</TreeView.ContextMenu>
|
||||||
|
|
||||||
|
<TreeView.ItemTemplate>
|
||||||
|
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<TextBlock Text="📁" IsVisible="{Binding IsFolder}"/>
|
||||||
|
<TextBlock Text="📄" IsVisible="{Binding IsFile}"/>
|
||||||
|
<TextBlock Text="{Binding Name}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</TreeDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Splitter -->
|
||||||
|
<GridSplitter Grid.Column="1" ResizeDirection="Columns"
|
||||||
|
Background="#CCCCCC"/>
|
||||||
|
|
||||||
|
<!-- ======================================================== -->
|
||||||
|
<!-- Right Panel: Details & Preview -->
|
||||||
|
<!-- ======================================================== -->
|
||||||
|
<Grid Grid.Column="2" RowDefinitions="Auto,*">
|
||||||
|
|
||||||
|
<!-- ================================================== -->
|
||||||
|
<!-- Top Bar: Entry Info -->
|
||||||
|
<!-- ================================================== -->
|
||||||
|
<Border Grid.Row="0" Padding="10,8"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderThickness="0,0,0,1" BorderBrush="#DDDDDD"
|
||||||
|
IsVisible="{Binding HasSelectedEntry}">
|
||||||
|
<Grid RowDefinitions="Auto,Auto">
|
||||||
|
<!-- Row 0: Alias -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*" Margin="0,0,0,6">
|
||||||
|
<TextBlock Grid.Column="0" Text="Alias:"
|
||||||
|
VerticalAlignment="Center" Margin="0,0,8,0"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Text="{Binding SelectedEntry.Alias, Mode=TwoWay}"
|
||||||
|
Watermark="File path in archive"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Row 1: Size info + compression toggle -->
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="20">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
|
<TextBlock Text="Size:" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding SelectedEntry.OriginalSizeText}" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
|
<TextBlock Text="Compressed:" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding SelectedEntry.CompressedSizeText}" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
|
<TextBlock Text="Ratio:" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding SelectedEntry.CompressionRatio}" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox Content="Compressed"
|
||||||
|
IsChecked="{Binding SelectedEntry.IsCompressed, Mode=TwoWay}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ================================================== -->
|
||||||
|
<!-- Content Area: Preview -->
|
||||||
|
<!-- ================================================== -->
|
||||||
|
<Panel Grid.Row="1">
|
||||||
|
<!-- No selection / No archive message -->
|
||||||
|
<TextBlock Text="Open a GSA archive and select a file to preview."
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Foreground="#999999" FontSize="14"
|
||||||
|
IsVisible="{Binding !HasPreview}"/>
|
||||||
|
|
||||||
|
<!-- Text Preview -->
|
||||||
|
<DockPanel IsVisible="{Binding IsTextPreview}">
|
||||||
|
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
|
||||||
|
Spacing="5" Margin="10,6">
|
||||||
|
<Button Content="Edit" Command="{Binding StartEditCommand}"
|
||||||
|
IsVisible="{Binding !IsEditingText}"/>
|
||||||
|
<Button Content="Save" Command="{Binding SaveEditCommand}"
|
||||||
|
IsVisible="{Binding IsEditingText}"/>
|
||||||
|
<Button Content="Cancel" Command="{Binding CancelEditCommand}"
|
||||||
|
IsVisible="{Binding IsEditingText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox Text="{Binding PreviewText, Mode=TwoWay}"
|
||||||
|
IsReadOnly="{Binding !IsEditingText}"
|
||||||
|
FontFamily="Consolas, Courier New, monospace"
|
||||||
|
FontSize="13"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
Margin="10,0,10,10"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- Image Preview -->
|
||||||
|
<ScrollViewer IsVisible="{Binding IsImagePreview}"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<Image Source="{Binding PreviewImage}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
MaxWidth="800" MaxHeight="600"
|
||||||
|
Margin="10"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Hex Dump Preview -->
|
||||||
|
<TextBox IsVisible="{Binding IsHexPreview}"
|
||||||
|
Text="{Binding HexDumpText}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
FontFamily="Consolas, Courier New, monospace"
|
||||||
|
FontSize="13"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
Margin="10"/>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
</Window>
|
||||||
52
GsaEditor/Views/MainWindow.axaml.cs
Normal file
52
GsaEditor/Views/MainWindow.axaml.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using GsaEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace GsaEditor.Views;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private bool _forceClose;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLoaded(e);
|
||||||
|
|
||||||
|
if (DataContext is MainWindowViewModel vm)
|
||||||
|
{
|
||||||
|
vm.SetWindow(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TreeView_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is MainWindowViewModel vm && sender is TreeView tree)
|
||||||
|
{
|
||||||
|
vm.SelectedNode = tree.SelectedItem as EntryTreeNodeViewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnClosing(WindowClosingEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnClosing(e);
|
||||||
|
|
||||||
|
if (_forceClose) return;
|
||||||
|
|
||||||
|
if (DataContext is MainWindowViewModel vm && vm.IsDirty)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
var confirmed = await Helpers.Dialogs.ConfirmAsync(this, "Unsaved Changes",
|
||||||
|
"You have unsaved changes. Close without saving?");
|
||||||
|
if (confirmed)
|
||||||
|
{
|
||||||
|
_forceClose = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
GsaEditor/app.manifest
Normal file
11
GsaEditor/app.manifest
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="GsaEditor.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows 10 and Windows 11 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Base.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Base.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Controls.ColorPicker.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Controls.ColorPicker.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Controls.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Controls.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.DesignerSupport.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.DesignerSupport.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Desktop.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Desktop.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Diagnostics.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Diagnostics.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Dialogs.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Dialogs.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Fonts.Inter.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Fonts.Inter.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.FreeDesktop.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.FreeDesktop.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Markup.Xaml.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Markup.Xaml.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Markup.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Markup.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Metal.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Metal.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.MicroCom.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.MicroCom.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Native.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Native.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.OpenGL.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.OpenGL.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Remote.Protocol.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Remote.Protocol.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Skia.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Skia.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Themes.Fluent.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Themes.Fluent.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Themes.Simple.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Themes.Simple.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Vulkan.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Vulkan.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Win32.Automation.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Win32.Automation.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Win32.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.Win32.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.X11.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.X11.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Avalonia.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/CommunityToolkit.Mvvm.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/CommunityToolkit.Mvvm.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.Core.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.Core.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.Core.pdb
Normal file
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.Core.pdb
Normal file
Binary file not shown.
276
GsaEditor/bin/Debug/net8.0/GsaEditor.Core.xml
Normal file
276
GsaEditor/bin/Debug/net8.0/GsaEditor.Core.xml
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<doc>
|
||||||
|
<assembly>
|
||||||
|
<name>GsaEditor.Core</name>
|
||||||
|
</assembly>
|
||||||
|
<members>
|
||||||
|
<member name="T:GsaEditor.Core.Compression.ZlibHelper">
|
||||||
|
<summary>
|
||||||
|
Provides zlib compression and decompression utilities for GSA archive entries.
|
||||||
|
The GSA format stores zlib-wrapped data: 2-byte header (CMF+FLG) + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
This helper skips the 2-byte zlib header and uses <see cref="T:System.IO.Compression.DeflateStream"/> for the core deflate data.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.Decompress(System.Byte[],System.UInt32)">
|
||||||
|
<summary>
|
||||||
|
Decompresses zlib-wrapped data into a byte array of the specified original size.
|
||||||
|
Skips the 2-byte zlib header before feeding data to <see cref="T:System.IO.Compression.DeflateStream"/>.
|
||||||
|
</summary>
|
||||||
|
<param name="compressedData">The zlib-wrapped compressed data (header + deflate + checksum).</param>
|
||||||
|
<param name="originalLength">The expected decompressed size in bytes.</param>
|
||||||
|
<returns>The decompressed byte array.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown when decompression fails or data is corrupt.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.Compress(System.Byte[])">
|
||||||
|
<summary>
|
||||||
|
Compresses data using zlib format: 2-byte header + deflate stream + 4-byte Adler-32 checksum.
|
||||||
|
</summary>
|
||||||
|
<param name="data">The uncompressed data to compress.</param>
|
||||||
|
<returns>The zlib-wrapped compressed byte array.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Compression.ZlibHelper.ComputeAdler32(System.Byte[])">
|
||||||
|
<summary>
|
||||||
|
Computes the Adler-32 checksum of the given data.
|
||||||
|
</summary>
|
||||||
|
<param name="data">The data to checksum.</param>
|
||||||
|
<returns>The 32-bit Adler-32 checksum value.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexEntry">
|
||||||
|
<summary>
|
||||||
|
Represents a single entry parsed from a .idx NML index file.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Id">
|
||||||
|
<summary>
|
||||||
|
The file alias (relative path) of the entry.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Method">
|
||||||
|
<summary>
|
||||||
|
Compression method: 0 = raw, 1 = zlib.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Length">
|
||||||
|
<summary>
|
||||||
|
Original (decompressed) size in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.CompressedLength">
|
||||||
|
<summary>
|
||||||
|
Compressed size in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.IO.GsaIndexEntry.Offset">
|
||||||
|
<summary>
|
||||||
|
Byte offset of the data block within the archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexReader">
|
||||||
|
<summary>
|
||||||
|
Parses .idx NML index files (XML-compatible format) into a list of <see cref="T:GsaEditor.Core.IO.GsaIndexEntry"/>.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaIndexReader.Read(System.String)">
|
||||||
|
<summary>
|
||||||
|
Reads index entries from the specified .idx file.
|
||||||
|
</summary>
|
||||||
|
<param name="filePath">The path to the .idx file.</param>
|
||||||
|
<returns>A list of parsed index entries.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaIndexWriter">
|
||||||
|
<summary>
|
||||||
|
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,
|
||||||
|
sizes, and data offset for fast lookup without a full sequential scan.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaIndexWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
|
<summary>
|
||||||
|
Writes the index file for the given archive to the specified path.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive whose entries will be indexed.</param>
|
||||||
|
<param name="filePath">The output .idx file path.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaReader">
|
||||||
|
<summary>
|
||||||
|
Reads a .gsa binary archive from a stream or file and produces a <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.
|
||||||
|
Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAGIC_NARC">
|
||||||
|
<summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAGIC_NARD">
|
||||||
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaReader.MAX_ALIAS_LENGTH">
|
||||||
|
<summary>Maximum allowed alias length in characters.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.Read(System.String)">
|
||||||
|
<summary>
|
||||||
|
Reads a GSA archive from the specified file path.
|
||||||
|
</summary>
|
||||||
|
<param name="filePath">The path to the .gsa archive file.</param>
|
||||||
|
<returns>A fully populated <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown if the file has an invalid magic word.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.Read(System.IO.Stream)">
|
||||||
|
<summary>
|
||||||
|
Reads a GSA archive from a stream by performing a sequential scan of all entries.
|
||||||
|
</summary>
|
||||||
|
<param name="stream">A readable, seekable stream positioned at the start of the archive.</param>
|
||||||
|
<returns>A fully populated <see cref="T:GsaEditor.Core.Models.GsaArchive"/>.</returns>
|
||||||
|
<exception cref="T:System.IO.InvalidDataException">Thrown if the stream has an invalid magic word.</exception>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.ReadEntry(System.IO.BinaryReader,GsaEditor.Core.Models.GsaArchive)">
|
||||||
|
<summary>
|
||||||
|
Reads a single file entry from the current stream position.
|
||||||
|
</summary>
|
||||||
|
<param name="reader">The binary reader positioned at the start of an entry.</param>
|
||||||
|
<param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
<returns>A populated <see cref="T:GsaEditor.Core.Models.GsaEntry"/>, or null if no more entries can be read.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaReader.AlignStream(System.IO.Stream,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Advances the stream position to the next aligned boundary.
|
||||||
|
</summary>
|
||||||
|
<param name="stream">The stream to align.</param>
|
||||||
|
<param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.IO.GsaWriter">
|
||||||
|
<summary>
|
||||||
|
Writes a <see cref="T:GsaEditor.Core.Models.GsaArchive"/> to a binary .gsa stream or file.
|
||||||
|
Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARC">
|
||||||
|
<summary>Magic word for the NARC (legacy) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.IO.GsaWriter.MAGIC_NARD">
|
||||||
|
<summary>Magic word for the NARD (enhanced) format.</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.String)">
|
||||||
|
<summary>
|
||||||
|
Writes the archive to the specified file path.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive to write.</param>
|
||||||
|
<param name="filePath">The output file path.</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.Write(GsaEditor.Core.Models.GsaArchive,System.IO.Stream)">
|
||||||
|
<summary>
|
||||||
|
Writes the archive to the given stream.
|
||||||
|
Each entry's <see cref="P:GsaEditor.Core.Models.GsaEntry.DataOffset"/> is updated to reflect the new position in the output.
|
||||||
|
</summary>
|
||||||
|
<param name="archive">The archive to write.</param>
|
||||||
|
<param name="stream">A writable stream.</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.WriteEntry(System.IO.BinaryWriter,GsaEditor.Core.Models.GsaEntry,GsaEditor.Core.Models.GsaArchive)">
|
||||||
|
<summary>
|
||||||
|
Writes a single file entry at the current stream position.
|
||||||
|
</summary>
|
||||||
|
<param name="writer">The binary writer.</param>
|
||||||
|
<param name="entry">The entry to write.</param>
|
||||||
|
<param name="archive">The parent archive (used for NARD padding alignment).</param>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.IO.GsaWriter.WritePadding(System.IO.BinaryWriter,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Writes zero-bytes to pad the stream to the next aligned boundary.
|
||||||
|
</summary>
|
||||||
|
<param name="writer">The binary writer.</param>
|
||||||
|
<param name="alignment">The alignment boundary in bytes.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaArchive">
|
||||||
|
<summary>
|
||||||
|
Represents a loaded GSA archive with its global header information and file entries.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.Format">
|
||||||
|
<summary>
|
||||||
|
The format variant of this archive (NARC or NARD).
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.OffsetPadding">
|
||||||
|
<summary>
|
||||||
|
(NARD only) Byte alignment for data offsets. Zero or unused for NARC.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.SizePadding">
|
||||||
|
<summary>
|
||||||
|
(NARD only) Byte alignment for data sizes. Zero or unused for NARC.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaArchive.Entries">
|
||||||
|
<summary>
|
||||||
|
The ordered list of file entries in the archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaEntry">
|
||||||
|
<summary>
|
||||||
|
Represents a single file entry within a GSA archive.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.Alias">
|
||||||
|
<summary>
|
||||||
|
Relative path (alias) of the file within the archive, using '/' as separator.
|
||||||
|
Maximum 511 characters.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.IsCompressed">
|
||||||
|
<summary>
|
||||||
|
Whether this entry's data is zlib-compressed.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.OriginalLength">
|
||||||
|
<summary>
|
||||||
|
Original (decompressed) size of the file in bytes.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.CompressedLength">
|
||||||
|
<summary>
|
||||||
|
Compressed size of the file in bytes. Equal to <see cref="P:GsaEditor.Core.Models.GsaEntry.OriginalLength"/> if not compressed.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.RawData">
|
||||||
|
<summary>
|
||||||
|
Raw data as stored in the archive (compressed bytes if <see cref="P:GsaEditor.Core.Models.GsaEntry.IsCompressed"/> is true).
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:GsaEditor.Core.Models.GsaEntry.DataOffset">
|
||||||
|
<summary>
|
||||||
|
Byte offset of this entry's data block within the archive file.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Models.GsaEntry.GetDecompressedData">
|
||||||
|
<summary>
|
||||||
|
Returns the decompressed data for this entry.
|
||||||
|
If the entry is not compressed, returns <see cref="P:GsaEditor.Core.Models.GsaEntry.RawData"/> directly.
|
||||||
|
</summary>
|
||||||
|
<returns>The decompressed file content.</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:GsaEditor.Core.Models.GsaEntry.SetData(System.Byte[],System.Boolean)">
|
||||||
|
<summary>
|
||||||
|
Sets the entry data from uncompressed bytes, optionally compressing with zlib.
|
||||||
|
Updates <see cref="P:GsaEditor.Core.Models.GsaEntry.OriginalLength"/>, <see cref="P:GsaEditor.Core.Models.GsaEntry.CompressedLength"/>,
|
||||||
|
<see cref="P:GsaEditor.Core.Models.GsaEntry.IsCompressed"/>, and <see cref="P:GsaEditor.Core.Models.GsaEntry.RawData"/>.
|
||||||
|
</summary>
|
||||||
|
<param name="uncompressedData">The uncompressed file content.</param>
|
||||||
|
<param name="compress">Whether to compress the data with zlib.</param>
|
||||||
|
</member>
|
||||||
|
<member name="T:GsaEditor.Core.Models.GsaFormat">
|
||||||
|
<summary>
|
||||||
|
Identifies the archive format variant.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.Models.GsaFormat.NARC">
|
||||||
|
<summary>
|
||||||
|
Legacy format with no padding support. Magic word = 0x4E415243 ("NARC").
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:GsaEditor.Core.Models.GsaFormat.NARD">
|
||||||
|
<summary>
|
||||||
|
Enhanced format with offset and size padding for console DVD alignment.
|
||||||
|
Magic word = 0x4E415244 ("NARD").
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
</doc>
|
||||||
681
GsaEditor/bin/Debug/net8.0/GsaEditor.deps.json
Normal file
681
GsaEditor/bin/Debug/net8.0/GsaEditor.deps.json
Normal file
@ -0,0 +1,681 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v8.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v8.0": {
|
||||||
|
"GsaEditor/1.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Avalonia.Desktop": "11.3.6",
|
||||||
|
"Avalonia.Diagnostics": "11.3.6",
|
||||||
|
"Avalonia.Fonts.Inter": "11.3.6",
|
||||||
|
"Avalonia.Themes.Fluent": "11.3.6",
|
||||||
|
"CommunityToolkit.Mvvm": "8.2.1",
|
||||||
|
"GsaEditor.Core": "1.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"GsaEditor.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia.BuildServices": "0.0.31",
|
||||||
|
"Avalonia.Remote.Protocol": "11.3.6",
|
||||||
|
"MicroCom.Runtime": "0.11.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Base.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Controls.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.DesignerSupport.dll": {
|
||||||
|
"assemblyVersion": "0.7.0.0",
|
||||||
|
"fileVersion": "0.7.0.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Dialogs.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Markup.Xaml.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Markup.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Metal.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.MicroCom.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.OpenGL.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Vulkan.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Angle.Windows.Natives/2.1.25547.20250602": {
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/win-arm64/native/av_libglesv2.dll": {
|
||||||
|
"rid": "win-arm64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "2.1.25606.0"
|
||||||
|
},
|
||||||
|
"runtimes/win-x64/native/av_libglesv2.dll": {
|
||||||
|
"rid": "win-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "2.1.25606.0"
|
||||||
|
},
|
||||||
|
"runtimes/win-x86/native/av_libglesv2.dll": {
|
||||||
|
"rid": "win-x86",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "2.1.25606.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.BuildServices/0.0.31": {},
|
||||||
|
"Avalonia.Controls.ColorPicker/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Avalonia.Remote.Protocol": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Controls.ColorPicker.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Desktop/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Avalonia.Native": "11.3.6",
|
||||||
|
"Avalonia.Skia": "11.3.6",
|
||||||
|
"Avalonia.Win32": "11.3.6",
|
||||||
|
"Avalonia.X11": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Desktop.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Diagnostics/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Avalonia.Controls.ColorPicker": "11.3.6",
|
||||||
|
"Avalonia.Themes.Simple": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Diagnostics.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Fonts.Inter/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Fonts.Inter.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.FreeDesktop/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Tmds.DBus.Protocol": "0.21.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.FreeDesktop.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Native/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Native.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/osx/native/libAvaloniaNative.dylib": {
|
||||||
|
"rid": "osx",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Remote.Protocol/11.3.6": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Remote.Protocol.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Skia/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"HarfBuzzSharp": "8.3.1.1",
|
||||||
|
"HarfBuzzSharp.NativeAssets.Linux": "8.3.1.1",
|
||||||
|
"HarfBuzzSharp.NativeAssets.WebAssembly": "8.3.1.1",
|
||||||
|
"SkiaSharp": "2.88.9",
|
||||||
|
"SkiaSharp.NativeAssets.Linux": "2.88.9",
|
||||||
|
"SkiaSharp.NativeAssets.WebAssembly": "2.88.9"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Skia.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Themes.Fluent/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Themes.Fluent.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Themes.Simple/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Themes.Simple.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.Win32/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Avalonia.Angle.Windows.Natives": "2.1.25547.20250602"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.Win32.Automation.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Avalonia.Win32.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Avalonia.X11/11.3.6": {
|
||||||
|
"dependencies": {
|
||||||
|
"Avalonia": "11.3.6",
|
||||||
|
"Avalonia.FreeDesktop": "11.3.6",
|
||||||
|
"Avalonia.Skia": "11.3.6"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Avalonia.X11.dll": {
|
||||||
|
"assemblyVersion": "11.3.6.0",
|
||||||
|
"fileVersion": "11.3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CommunityToolkit.Mvvm/8.2.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/CommunityToolkit.Mvvm.dll": {
|
||||||
|
"assemblyVersion": "8.2.0.0",
|
||||||
|
"fileVersion": "8.2.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp/8.3.1.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"HarfBuzzSharp.NativeAssets.Win32": "8.3.1.1",
|
||||||
|
"HarfBuzzSharp.NativeAssets.macOS": "8.3.1.1"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/HarfBuzzSharp.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "8.3.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.Linux/8.3.1.1": {
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/linux-arm/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-arm",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-arm64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-arm64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-loongarch64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-loongarch64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-musl-arm/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-musl-arm",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-musl-arm64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-musl-arm64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-musl-loongarch64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-musl-loongarch64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-musl-riscv64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-musl-riscv64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-musl-x64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-musl-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-riscv64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-riscv64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-x64/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-x86/native/libHarfBuzzSharp.so": {
|
||||||
|
"rid": "linux-x86",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.macOS/8.3.1.1": {
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/osx/native/libHarfBuzzSharp.dylib": {
|
||||||
|
"rid": "osx",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.WebAssembly/8.3.1.1": {},
|
||||||
|
"HarfBuzzSharp.NativeAssets.Win32/8.3.1.1": {
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/win-arm64/native/libHarfBuzzSharp.dll": {
|
||||||
|
"rid": "win-arm64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/win-x64/native/libHarfBuzzSharp.dll": {
|
||||||
|
"rid": "win-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/win-x86/native/libHarfBuzzSharp.dll": {
|
||||||
|
"rid": "win-x86",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MicroCom.Runtime/0.11.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net5.0/MicroCom.Runtime.dll": {
|
||||||
|
"assemblyVersion": "0.11.0.0",
|
||||||
|
"fileVersion": "0.11.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SkiaSharp/2.88.9": {
|
||||||
|
"dependencies": {
|
||||||
|
"SkiaSharp.NativeAssets.Win32": "2.88.9",
|
||||||
|
"SkiaSharp.NativeAssets.macOS": "2.88.9"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/SkiaSharp.dll": {
|
||||||
|
"assemblyVersion": "2.88.0.0",
|
||||||
|
"fileVersion": "2.88.9.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.Linux/2.88.9": {
|
||||||
|
"dependencies": {
|
||||||
|
"SkiaSharp": "2.88.9"
|
||||||
|
},
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/linux-arm/native/libSkiaSharp.so": {
|
||||||
|
"rid": "linux-arm",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-arm64/native/libSkiaSharp.so": {
|
||||||
|
"rid": "linux-arm64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-musl-x64/native/libSkiaSharp.so": {
|
||||||
|
"rid": "linux-musl-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/linux-x64/native/libSkiaSharp.so": {
|
||||||
|
"rid": "linux-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.macOS/2.88.9": {
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/osx/native/libSkiaSharp.dylib": {
|
||||||
|
"rid": "osx",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.WebAssembly/2.88.9": {},
|
||||||
|
"SkiaSharp.NativeAssets.Win32/2.88.9": {
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/win-arm64/native/libSkiaSharp.dll": {
|
||||||
|
"rid": "win-arm64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/win-x64/native/libSkiaSharp.dll": {
|
||||||
|
"rid": "win-x64",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
},
|
||||||
|
"runtimes/win-x86/native/libSkiaSharp.dll": {
|
||||||
|
"rid": "win-x86",
|
||||||
|
"assetType": "native",
|
||||||
|
"fileVersion": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.IO.Pipelines/8.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.IO.Pipelines.dll": {
|
||||||
|
"assemblyVersion": "8.0.0.0",
|
||||||
|
"fileVersion": "8.0.23.53103"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Tmds.DBus.Protocol/0.21.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.IO.Pipelines": "8.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Tmds.DBus.Protocol.dll": {
|
||||||
|
"assemblyVersion": "0.21.2.0",
|
||||||
|
"fileVersion": "0.21.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GsaEditor.Core/1.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"GsaEditor.Core.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"GsaEditor/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
},
|
||||||
|
"Avalonia/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-h8V6Cmklaak2h+WVp5gcsprwmrCLQylfNmIQnOQyiETzOazKGmFhlcf+K9zoupxfkhOQXsbSJpb/elyCkrjlig==",
|
||||||
|
"path": "avalonia/11.3.6",
|
||||||
|
"hashPath": "avalonia.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Angle.Windows.Natives/2.1.25547.20250602": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ZL0VLc4s9rvNNFt19Pxm5UNAkmKNylugAwJPX9ulXZ6JWs/l6XZihPWWTyezaoNOVyEPU8YbURtW7XMAtqXH5A==",
|
||||||
|
"path": "avalonia.angle.windows.natives/2.1.25547.20250602",
|
||||||
|
"hashPath": "avalonia.angle.windows.natives.2.1.25547.20250602.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.BuildServices/0.0.31": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-KmCN6Hc+45q4OnF10ge450yVUvWuxU6bdQiyKqiSvrHKpahNrEdk0kG6Ip6GHk2SKOCttGQuA206JVdkldEENg==",
|
||||||
|
"path": "avalonia.buildservices/0.0.31",
|
||||||
|
"hashPath": "avalonia.buildservices.0.0.31.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Controls.ColorPicker/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-zgJFM7P7hOS9qcuSUqL2tjBFIsi03qiwAztYjtjtKjfBvLBOrVcmL5qHwjfjeLRLtjHprjMraAHtu3O2vi+51g==",
|
||||||
|
"path": "avalonia.controls.colorpicker/11.3.6",
|
||||||
|
"hashPath": "avalonia.controls.colorpicker.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Desktop/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-+r/3tPkf2E7rxbKYYd5bRq9vdaPCvuhf+PAKFmKqdQrzjWPSP4/Dg0VDIcgPsv+A2Tq82x67ZUJGQyhiLfsMVQ==",
|
||||||
|
"path": "avalonia.desktop/11.3.6",
|
||||||
|
"hashPath": "avalonia.desktop.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Diagnostics/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-iOGrfU/0TudsfHARpsreVt5V1oaer838IghYdgZjlOvGKmh5J1uc5GOSBmBodUpuPDE78wmdVrJZLC57qsndzg==",
|
||||||
|
"path": "avalonia.diagnostics/11.3.6",
|
||||||
|
"hashPath": "avalonia.diagnostics.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Fonts.Inter/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ASuCuosS8elNsRxNpdZE/UrmMSh1wtwoqRDEgpdcbVuMRUH/8ElCur5PdyWhJSwIit/YXaS+xb2xQAGOnvT45w==",
|
||||||
|
"path": "avalonia.fonts.inter/11.3.6",
|
||||||
|
"hashPath": "avalonia.fonts.inter.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.FreeDesktop/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-vI+m3r5GRoF0CS1EpMG+gx9qCQVkCwlsHyW6+UWxNNpL2IZ9S3zPt/GNgG6vFrhZpBTsoG+OZnZ9XPRT+KkqUw==",
|
||||||
|
"path": "avalonia.freedesktop/11.3.6",
|
||||||
|
"hashPath": "avalonia.freedesktop.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Native/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-QaIKZLfquJ12Rpwo9pQ2V24TfpJI2tQJeYe235G4cJnI3d699DvMUcntQA1VLDCwAK3xHOSTyoN737oU3f9ErQ==",
|
||||||
|
"path": "avalonia.native/11.3.6",
|
||||||
|
"hashPath": "avalonia.native.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Remote.Protocol/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-i1lW1ZA5Ghe19kbPiC2pMq48P/9CcOWi8hFj0Y8UZr/czCQok3YJRwU5z8vKsJ5xsMvzpPf+GO/6Qu9Dy2yLvg==",
|
||||||
|
"path": "avalonia.remote.protocol/11.3.6",
|
||||||
|
"hashPath": "avalonia.remote.protocol.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Skia/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-qM1qGCUxPpheLwQ6gr+I4h2dhFz3L9Apa1MhiUsUELzSI6FqfzX2+67M8f/6gOHAtrjwxkjpanNW6eF9zW+g7Q==",
|
||||||
|
"path": "avalonia.skia/11.3.6",
|
||||||
|
"hashPath": "avalonia.skia.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Themes.Fluent/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-YQ3x66qgMqDxbQoLTqYKGA7yXnxi8fzDL9+CITPrXrVZimlemWmjYqE0NWgd2c78gpp7dNEV4eYzwbbr8bH+9A==",
|
||||||
|
"path": "avalonia.themes.fluent/11.3.6",
|
||||||
|
"hashPath": "avalonia.themes.fluent.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Themes.Simple/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-MuwYjUI9qMdu7TYyJbBntWlZRJWi6QmAYhMEBEdDgiEO0yUpTiKNLpYSn+MS8Xh9Jm9N4krclBigW7FYJkqLOA==",
|
||||||
|
"path": "avalonia.themes.simple/11.3.6",
|
||||||
|
"hashPath": "avalonia.themes.simple.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.Win32/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-KHft8utwz5ktcwHdEAwAPfNGdwqoE0qrwVmPjGYggqJAI4PuaiATkdSObSCXfxRVs2H9aqVB8hiEVB+9rvkxYw==",
|
||||||
|
"path": "avalonia.win32/11.3.6",
|
||||||
|
"hashPath": "avalonia.win32.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Avalonia.X11/11.3.6": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-6NKE1py4zbkMJg++2rMJHyjEXNo2JU0Nj9nEMnXh2gyHyfclhULIGIqmpsLvJ7Ljv1+6iLDqArCFRn+mi1hANw==",
|
||||||
|
"path": "avalonia.x11/11.3.6",
|
||||||
|
"hashPath": "avalonia.x11.11.3.6.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"CommunityToolkit.Mvvm/8.2.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-I24ofWVEdplxYjUez9/bljv/qb8r8Ccj6cvYXHexNBegLaD3iDy3QrzAAOYVMmfGWIXxlU1ZtECQNfU07+6hXQ==",
|
||||||
|
"path": "communitytoolkit.mvvm/8.2.1",
|
||||||
|
"hashPath": "communitytoolkit.mvvm.8.2.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp/8.3.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-tLZN66oe/uiRPTZfrCU4i8ScVGwqHNh5MHrXj0yVf4l7Mz0FhTGnQ71RGySROTmdognAs0JtluHkL41pIabWuQ==",
|
||||||
|
"path": "harfbuzzsharp/8.3.1.1",
|
||||||
|
"hashPath": "harfbuzzsharp.8.3.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.Linux/8.3.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-3EZ1mpIiKWRLL5hUYA82ZHteeDIVaEA/Z0rA/wU6tjx6crcAkJnBPwDXZugBSfo8+J3EznvRJf49uMsqYfKrHg==",
|
||||||
|
"path": "harfbuzzsharp.nativeassets.linux/8.3.1.1",
|
||||||
|
"hashPath": "harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.macOS/8.3.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-jbtCsgftcaFLCA13tVKo5iWdElJScrulLTKJre36O4YQTIlwDtPPqhRZNk+Y0vv4D1gxbscasGRucUDfS44ofQ==",
|
||||||
|
"path": "harfbuzzsharp.nativeassets.macos/8.3.1.1",
|
||||||
|
"hashPath": "harfbuzzsharp.nativeassets.macos.8.3.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.WebAssembly/8.3.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-loJweK2u/mH/3C2zBa0ggJlITIszOkK64HLAZB7FUT670dTg965whLFYHDQo69NmC4+d9UN0icLC9VHidXaVCA==",
|
||||||
|
"path": "harfbuzzsharp.nativeassets.webassembly/8.3.1.1",
|
||||||
|
"hashPath": "harfbuzzsharp.nativeassets.webassembly.8.3.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"HarfBuzzSharp.NativeAssets.Win32/8.3.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-UsJtQsfAJoFDZrXc4hCUfRPMqccfKZ0iumJ/upcUjz/cmsTgVFGNEL5yaJWmkqsuFYdMWbj/En5/kS4PFl9hBA==",
|
||||||
|
"path": "harfbuzzsharp.nativeassets.win32/8.3.1.1",
|
||||||
|
"hashPath": "harfbuzzsharp.nativeassets.win32.8.3.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"MicroCom.Runtime/0.11.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA==",
|
||||||
|
"path": "microcom.runtime/0.11.0",
|
||||||
|
"hashPath": "microcom.runtime.0.11.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SkiaSharp/2.88.9": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-3MD5VHjXXieSHCleRLuaTXmL2pD0mB7CcOB1x2kA1I4bhptf4e3R27iM93264ZYuAq6mkUyX5XbcxnZvMJYc1Q==",
|
||||||
|
"path": "skiasharp/2.88.9",
|
||||||
|
"hashPath": "skiasharp.2.88.9.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.Linux/2.88.9": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-cWSaJKVPWAaT/WIn9c8T5uT/l4ETwHxNJTkEOtNKjphNo8AW6TF9O32aRkxqw3l8GUdUo66Bu7EiqtFh/XG0Zg==",
|
||||||
|
"path": "skiasharp.nativeassets.linux/2.88.9",
|
||||||
|
"hashPath": "skiasharp.nativeassets.linux.2.88.9.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.macOS/2.88.9": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Nv5spmKc4505Ep7oUoJ5vp3KweFpeNqxpyGDWyeEPTX2uR6S6syXIm3gj75dM0YJz7NPvcix48mR5laqs8dPuA==",
|
||||||
|
"path": "skiasharp.nativeassets.macos/2.88.9",
|
||||||
|
"hashPath": "skiasharp.nativeassets.macos.2.88.9.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.WebAssembly/2.88.9": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-kt06RccBHSnAs2wDYdBSfsjIDbY3EpsOVqnlDgKdgvyuRA8ZFDaHRdWNx1VHjGgYzmnFCGiTJBnXFl5BqGwGnA==",
|
||||||
|
"path": "skiasharp.nativeassets.webassembly/2.88.9",
|
||||||
|
"hashPath": "skiasharp.nativeassets.webassembly.2.88.9.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SkiaSharp.NativeAssets.Win32/2.88.9": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-wb2kYgU7iy84nQLYZwMeJXixvK++GoIuECjU4ECaUKNuflyRlJKyiRhN1MAHswvlvzuvkrjRWlK0Za6+kYQK7w==",
|
||||||
|
"path": "skiasharp.nativeassets.win32/2.88.9",
|
||||||
|
"hashPath": "skiasharp.nativeassets.win32.2.88.9.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.IO.Pipelines/8.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA==",
|
||||||
|
"path": "system.io.pipelines/8.0.0",
|
||||||
|
"hashPath": "system.io.pipelines.8.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Tmds.DBus.Protocol/0.21.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ScSMrUrrw8px4kK1Glh0fZv/HQUlg1078bNXNPfRPKQ3WbRzV9HpsydYEOgSoMK5LWICMf2bMwIFH0pGjxjcMA==",
|
||||||
|
"path": "tmds.dbus.protocol/0.21.2",
|
||||||
|
"hashPath": "tmds.dbus.protocol.0.21.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"GsaEditor.Core/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.exe
Normal file
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.exe
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.pdb
Normal file
BIN
GsaEditor/bin/Debug/net8.0/GsaEditor.pdb
Normal file
Binary file not shown.
13
GsaEditor/bin/Debug/net8.0/GsaEditor.runtimeconfig.json
Normal file
13
GsaEditor/bin/Debug/net8.0/GsaEditor.runtimeconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net8.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "8.0.0"
|
||||||
|
},
|
||||||
|
"configProperties": {
|
||||||
|
"System.Runtime.InteropServices.BuiltInComInterop.IsSupported": true,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
GsaEditor/bin/Debug/net8.0/HarfBuzzSharp.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/HarfBuzzSharp.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/MicroCom.Runtime.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/MicroCom.Runtime.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/SkiaSharp.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/SkiaSharp.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/System.IO.Pipelines.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/System.IO.Pipelines.dll
Normal file
Binary file not shown.
BIN
GsaEditor/bin/Debug/net8.0/Tmds.DBus.Protocol.dll
Normal file
BIN
GsaEditor/bin/Debug/net8.0/Tmds.DBus.Protocol.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user