mirror of
https://github.com/kwsch/pkNX.git
synced 2026-04-19 08:07:47 -05:00
Reduce allocation of button string fetch (for fun!) Type return covariance for Editors (no longer limited by netframework!)
173 lines
5.2 KiB
C#
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);
|
|
}
|
|
}
|