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()); + } } }