diff --git a/pk3DS.Core/CTR/ALYT.cs b/pk3DS.Core/CTR/ALYT.cs
index a61734c..01b6624 100644
--- a/pk3DS.Core/CTR/ALYT.cs
+++ b/pk3DS.Core/CTR/ALYT.cs
@@ -1,12 +1,240 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Collections.Generic;
+using System.IO;
using System.Text;
-namespace CTR
+namespace pk3DS.Core.CTR
{
+ ///
+ /// Archive LaYouT container
+ ///
+ /// File length is padded to nearest 0x80
public class ALYT
{
+ private const string Identifier = nameof(ALYT);
+ public string Magic; // 0x00
+ public short unk4; // 0x04
+ public short unk6; // 0x06
+ public int LTBLOffset; // 0x08
+ public int LTBLSize; // 0x0C
+ public int LMTLOffset; // 0x10
+ public int LMTLSize; // 0x14
+ public int LFNLOffset; // 0x18
+ public int LFNLSize; // 0x1C
+ public int DataOffset; // 0x20
+ public int DataSize; // 0x24
+
+ public LTBL LTBL;
+ public LMTL LMTL;
+ public LFNL LFNL;
+ public Contents Content;
+ public byte[] Data;
+
+ public string FileName { get; }
+ public string FilePath { get; }
+ public string Extension { get; }
+ public bool SigMatches => Magic == Identifier;
+
+ public ALYT(string path)
+ {
+ FileName = Path.GetFileNameWithoutExtension(path);
+ FilePath = Path.GetDirectoryName(path);
+ Extension = Path.GetExtension(path);
+
+ using (var br = new BinaryReader(File.OpenRead(path)))
+ ReadALYT(br);
+ }
+
+ public ALYT(byte[] data)
+ {
+ using (var ms = new MemoryStream(data))
+ using (var br = new BinaryReader(ms))
+ ReadALYT(br);
+ }
+
+ public ALYT(Stream ms)
+ {
+ using (var br = new BinaryReader(ms))
+ ReadALYT(br);
+ }
+
+ private void ReadALYT(BinaryReader br)
+ {
+ ReadHeader(br);
+ LTBL = new LTBL(br, (LTBLSize - 8) / 4);
+ LMTL = new LMTL(br, (LMTLSize - 8) / 4);
+ LFNL = new LFNL(br, (LFNLSize - 8) / 4);
+ Content = new Contents(br, this);
+ }
+
+ private void ReadHeader(BinaryReader br)
+ {
+ Magic = new string(br.ReadChars(4));
+ if (!SigMatches)
+ return;
+ unk4 = br.ReadInt16();
+ unk6 = br.ReadInt16();
+ LTBLOffset = br.ReadInt32();
+ LTBLSize = br.ReadInt32();
+ LMTLOffset = br.ReadInt32();
+ LMTLSize = br.ReadInt32();
+ LFNLOffset = br.ReadInt32();
+ LFNLSize = br.ReadInt32();
+ DataOffset = br.ReadInt32();
+ DataSize = br.ReadInt32();
+ }
+
+ public class Contents
+ {
+ public readonly string[] Labels;
+ public readonly string[] Symbols;
+
+ public Contents(BinaryReader br, ALYT alyt)
+ {
+ br.BaseStream.Position = alyt.DataOffset;
+ Labels = new string[br.ReadInt32()];
+ for (int i = 0; i < Labels.Length; i++)
+ Labels[i] = ReadString(br, 0x40);
+
+ Symbols = new string[br.ReadInt32()];
+ for (int i = 0; i < Symbols.Length; i++)
+ Symbols[i] = ReadString(br, 0x20);
+
+ // skip to end of section
+ // this is bad
+ while (br.PeekChar() == 0)
+ br.ReadByte();
+
+ int len = (int)br.BaseStream.Position - alyt.DataOffset;
+ len = alyt.DataSize - len;
+ alyt.Data = br.ReadBytes(len);
+ }
+
+ public static string ReadString(BinaryReader br, int skip)
+ {
+ var sb = new StringBuilder();
+ var ofs = br.BaseStream.Position;
+ while (true)
+ {
+ var c = (char)br.ReadByte();
+ if (c == 0)
+ {
+ br.BaseStream.Position = ofs + skip;
+ return sb.ToString();
+ }
+ sb.Append(c);
+ }
+ }
+ }
+
+ ///
+ /// Rips out the data portion of the ALYT, assuming the ALYT is partially valid.
+ ///
+ public static byte[] GetData(string path)
+ {
+ using (var br = new BinaryReader(File.OpenRead(path)))
+ return GetData(br);
+ }
+ ///
+ /// Rips out the data portion of the ALYT, assuming the ALYT is partially valid.
+ ///
+ public static byte[] GetData(byte[] data)
+ {
+ using (var ms = new MemoryStream(data))
+ using (var br = new BinaryReader(ms))
+ return GetData(br);
+ }
+ ///
+ /// Rips out the data portion of the ALYT, assuming the ALYT is partially valid.
+ ///
+ public static byte[] GetData(Stream ms)
+ {
+ using (var br = new BinaryReader(ms))
+ return GetData(br);
+ }
+ private static byte[] GetData(BinaryReader br)
+ {
+ if (br.BaseStream.Length <= 0x80)
+ return null;
+ if (Identifier != new string(br.ReadChars(4)))
+ return null; // not ALYT
+
+ br.BaseStream.Position = 0x20; // DataOffset
+ int start = br.ReadInt32();
+ int length = br.ReadInt32();
+
+ br.BaseStream.Position = start;
+ int count40 = br.ReadInt32();
+ br.BaseStream.Position += count40 * 0x40;
+
+ int count20 = br.ReadInt32();
+ br.BaseStream.Position += count20 * 0x20;
+
+ // skip to end of section
+ // this is bad
+ while (br.PeekChar() == 0)
+ br.ReadByte();
+
+ int len = (int)br.BaseStream.Position - start;
+ len = length - len;
+ return br.ReadBytes(len);
+ }
+ }
+
+ public class LTBL
+ {
+ private const string Identifier = nameof(LTBL);
+ public string Magic; // 0x00
+ public short unk4; // 0x04
+ public short unk6; // 0x06
+ public List Values;
+
+ public LTBL(BinaryReader br, int count)
+ {
+ Magic = new string(br.ReadChars(Identifier.Length));
+ unk4 = br.ReadInt16();
+ unk6 = br.ReadInt16();
+ Values = new List(count);
+ for (int i = 0; i < count; i++)
+ Values.Add((short)br.ReadInt32());
+ }
+ }
+
+ public class LMTL
+ {
+ private const string Identifier = nameof(LMTL);
+ public string Magic; // 0x00
+ public short unk4; // 0x04
+ public short unk6; // 0x06
+ public List Values;
+
+ public LMTL(BinaryReader br, int count)
+ {
+ Magic = new string(br.ReadChars(Identifier.Length));
+ unk4 = br.ReadInt16();
+ unk6 = br.ReadInt16();
+ Values = new List(count);
+ for (int i = 0; i < count; i++)
+ Values.Add((short)br.ReadInt32());
+ }
+ }
+
+ public class LFNL
+ {
+ private const string Identifier = nameof(LFNL);
+ public string Magic; // 0x00
+ public short unk4; // 0x04
+ public short unk6; // 0x06
+ public List Values;
+
+ public LFNL(BinaryReader br, int count)
+ {
+ Magic = new string(br.ReadChars(Identifier.Length));
+ unk4 = br.ReadInt16();
+ unk6 = br.ReadInt16();
+ Values = new List(count);
+ for (int i = 0; i < count; i++)
+ Values.Add((short)br.ReadInt32());
+ }
}
}
diff --git a/pk3DS.Core/CTR/SARC.cs b/pk3DS.Core/CTR/SARC.cs
new file mode 100644
index 0000000..80bfeb9
--- /dev/null
+++ b/pk3DS.Core/CTR/SARC.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace pk3DS.Core.CTR
+{
+ public class SARC
+ {
+ private const string Identifier = nameof(SARC);
+
+ public string Magic;
+ public ushort HeaderSize;
+ public ushort Endianness;
+ public uint FileSize;
+ public uint DataOffset;
+ public uint Unknown;
+
+ public SFAT SFAT;
+ public SFNT SFNT;
+
+ // Assigned Properties
+ public string FileName;
+ public string FilePath;
+ public string Extension;
+ public readonly bool Valid;
+
+ public bool SigMatches => Magic == Identifier;
+
+ public SARC()
+ {
+ SFAT = new SFAT();
+ SFNT = new SFNT();
+ }
+ public SARC(string path)
+ {
+ FileName = Path.GetFileNameWithoutExtension(path);
+ FilePath = Path.GetDirectoryName(path);
+ Extension = Path.GetExtension(path);
+
+ using (var br = new BinaryReader(File.OpenRead(path)))
+ {
+ ReadHeader(br);
+ SFAT = new SFAT(br);
+ SFNT = new SFNT(br);
+ }
+ Valid = true;
+ }
+ private void ReadHeader(BinaryReader br)
+ {
+ Magic = new string(br.ReadChars(4));
+ if (!SigMatches)
+ return;
+
+ HeaderSize = br.ReadUInt16();
+ Endianness = br.ReadUInt16();
+ FileSize = br.ReadUInt32();
+ DataOffset = br.ReadUInt32();
+ Unknown = br.ReadUInt32();
+ }
+ }
+ public class SFAT
+ {
+ public const string Identifier = nameof(SFAT);
+ public bool SigMatches => Magic == Identifier;
+
+ public string Magic;
+ public ushort HeaderSize;
+ public ushort EntryCount;
+ public uint HashMult;
+ public List Entries;
+
+ public SFAT() { }
+ public SFAT(BinaryReader br)
+ {
+ Magic = new string(br.ReadChars(4));
+ if (!SigMatches)
+ throw new FormatException(nameof(SFAT));
+
+ HeaderSize = br.ReadUInt16();
+ EntryCount = br.ReadUInt16();
+ HashMult = br.ReadUInt32();
+ Entries = new List();
+
+ for (int i = 0; i < EntryCount; i++)
+ Entries.Add(new SFATEntry(br));
+ }
+ }
+ public class SFNT
+ {
+ public const string Identifier = nameof(SFNT);
+ public bool SigMatches => Magic == Identifier;
+
+ public string Magic;
+ public ushort HeaderSize;
+ public ushort Unknown;
+ public uint StringOffset;
+
+ public SFNT() { }
+ public SFNT(BinaryReader br)
+ {
+ Magic = new string(br.ReadChars(4));
+ if (!SigMatches)
+ throw new FormatException(nameof(SFNT));
+
+ HeaderSize = br.ReadUInt16();
+ Unknown = br.ReadUInt16();
+ StringOffset = (uint)br.BaseStream.Position;
+ }
+ }
+ public class SFATEntry
+ {
+ public uint FileNameHash;
+ public uint FileNameOffset;
+ public uint FileDataStart;
+ public uint FileDataEnd;
+
+ public SFATEntry(BinaryReader br)
+ {
+ FileNameHash = br.ReadUInt32();
+ FileNameOffset = br.ReadUInt32();
+ FileDataStart = br.ReadUInt32();
+ FileDataEnd = br.ReadUInt32();
+ }
+ }
+}
\ No newline at end of file
diff --git a/pk3DS/ARCUtil.cs b/pk3DS/ARCUtil.cs
index 045cab3..d33ea4d 100644
--- a/pk3DS/ARCUtil.cs
+++ b/pk3DS/ARCUtil.cs
@@ -1,67 +1,19 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
+using pk3DS.Core.CTR;
namespace pk3DS.ARCUtil
{
public static class ARC
{
- // Multi Type Archive Handling
- internal static bool onefile = true;
- internal static SARC analyzeSARC(string path)
+ private static SARC analyzeSARC(string path)
{
- SARC sarc = new SARC
- {
- FileName = Path.GetFileNameWithoutExtension(path),
- FilePath = Path.GetDirectoryName(path),
- Extension = Path.GetExtension(path)
- };
- BinaryReader br = new BinaryReader(File.OpenRead(path));
- sarc.valid = true;
- sarc.Signature = new string(br.ReadChars(4));
- if (sarc.Signature != "SARC")
- {
- sarc.valid = false;
- return sarc;
- }
- sarc.HeaderSize = br.ReadUInt16();
- sarc.Endianness = br.ReadUInt16();
- sarc.FileSize = br.ReadUInt32();
- sarc.DataOffset = br.ReadUInt32();
- sarc.Unknown = br.ReadUInt32();
- sarc.SFat = new SFAT { Signature = new string(br.ReadChars(4)) };
- if (sarc.SFat.Signature != "SFAT")
- {
- sarc.valid = false;
- return sarc;
- }
- sarc.SFat.HeaderSize = br.ReadUInt16();
- sarc.SFat.EntryCount = br.ReadUInt16();
- sarc.SFat.HashMult = br.ReadUInt32();
- sarc.SFat.Entries = new List();
- for (int i = 0; i < sarc.SFat.EntryCount; i++)
- {
- SFATEntry s = new SFATEntry
- {
- FileNameHash = br.ReadUInt32(),
- FileNameOffset = br.ReadUInt32(),
- FileDataStart = br.ReadUInt32(),
- FileDataEnd = br.ReadUInt32()
- };
- sarc.SFat.Entries.Add(s);
- }
- sarc.SFnt = new SFNT { Signature = new string(br.ReadChars(4)) };
- if (sarc.SFnt.Signature != "SFNT")
- {
- sarc.valid = false;
- return sarc;
- }
- sarc.SFnt.HeaderSize = br.ReadUInt16();
- sarc.SFnt.Unknown = br.ReadUInt16();
- sarc.SFnt.StringOffset = (uint)br.BaseStream.Position;
- return sarc;
+ try { return new SARC(path); }
+ catch { return new SARC(); }
}
internal static ShuffleARC AnalyzeShuffle(string path)
{
@@ -430,73 +382,86 @@ internal static string Interpret(string path)
}
else if (sharc.valid)
{
- ret += "New Shuffle Archive with " + sharc.FileCount + " files." + Environment.NewLine;
- string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + sharc.FileName + "_" + Path.DirectorySeparatorChar;
- if (!Directory.Exists(dir))
- {
- Console.WriteLine("Making dir: " + dir);
- Directory.CreateDirectory(dir);
- }
-
- string diglen = "".PadLeft((int)(Math.Log10(sharc.FileCount) + 1), '0');
- for (int i = 0; i < sharc.FileCount; i++)
- {
- var fs = File.OpenRead(path);
- fs.Seek(sharc.Files[i].Offset, SeekOrigin.Begin);
- byte[] fileBuffer = new byte[sharc.Files[i].Length];
- fs.Read(fileBuffer, 0, fileBuffer.Length);
- fs.Close();
- uint check = 0;
- for (int j = 0; j < fileBuffer.Length; j += 4)
- check += BitConverter.ToUInt32(fileBuffer, j);
- Console.WriteLine(i.ToString(diglen) + ": " + check.ToString("X8"));
- File.WriteAllBytes(dir + i.ToString(diglen) + ".zip", fileBuffer);
- ret += "Extracted " + i.ToString(diglen) + " (Offset: " + sharc.Files[i].Offset.ToString("X8") + ", Len: " + sharc.Files[i].Length.ToString("X8") + ")." + Environment.NewLine;
- }
- ret += Environment.NewLine;
- }
- else if (sarc.valid)
- {
- ret = "New SARC with " + sarc.SFat.EntryCount + " files." + Environment.NewLine;
- string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + sarc.FileName + "_" + Path.DirectorySeparatorChar;
- if (!Directory.Exists(dir))
- {
- Console.WriteLine("Making dir: " + dir);
- Directory.CreateDirectory(dir);
- }
-
- foreach (SFATEntry t in sarc.SFat.Entries)
- {
- var fs = File.OpenRead(path);
- 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);
- 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);
- }
- string FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar);
- fs.Close();
- 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);
- }
+ UnpackShuffleARC(path, sharc, ret);
}
+ else if (sarc.Valid)
+ UnpackSARC(path, sarc);
else
{
ret = "Not a valid .DARC/.FARC/.SARC/.GAR/Shuffle Archive file";
}
return ret;
}
-
+
+ private static bool UnpackShuffleARC(string path, ShuffleARC sharc, string ret)
+ {
+ Debug.WriteLine($"New Shuffle Archive with {sharc.FileCount} files.");
+ string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + sharc.FileName + "_" + Path.DirectorySeparatorChar;
+ if (!Directory.Exists(dir))
+ {
+ Console.WriteLine("Making dir: " + dir);
+ Directory.CreateDirectory(dir);
+ }
+
+ string diglen = "".PadLeft((int)(Math.Log10(sharc.FileCount) + 1), '0');
+ for (int i = 0; i < sharc.FileCount; i++)
+ {
+ var fs = File.OpenRead(path);
+ fs.Seek(sharc.Files[i].Offset, SeekOrigin.Begin);
+ byte[] fileBuffer = new byte[sharc.Files[i].Length];
+ fs.Read(fileBuffer, 0, fileBuffer.Length);
+ fs.Close();
+ uint check = 0;
+ for (int j = 0; j < fileBuffer.Length; j += 4)
+ check += BitConverter.ToUInt32(fileBuffer, j);
+ Console.WriteLine(i.ToString(diglen) + ": " + check.ToString("X8"));
+ File.WriteAllBytes(dir + i.ToString(diglen) + ".zip", fileBuffer);
+ Debug.WriteLine($"Extracted {i.ToString(diglen)} (Offset: {sharc.Files[i].Offset:X8}, Len: {sharc.Files[i].Length:X8}).");
+ }
+ return true;
+ }
+
+ private static bool UnpackSARC(string path, 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);
+ 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();
+
+ return true;
+ }
+
// Unpacking
internal static string unpackDARC(string path, string outFolder = null, bool delete = true)
{
@@ -668,45 +633,6 @@ public struct FARCFile
public string Name;
}
- public class SARC
- {
- public string Signature;
- public ushort HeaderSize = 0x14;
- public ushort Endianness;
- public uint FileSize;
- public uint DataOffset;
- public uint Unknown;
- public SFAT SFat;
- public SFNT SFnt;
-
- public string FileName;
- public string FilePath;
- public string Extension;
- public bool valid;
- }
- public class SFAT
- {
- public string Signature;
- public ushort HeaderSize;
- public ushort EntryCount;
- public uint HashMult;
- public List Entries;
- }
- public class SFATEntry
- {
- public uint FileNameHash;
- public uint FileNameOffset;
- public uint FileDataStart;
- public uint FileDataEnd;
- }
- public class SFNT
- {
- public string Signature;
- public ushort HeaderSize;
- public ushort Unknown;
- public uint StringOffset;
- }
-
public class ShuffleARC
{
public uint magic; //0xB