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