diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 4a33ab8c..0c3d477d 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using libhac; @@ -9,7 +10,7 @@ namespace hactoolnet { static void Main(string[] args) { - ListSdContents(args); + DumpMeta(args); } static void DecryptNax0(string[] args) @@ -30,15 +31,57 @@ namespace hactoolnet } static void ListSdContents(string[] args) + { + Console.WriteLine($"Using key file {args[0]}"); + Console.WriteLine($"SD seed {BitConverter.ToString(args[1].ToBytes())}"); + Console.WriteLine($"SD path {args[2]}"); + var keyset = ExternalKeys.ReadKeyFile(args[0]); + + if (keyset.master_keys[0].IsEmpty()) + { + Console.WriteLine("Need master key 0"); + } + + keyset.SetSdSeed(args[1].ToBytes()); + var sdfs = new SdFs(keyset, args[2]); + var ncas = sdfs.ReadAllNca(); + + foreach (var nca in ncas.Where(x => x != null)) + { + Console.WriteLine($"{nca.Header.TitleId:X16} {nca.Header.ContentType.ToString().PadRight(10, ' ')} {nca.Name}"); + } + } + + static void DumpMeta(string[] args) { var keyset = ExternalKeys.ReadKeyFile(args[0]); keyset.SetSdSeed(args[1].ToBytes()); var sdfs = new SdFs(keyset, args[2]); var ncas = sdfs.ReadAllNca().ToArray(); - foreach (var nca in ncas) + var metadata = new List(); + + using (var progress = new ProgressBar()) { - Console.WriteLine($"{nca.Header.TitleId:X16} {nca.Header.ContentType} {nca.Name}"); + foreach (var nca in ncas.Where(x => x.Header.ContentType == ContentType.Meta)) + { + foreach (var section in nca.Sections.Where(x => x.Header.FsType == SectionFsType.Pfs0)) + { + var sect = nca.OpenSection(section.SectionNum); + var pfs0 = sect.Pfs0; + pfs0.Open(sect.Stream); + + foreach (var entry in pfs0.Entries) + { + var path = Path.Combine("meta", entry.Name); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + var file = pfs0.GetFile(entry.Index); + metadata.Add(file); + File.WriteAllBytes(path, file); + progress.LogMessage(path); + } + } + } } } } diff --git a/hactoolnet/hactoolnet.csproj b/hactoolnet/hactoolnet.csproj index e70e51cf..48fc5f7e 100644 --- a/hactoolnet/hactoolnet.csproj +++ b/hactoolnet/hactoolnet.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.1 + netcoreapp2.1;net45 7.3 diff --git a/libhac/Nca.cs b/libhac/Nca.cs index 185b0166..f15d14f7 100644 --- a/libhac/Nca.cs +++ b/libhac/Nca.cs @@ -38,11 +38,28 @@ namespace libhac } } - public Stream OpenSection(int index) + public NcaSection OpenSection(int index) { if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index)); var sect = Sections[index]; - Stream.Position = sect.Offset; + + sect.Stream = null; + long offset = sect.Offset; + long size = sect.Size; + + switch (sect.Header.FsType) + { + case SectionFsType.Pfs0: + offset = sect.Offset + sect.Pfs0.Superblock.Pfs0Offset; + size = sect.Pfs0.Superblock.Pfs0Size; + break; + case SectionFsType.Romfs: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + Stream.Position = offset; switch (sect.Header.CryptType) { @@ -51,14 +68,15 @@ namespace libhac case SectionCryptType.XTS: break; case SectionCryptType.CTR: - return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], sect.Offset, sect.Size, sect.Offset)); + sect.Stream = new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset)); + break; case SectionCryptType.BKTR: break; default: throw new ArgumentOutOfRangeException(); } - return null; + return sect; } private void ReadHeader(Keyset keyset, Stream stream) @@ -107,7 +125,7 @@ namespace libhac public class NcaSection { - public Stream Stream; + public Stream Stream { get; set; } public NcaFsHeader Header { get; set; } public SectionType Type { get; set; } public int SectionNum { get; set; } diff --git a/libhac/Pfs0.cs b/libhac/Pfs0.cs index 2b4f7b75..aa30ad58 100644 --- a/libhac/Pfs0.cs +++ b/libhac/Pfs0.cs @@ -1,10 +1,56 @@ using System.IO; +using System.Text; namespace libhac { public class Pfs0 { public Pfs0Superblock Superblock { get; set; } + public Pfs0Header Header { get; set; } + public int HeaderSize { get; set; } + public Pfs0FileEntry[] Entries { get; set; } + private Stream Stream { get; set; } + + public void Open(Stream file) + { + byte[] headerBytes; + using (var reader = new BinaryReader(file, Encoding.Default, true)) + { + Header = new Pfs0Header(reader); + HeaderSize = (int)(16 + 24 * Header.NumFiles + Header.StringTableSize); + file.Position = 0; + headerBytes = reader.ReadBytes(HeaderSize); + } + + using (var reader = new BinaryReader(new MemoryStream(headerBytes))) + { + reader.BaseStream.Position = 16; + + Entries = new Pfs0FileEntry[Header.NumFiles]; + for (int i = 0; i < Header.NumFiles; i++) + { + Entries[i] = new Pfs0FileEntry(reader) { Index = i }; + } + + int stringTableOffset = 16 + 24 * Header.NumFiles; + for (int i = 0; i < Header.NumFiles; i++) + { + reader.BaseStream.Position = stringTableOffset + Entries[i].StringTableOffset; + Entries[i].Name = reader.ReadAsciiZ(); + } + } + + Stream = file; + } + + public byte[] GetFile(int index) + { + var entry = Entries[index]; + var file = new byte[entry.Size]; + Stream.Position = HeaderSize + entry.Offset; + Stream.Read(file, 0, file.Length); + return file; + } } public class Pfs0Superblock @@ -12,21 +58,55 @@ namespace libhac public byte[] MasterHash; /* SHA-256 hash of the hash table. */ public uint BlockSize; /* In bytes. */ public uint Always2; - public ulong HashTableOffset; /* Normally zero. */ - public ulong HashTableSize; - public ulong Pfs0Offset; - public ulong Pfs0Size; + public long HashTableOffset; /* Normally zero. */ + public long HashTableSize; + public long Pfs0Offset; + public long Pfs0Size; public Pfs0Superblock(BinaryReader reader) { MasterHash = reader.ReadBytes(0x20); BlockSize = reader.ReadUInt32(); Always2 = reader.ReadUInt32(); - HashTableOffset = reader.ReadUInt64(); - HashTableSize = reader.ReadUInt64(); - Pfs0Offset = reader.ReadUInt64(); - Pfs0Size = reader.ReadUInt64(); + HashTableOffset = reader.ReadInt64(); + HashTableSize = reader.ReadInt64(); + Pfs0Offset = reader.ReadInt64(); + Pfs0Size = reader.ReadInt64(); reader.BaseStream.Position += 0xF0; } } + + public class Pfs0Header + { + public string Magic; + public int NumFiles; + public uint StringTableSize; + public long Reserved; + + public Pfs0Header(BinaryReader reader) + { + Magic = reader.ReadAscii(4); + NumFiles = reader.ReadInt32(); + StringTableSize = reader.ReadUInt32(); + Reserved = reader.ReadInt32(); + } + } + + public class Pfs0FileEntry + { + public int Index; + public long Offset; + public long Size; + public uint StringTableOffset; + public uint Reserved; + public string Name; + + public Pfs0FileEntry(BinaryReader reader) + { + Offset = reader.ReadInt64(); + Size = reader.ReadInt64(); + StringTableOffset = reader.ReadUInt32(); + Reserved = reader.ReadUInt32(); + } + } } diff --git a/libhac/Util.cs b/libhac/Util.cs index 25586971..733864d8 100644 --- a/libhac/Util.cs +++ b/libhac/Util.cs @@ -76,6 +76,21 @@ namespace libhac } } + public static string ReadAsciiZ(this BinaryReader reader) + { + var start = reader.BaseStream.Position; + + // Read until we hit the end of the stream (-1) or a zero + while (reader.BaseStream.ReadByte() - 1 > 0) { } + + int size = (int)(reader.BaseStream.Position - start - 1); + reader.BaseStream.Position = start; + + string text = reader.ReadAscii(size); + reader.BaseStream.Position++; // Skip the null byte + return text; + } + public static string ReadAscii(this BinaryReader reader, int size) { return Encoding.ASCII.GetString(reader.ReadBytes(size), 0, size);