using System.Text;
using GsaEditor.Core.Models;
namespace GsaEditor.Core.IO;
///
/// Writes a to a binary .gsa stream or file.
/// Supports both NARC (legacy) and NARD (enhanced with padding) format variants.
///
public static class GsaWriter
{
/// Magic word for the NARC (legacy) format.
private const uint MAGIC_NARC = 0x4E415243;
/// Magic word for the NARD (enhanced) format.
private const uint MAGIC_NARD = 0x4E415244;
///
/// Writes the archive to the specified file path.
///
/// The archive to write.
/// The output file path.
public static void Write(GsaArchive archive, string filePath)
{
using var stream = File.Create(filePath);
Write(archive, stream);
}
///
/// Writes the archive to the given stream.
/// Each entry's is updated to reflect the new position in the output.
///
/// The archive to write.
/// A writable stream.
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);
}
}
///
/// Writes a single file entry at the current stream position.
///
/// The binary writer.
/// The entry to write.
/// The parent archive (used for NARD padding alignment).
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);
}
///
/// Writes zero-bytes to pad the stream to the next aligned boundary.
///
/// The binary writer.
/// The alignment boundary in bytes.
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]);
}
}
}