diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index afa41a22..726ce303 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -8,6 +8,12 @@ namespace hactoolnet public static class Program { static void Main(string[] args) + { + ReadNca(); + //ListSdfs(args); + } + + private static void ListSdfs(string[] args) { var sdfs = LoadSdFs(args); @@ -18,7 +24,22 @@ namespace hactoolnet ListTitles(sdfs); //DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D"); - DecryptTitle(sdfs, 0x0100000000010800); + DecryptTitle(sdfs, 0x010025400AECE000); + } + + static void ReadNca() + { + var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt"); + using (var file = new FileStream("acc9da939a8e0b339aa3be3d409d9ada.nca", FileMode.Open, FileAccess.Read)) + { + var nca = new Nca(keyset, file, false); + var romfs = nca.OpenSection(0); + + using (var output = new FileStream("romfs_net", FileMode.Create)) + { + romfs.CopyTo(output); + } + } } static void DecryptNax0(SdFs sdFs, string name) diff --git a/libhac/Keyset.cs b/libhac/Keyset.cs index 99b9ca72..c4a7c54e 100644 --- a/libhac/Keyset.cs +++ b/libhac/Keyset.cs @@ -41,6 +41,8 @@ namespace libhac public byte[] acid_fixed_key_modulus { get; set; } = new byte[0x100]; public byte[] package2_fixed_key_modulus { get; set; } = new byte[0x100]; + public Dictionary TitleKeys { get; } = new Dictionary(new ByteArray128BitComparer()); + public void SetSdSeed(byte[] sdseed) { for (int k = 0; k < sd_card_key_sources.Length; k++) @@ -76,12 +78,46 @@ namespace libhac public static class ExternalKeys { + private const int TitleKeySize = 0x10; private static readonly Dictionary KeyDict = CreateKeyDict(); + public static Keyset ReadKeyFile(string filename, string titleKeysFilename, IProgressReport progress = null) + { + var keyset = ReadKeyFile(filename, progress); + using (var reader = new StreamReader(new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read))) + { + string line; + while ((line = reader.ReadLine()) != null) + { + var a = line.Split(','); + if (a.Length != 2) continue; + + var rightsId = a[0].Trim().ToBytes(); + var titleKey = a[1].Trim().ToBytes(); + + if (rightsId.Length != TitleKeySize) + { + progress?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); + continue; + } + + if (titleKey.Length != TitleKeySize) + { + progress?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); + continue; + } + + keyset.TitleKeys.Add(rightsId, titleKey); + } + } + + return keyset; + } + public static Keyset ReadKeyFile(string filename, IProgressReport progress = null) { var keyset = new Keyset(); - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open))) + using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) { string line; while ((line = reader.ReadLine()) != null) diff --git a/libhac/Nca.cs b/libhac/Nca.cs index 345803e0..14a65c63 100644 --- a/libhac/Nca.cs +++ b/libhac/Nca.cs @@ -57,6 +57,8 @@ namespace libhac size = sect.Pfs0.Pfs0Size; break; case SectionFsType.Romfs: + offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].LogicalOffset; + size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize; break; default: throw new ArgumentOutOfRangeException(); diff --git a/libhac/Romfs.cs b/libhac/Romfs.cs new file mode 100644 index 00000000..8d28d5f8 --- /dev/null +++ b/libhac/Romfs.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace libhac +{ + public class Romfs + { + public static readonly int IvfcMaxLevel = 6; + } + + public class RomfsHeader + { + public ulong HeaderSize; + public ulong DirHashTableOffset; + public ulong DirHashTableSize; + public ulong DirMetaTableOffset; + public ulong DirMetaTableSize; + public ulong FileHashTableOffset; + public ulong FileHashTableSize; + public ulong FileMetaTableOffset; + public ulong FileMetaTableSize; + public ulong DataOffset; + } + + public class IvfcLevel + { + public ulong DataOffset { get; set; } + public ulong DataSize { get; set; } + public ulong HashOffset { get; set; } + public ulong HashBlockSize { get; set; } + public ulong HashBlockCount { get; set; } + } +} diff --git a/libhac/Util.cs b/libhac/Util.cs index d0ef138a..86d3c9dc 100644 --- a/libhac/Util.cs +++ b/libhac/Util.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace libhac @@ -255,4 +257,44 @@ namespace libhac return readable.ToString("0.### ") + suffix; } } + + public class ByteArray128BitComparer : EqualityComparer + { + public override bool Equals(byte[] first, byte[] second) + { + if (first == null || second == null) + { + // null == null returns true. + // non-null == null returns false. + return first == second; + } + if (ReferenceEquals(first, second)) + { + return true; + } + if (first.Length != second.Length) + { + return false; + } + // Linq extension method is based on IEnumerable, must evaluate every item. + return first.SequenceEqual(second); + } + + public override int GetHashCode(byte[] obj) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + if (obj.Length != 16) + { + throw new ArgumentException("Length must be 16 bytes"); + } + + var hi = BitConverter.ToUInt64(obj, 0); + var lo = BitConverter.ToUInt64(obj, 8); + + return (hi.GetHashCode() * 397) ^ lo.GetHashCode(); + } + } }