pkNX/pkNX.Containers/SARC/SARC.cs
Kurt efb34f556b Minor tweaks
Reduce allocation of button string fetch (for fun!)
Type return covariance for Editors (no longer limited by netframework!)
2022-10-02 10:24:44 -07:00

173 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace pkNX.Containers;
/// <summary>
/// Simple (?) ARChive
/// </summary>
public sealed class SARC : LargeContainer
{
private const string Identifier = nameof(SARC);
public SARCHeader Header { get; set; }
public SFAT SFAT;
public SFNT SFNT;
public override int Count => SFAT.Entries.Count;
/// <summary>
/// The required <see cref="SARCHeader.Magic"/> matches the first 4 bytes of the file data.
/// </summary>
public bool SigMatches => Header.Magic == Identifier;
/// <summary>
/// Initializes an empty <see cref="SARC"/>.
/// </summary>
/// <param name="files">Files inside the <see cref="SARC"/>.</param>
/// <param name="baseFolder">Root location of the archive</param>
public SARC(IReadOnlyList<string> files, string baseFolder)
{
Header = new SARCHeader();
SFAT = new SFAT(baseFolder, files);
SFNT = new SFNT();
Files = new byte[]?[SFAT.Entries.Count];
}
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
/// <summary>
/// Initializes a <see cref="SARC"/> from a file location.
/// </summary>
/// <param name="path"></param>
public SARC(string path) => OpenBinary(path);
/// <summary>
/// Initializes a <see cref="SARC"/> from a provided stream.
/// </summary>
/// <param name="fs"></param>
public SARC(Stream fs) => OpenRead(new BinaryReader(fs));
/// <summary>
/// Initializes a <see cref="SARC"/> from a provided reader.
/// </summary>
/// <param name="br"></param>
public SARC(BinaryReader br) => OpenRead(br);
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
/// <summary>
/// Reads the contents of the <see cref="SARC"/> header and file info tables.
/// </summary>
protected override void Initialize()
{
if (Reader is null)
throw new NullReferenceException(nameof(Reader));
Header = new SARCHeader(Reader);
if (!SigMatches)
return;
SFAT = new SFAT(Reader, Header.DataOffset);
SFNT = new SFNT(Reader);
}
protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
{
return Task.Run(() => PackSARC(bw, handler, token), token);
}
private void PackSARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
{
StartPack();
var count = SFAT.Entries.Count;
handler.Initialize(count);
var currentStringOffset = SFNT.StringOffset;
WriteIntro(bw);
for (int i = 0; i < count; i++)
{
var entr = SFAT.Entries[i];
if (entr.FileName == null)
entr.GetFileName(Reader!.BaseStream, (int)currentStringOffset);
entr.WriteFileName(Reader!.BaseStream, (int)SFNT.StringOffset);
while (Reader.BaseStream.Position % 4 != 0)
bw.Write((byte)0);
}
for (int i = 0; i < count; i++)
{
if (token.IsCancellationRequested)
return;
WriteEntry(bw, i);
}
WriteIntro(bw, true);
bw.Flush();
}
private static void StartPack() { } // do nothing?
private void WriteIntro(BinaryWriter bw, bool finalPass = false)
{
Header.Write(bw);
SFAT.Write(bw);
SFNT.Write(bw);
if (finalPass)
Header.DataOffset = (int)bw.BaseStream.Position;
}
private void WriteEntry(BinaryWriter bw, int i)
{
throw new NotImplementedException();
}
protected override int GetFileOffset(int file, int subFile = 0)
{
var f = SFAT[file];
return Header.DataOffset + f.Start;
}
public override byte[] GetEntry(int index, int subFile)
{
var f = SFAT[index];
if (f.File is byte[] data)
return data;
data = f.GetFileData(Reader!.BaseStream);
f.File = data; // cache for future fetches
return data;
}
public override void Dump(string? path, ContainerHandler handler)
{
path ??= FilePath;
if (path == null)
throw new ArgumentNullException(nameof(path));
if (File.Exists(path))
path = Path.GetDirectoryName(path);
if (path == null)
throw new ArgumentNullException(nameof(path));
var folder = FileName ?? Identifier;
string dir = Path.Combine(path, folder);
Directory.CreateDirectory(dir);
var count = SFAT.Entries.Count;
handler.Initialize(count);
for (int i = 0; i < count; i++)
{
SFAT.Entries[i].Dump(Reader!.BaseStream, path, Header.DataOffset);
handler.StepFile(i + 1);
}
}
public static SARC? GetSARC(BinaryReader br)
{
if (br.BaseStream.Length < 20)
return null;
br.BaseStream.Position = 0;
var ident = new string(br.ReadChars(4));
if (ident != SARCHeader.Identifier)
return null;
return new SARC(br);
}
}