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]); } } }