diff --git a/pk3DS.Core/CTR/SARC.cs b/pk3DS.Core/CTR/SARC.cs index 80bfeb9..7ef4f79 100644 --- a/pk3DS.Core/CTR/SARC.cs +++ b/pk3DS.Core/CTR/SARC.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Text; namespace pk3DS.Core.CTR { - public class SARC + /// + /// Simple (?) ARChive + /// + public class SARC : IDisposable { private const string Identifier = nameof(SARC); @@ -24,28 +29,61 @@ public class SARC public string Extension; public readonly bool Valid; + /// + /// The required matches the first 4 bytes of the file data. + /// public bool SigMatches => Magic == Identifier; + private readonly Stream stream; + private readonly BinaryReader br; + /// + /// Initializes an empty . + /// public SARC() { SFAT = new SFAT(); SFNT = new SFNT(); } + /// + /// Initializes a from a file location. + /// + /// public SARC(string path) { - FileName = Path.GetFileNameWithoutExtension(path); - FilePath = Path.GetDirectoryName(path); - Extension = Path.GetExtension(path); + SetFileInfo(path); - using (var br = new BinaryReader(File.OpenRead(path))) - { - ReadHeader(br); - SFAT = new SFAT(br); - SFNT = new SFNT(br); - } + stream = File.OpenRead(path); + br = new BinaryReader(stream); + ReadSARC(); Valid = true; } - private void ReadHeader(BinaryReader br) + /// + /// Initializes a from a provided stream. + /// + /// + public SARC(Stream fs) + { + stream = fs; + br = new BinaryReader(stream); + ReadSARC(); + Valid = true; + } + /// + /// Initializes a from a provided array. + /// + /// + public SARC(byte[] data) + { + stream = new MemoryStream(data); + br = new BinaryReader(stream); + ReadSARC(); + Valid = true; + } + + /// + /// Reads the contents of the header and file info tables. + /// + private void ReadSARC() { Magic = new string(br.ReadChars(4)); if (!SigMatches) @@ -56,11 +94,119 @@ private void ReadHeader(BinaryReader br) FileSize = br.ReadUInt32(); DataOffset = br.ReadUInt32(); Unknown = br.ReadUInt32(); + + SFAT = new SFAT(br); + SFNT = new SFNT(br); + } + + /// + /// Sets File information for the original file. + /// + /// + public void SetFileInfo(string path) + { + FileName = Path.GetFileNameWithoutExtension(path); + FilePath = Path.GetDirectoryName(path); + Extension = Path.GetExtension(path); + } + + /// + /// Gets the entry filename for a given . + /// + /// Entry to fetch data for + /// File Name + public string GetFileName(SFATEntry entry) => GetFileName(entry.FileNameOffset); + + /// + /// Gets the entry data for a given , + /// + /// Entry to fetch data for + /// Data array + public byte[] GetData(SFATEntry entry) => GetData(entry.FileDataStart, entry.FileDataLength); + /// + /// Overwrites the entry data, assuming the size is the exact same. + /// + /// File entry to overwrite + /// Data to write + public void SetData(SFATEntry entry, byte[] data) + { + if (data.Length != entry.FileDataLength) + throw new ArgumentException(nameof(data.Length)); + SetData(entry.FileDataStart, data); + } + /// + /// Exports the entry data for a given at a provided path with its assigned file name via the name table. + /// + /// Entry to export + /// Path to export to. If left null, will output to the FilePath, if it is assigned. + public void ExportFile(SFATEntry t, string outpath = null) + { + outpath = outpath ?? FilePath; + byte[] data = GetData(t); + string name = GetFileName(t); + + string dir = Path.GetDirectoryName(name); + if (dir == null) + throw new ArgumentException(name); + string location = Path.Combine(outpath, dir); + Directory.CreateDirectory(location); + + var filepath = Path.Combine(outpath, name); + File.WriteAllBytes(filepath, data); + } + + private string GetFileName(int offset) + { + stream.Seek(SFNT.StringOffset, SeekOrigin.Begin); + stream.Seek((offset & 0x00FFFFFF) * 4, SeekOrigin.Current); + StringBuilder sb = new StringBuilder(); + for (char c = (char)stream.ReadByte(); c != 0; c = (char)stream.ReadByte()) + sb.Append(c); + + string name = sb.ToString().Replace('/', Path.DirectorySeparatorChar); + return name; + } + private void SetFileName(int offset, string value) + { + var str = value.Replace(Path.DirectorySeparatorChar, '/'); + stream.Seek(SFNT.StringOffset, SeekOrigin.Begin); + stream.Seek((offset & 0x00FFFFFF) * 4, SeekOrigin.Current); + foreach (var b in str) + stream.WriteByte((byte)b); + stream.WriteByte((byte)'\0'); + } + private byte[] GetData(int offset, int length) + { + byte[] fileBuffer = new byte[length]; + stream.Seek(offset + DataOffset, SeekOrigin.Begin); + stream.Read(fileBuffer, 0, length); + return fileBuffer; + } + private void SetData(int offset, byte[] data) + { + stream.Seek(offset + DataOffset, SeekOrigin.Begin); + stream.Write(data, 0, data.Length); + } + + /// + /// Disposes of the and objects and frees the if originally loaded from that location. + /// + public void Dispose() + { + stream?.Dispose(); + br?.Dispose(); } } + + /// + /// File Access Table + /// public class SFAT { public const string Identifier = nameof(SFAT); + /// + /// The required matches the first 4 bytes of the file data. + /// public bool SigMatches => Magic == Identifier; public string Magic; @@ -85,9 +231,15 @@ public SFAT(BinaryReader br) Entries.Add(new SFATEntry(br)); } } + /// + /// File Name Table + /// public class SFNT { public const string Identifier = nameof(SFNT); + /// + /// The required matches the first 4 bytes of the file data. + /// public bool SigMatches => Magic == Identifier; public string Magic; @@ -107,19 +259,24 @@ public SFNT(BinaryReader br) StringOffset = (uint)br.BaseStream.Position; } } + /// + /// File Access Table () Entry + /// public class SFATEntry { public uint FileNameHash; - public uint FileNameOffset; - public uint FileDataStart; - public uint FileDataEnd; + public int FileNameOffset; + public int FileDataStart; + public int FileDataEnd; + + public int FileDataLength => FileDataEnd - FileDataStart; public SFATEntry(BinaryReader br) { FileNameHash = br.ReadUInt32(); - FileNameOffset = br.ReadUInt32(); - FileDataStart = br.ReadUInt32(); - FileDataEnd = br.ReadUInt32(); + FileNameOffset = br.ReadInt32(); + FileDataStart = br.ReadInt32(); + FileDataEnd = br.ReadInt32(); } } } \ No newline at end of file diff --git a/pk3DS/ARCUtil.cs b/pk3DS/ARCUtil.cs index d33ea4d..a84c6d6 100644 --- a/pk3DS/ARCUtil.cs +++ b/pk3DS/ARCUtil.cs @@ -385,7 +385,7 @@ internal static string Interpret(string path) UnpackShuffleARC(path, sharc, ret); } else if (sarc.Valid) - UnpackSARC(path, sarc); + UnpackSARC(sarc); else { ret = "Not a valid .DARC/.FARC/.SARC/.GAR/Shuffle Archive file"; @@ -421,43 +421,16 @@ private static bool UnpackShuffleARC(string path, ShuffleARC sharc, string ret) return true; } - private static bool UnpackSARC(string path, SARC sarc) + public static bool UnpackSARC(SARC sarc) { Debug.WriteLine($"New SARC with {sarc.SFAT.EntryCount} files."); - string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + sarc.FileName + "_" + Path.DirectorySeparatorChar; - if (!Directory.Exists(dir)) - { - Debug.WriteLine($"Making dir: {dir}"); - Directory.CreateDirectory(dir); - } - var fs = File.OpenRead(path); + string outfolder = $"{sarc.FileName}_sarc"; + string outpath = Path.Combine(sarc.FilePath, outfolder); + Directory.CreateDirectory(outpath); + foreach (SFATEntry t in sarc.SFAT.Entries) - { - // Read File Data - uint FileLen = t.FileDataEnd - t.FileDataStart; - fs.Seek(t.FileDataStart + sarc.DataOffset, SeekOrigin.Begin); - byte[] fileBuffer = new byte[FileLen]; - fs.Read(fileBuffer, 0, (int)FileLen); - - // Read File Name - fs.Seek(sarc.SFNT.StringOffset, SeekOrigin.Begin); - fs.Seek((t.FileNameOffset & 0x00FFFFFF) * 4, SeekOrigin.Current); - StringBuilder sb = new StringBuilder(); - for (char c = (char)fs.ReadByte(); c != 0; c = (char)fs.ReadByte()) - sb.Append(c); - - // Write File - string FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar); - string FileDir = Path.GetDirectoryName(dir + FileName) + Path.DirectorySeparatorChar; - if (!Directory.Exists(FileDir)) - { - Console.WriteLine($"Making dir: {FileDir}"); - Directory.CreateDirectory(FileDir); - } - File.WriteAllBytes(dir + FileName, fileBuffer); - } - fs.Close(); + sarc.ExportFile(t, outpath); return true; }