From d12f419d8dc7d58faf25a48ac17528ef526971cb Mon Sep 17 00:00:00 2001 From: jonnysp Date: Wed, 3 Oct 2018 22:14:23 +0200 Subject: [PATCH] Add Partition FS extraction (#12) Add Partition FS extraction + misc. changes --- LibHac/Crypto.cs | 6 +++-- LibHac/Nca.cs | 49 ++++++++++++++------------------------- LibHac/NcaStructs.cs | 39 ++++++++++++++----------------- LibHac/Pfs.cs | 12 +++------- LibHac/Savefile/Header.cs | 12 +--------- LibHac/Util.cs | 5 ++-- LibHac/XciHeader.cs | 9 +------ hactoolnet/Options.cs | 1 + hactoolnet/ProcessNca.cs | 2 +- hactoolnet/ProcessNsp.cs | 43 ++++++++++++++++++++++++++++++++++ hactoolnet/Program.cs | 2 ++ 11 files changed, 94 insertions(+), 86 deletions(-) diff --git a/LibHac/Crypto.cs b/LibHac/Crypto.cs index ad1c1d1d..6c8551a8 100644 --- a/LibHac/Crypto.cs +++ b/LibHac/Crypto.cs @@ -11,12 +11,14 @@ namespace LibHac internal const int Aes128Size = 0x10; internal const int Sha256DigestSize = 0x20; - public static bool CheckMemoryHashTable(byte[] data, byte[] hash) + public static Validity CheckMemoryHashTable(byte[] data, byte[] hash, int offset, int count) { + Validity comp; using (SHA256 sha = SHA256.Create()) { - return Util.ArraysEqual(hash, sha.ComputeHash(data)); + comp = Util.ArraysEqual(hash, sha.ComputeHash(data, offset, count)) ? Validity.Valid : Validity.Invalid; } + return comp; } public static void DecryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) diff --git a/LibHac/Nca.cs b/LibHac/Nca.cs index a36e0e9e..a42d8b9f 100644 --- a/LibHac/Nca.cs +++ b/LibHac/Nca.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Linq; -using System.Security.Cryptography; using LibHac.Streams; using LibHac.XTSSharp; @@ -192,7 +191,7 @@ namespace LibHac var reader = new BinaryReader(new MemoryStream(headerBytes)); - Header = NcaHeader.Read(reader); + Header = new NcaHeader(reader); } private void DecryptKeyArea(Keyset keyset) @@ -277,7 +276,6 @@ namespace LibHac NcaSection sect = Sections[index]; byte[] expected = null; - byte[] actual; long offset = 0; long size = 0; @@ -310,15 +308,8 @@ namespace LibHac stream.Position = offset; stream.Read(hashTable, 0, hashTable.Length); - using (SHA256 hash = SHA256.Create()) - { - actual = hash.ComputeHash(hashTable); - } - - Validity validity = Util.ArraysEqual(expected, actual) ? Validity.Valid : Validity.Invalid; - - sect.SuperblockHashValidity = validity; - if (sect.Type == SectionType.Romfs) sect.Romfs.IvfcLevels[0].HashValidity = validity; + sect.SuperblockHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length); + if (sect.Type == SectionType.Romfs) sect.Romfs.IvfcLevels[0].HashValidity = sect.SuperblockHashValidity; } public void VerifySection(int index, IProgressReport logger = null) @@ -362,8 +353,7 @@ namespace LibHac var table = new byte[level.HashSize]; section.Position = level.HashOffset; section.Read(table, 0, table.Length); - level.HashValidity = - VerifyHashTable(section, table, level.DataOffset, level.DataSize, level.HashBlockSize, true, logger); + level.HashValidity = VerifyHashTable(section, table, level.DataOffset, level.DataSize, level.HashBlockSize, true, logger); } } @@ -377,26 +367,23 @@ namespace LibHac section.Position = dataOffset; logger?.SetTotal(blockCount); - using (SHA256 sha256 = SHA256.Create()) + for (long i = 0; i < blockCount; i++) { - for (long i = 0; i < blockCount; i++) + var remaining = (dataLen - i * blockSize); + if (remaining < blockSize) { - long remaining = dataLen - i * blockSize; - if (remaining < blockSize) - { - Array.Clear(currentBlock, 0, currentBlock.Length); - if (!isFinalBlockFull) curBlockSize = (int)remaining; - } - Array.Copy(hashTable, i * hashSize, expectedHash, 0, hashSize); - section.Read(currentBlock, 0, curBlockSize); - byte[] actualHash = sha256.ComputeHash(currentBlock, 0, curBlockSize); - - if (!Util.ArraysEqual(expectedHash, actualHash)) - { - return Validity.Invalid; - } - logger?.ReportAdd(1); + Array.Clear(currentBlock, 0, currentBlock.Length); + if (!isFinalBlockFull) curBlockSize = (int)remaining; } + Array.Copy(hashTable, i * hashSize, expectedHash, 0, hashSize); + section.Read(currentBlock, 0, curBlockSize); + + if (Crypto.CheckMemoryHashTable(currentBlock, expectedHash, 0, curBlockSize) == Validity.Invalid) + { + return Validity.Invalid; + } + + logger?.ReportAdd(1); } return Validity.Valid; diff --git a/LibHac/NcaStructs.cs b/LibHac/NcaStructs.cs index 0a0c8f02..16cf5763 100644 --- a/LibHac/NcaStructs.cs +++ b/LibHac/NcaStructs.cs @@ -25,50 +25,47 @@ namespace LibHac public NcaFsHeader[] FsHeaders = new NcaFsHeader[4]; - public static NcaHeader Read(BinaryReader reader) + public NcaHeader(BinaryReader reader) { - var head = new NcaHeader(); - - head.Signature1 = reader.ReadBytes(0x100); - head.Signature2 = reader.ReadBytes(0x100); - head.Magic = reader.ReadAscii(4); - if (head.Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file"); - head.Distribution = (DistributionType)reader.ReadByte(); - head.ContentType = (ContentType)reader.ReadByte(); - head.CryptoType = reader.ReadByte(); - head.KaekInd = reader.ReadByte(); - head.NcaSize = reader.ReadUInt64(); - head.TitleId = reader.ReadUInt64(); + Signature1 = reader.ReadBytes(0x100); + Signature2 = reader.ReadBytes(0x100); + Magic = reader.ReadAscii(4); + if (Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file"); + Distribution = (DistributionType)reader.ReadByte(); + ContentType = (ContentType)reader.ReadByte(); + CryptoType = reader.ReadByte(); + KaekInd = reader.ReadByte(); + NcaSize = reader.ReadUInt64(); + TitleId = reader.ReadUInt64(); reader.BaseStream.Position += 4; - head.SdkVersion = new TitleVersion(reader.ReadUInt32()); - head.CryptoType2 = reader.ReadByte(); + SdkVersion = new TitleVersion(reader.ReadUInt32()); + CryptoType2 = reader.ReadByte(); reader.BaseStream.Position += 0xF; - head.RightsId = reader.ReadBytes(0x10); + RightsId = reader.ReadBytes(0x10); for (int i = 0; i < 4; i++) { - head.SectionEntries[i] = new NcaSectionEntry(reader); + SectionEntries[i] = new NcaSectionEntry(reader); } for (int i = 0; i < 4; i++) { - head.SectionHashes[i] = reader.ReadBytes(0x20); + SectionHashes[i] = reader.ReadBytes(0x20); } for (int i = 0; i < 4; i++) { - head.EncryptedKeys[i] = reader.ReadBytes(0x10); + EncryptedKeys[i] = reader.ReadBytes(0x10); } reader.BaseStream.Position += 0xC0; for (int i = 0; i < 4; i++) { - head.FsHeaders[i] = new NcaFsHeader(reader); + FsHeaders[i] = new NcaFsHeader(reader); } - return head; } } diff --git a/LibHac/Pfs.cs b/LibHac/Pfs.cs index bafff8e1..490b3906 100644 --- a/LibHac/Pfs.cs +++ b/LibHac/Pfs.cs @@ -137,18 +137,12 @@ namespace LibHac } - if (Type == PfsType.Hfs0) { + if (Type == PfsType.Hfs0) + { for (int i = 0; i < NumFiles; i++) { reader.BaseStream.Position = HeaderSize + Files[i].Offset; - if (Crypto.CheckMemoryHashTable(reader.ReadBytes(Files[i].HashedRegionSize), Files[i].Hash)) - { - Files[i].HashValidity = Validity.Valid; - } - else - { - Files[i].HashValidity = Validity.Invalid; - } + Files[i].HashValidity = Crypto.CheckMemoryHashTable(reader.ReadBytes(Files[i].HashedRegionSize), Files[i].Hash, 0, Files[i].HashedRegionSize); } } diff --git a/LibHac/Savefile/Header.cs b/LibHac/Savefile/Header.cs index 47f28c23..60b8fe1c 100644 --- a/LibHac/Savefile/Header.cs +++ b/LibHac/Savefile/Header.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Security.Cryptography; namespace LibHac.Savefile { @@ -85,19 +84,10 @@ namespace LibHac.Savefile MetaMapEntries[i] = new MapEntry(reader); } - HeaderHashValidity = ValidateHeaderHash(); + HeaderHashValidity = Crypto.CheckMemoryHashTable(Data, Layout.Hash, 0x300, 0x3d00); SignatureValidity = ValidateSignature(keyset); } - private Validity ValidateHeaderHash() - { - using (SHA256 sha256 = SHA256.Create()) - { - byte[] hash = sha256.ComputeHash(Data, 0x300, 0x3d00); - return Util.ArraysEqual(hash, Layout.Hash) ? Validity.Valid : Validity.Invalid; - } - } - private Validity ValidateSignature(Keyset keyset) { var calculatedCmac = new byte[0x10]; diff --git a/LibHac/Util.cs b/LibHac/Util.cs index 72496542..8eebbae7 100644 --- a/LibHac/Util.cs +++ b/LibHac/Util.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; namespace LibHac @@ -424,8 +423,8 @@ namespace LibHac { return false; } - // Linq extension method is based on IEnumerable, must evaluate every item. - return first.SequenceEqual(second); + + return Util.ArraysEqual(first, second); } public override int GetHashCode(byte[] obj) diff --git a/LibHac/XciHeader.cs b/LibHac/XciHeader.cs index fbe8decc..58c24c24 100644 --- a/LibHac/XciHeader.cs +++ b/LibHac/XciHeader.cs @@ -134,14 +134,7 @@ namespace LibHac } reader.BaseStream.Position = PartitionFsHeaderAddress; - - if (Crypto.CheckMemoryHashTable(reader.ReadBytes((int)PartitionFsHeaderSize), PartitionFsHeaderHash)) { - PartitionFsHeaderValidity = Validity.Valid; - } - else - { - PartitionFsHeaderValidity = Validity.Invalid; - } + PartitionFsHeaderValidity = Crypto.CheckMemoryHashTable(reader.ReadBytes((int)PartitionFsHeaderSize), PartitionFsHeaderHash, 0, (int)PartitionFsHeaderSize); } diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs index e7fba555..1437906b 100644 --- a/hactoolnet/Options.cs +++ b/hactoolnet/Options.cs @@ -42,6 +42,7 @@ namespace hactoolnet { Nca, Pfs0, + Nsp, Romfs, Nax0, Xci, diff --git a/hactoolnet/ProcessNca.cs b/hactoolnet/ProcessNca.cs index 4192c824..d5d6e840 100644 --- a/hactoolnet/ProcessNca.cs +++ b/hactoolnet/ProcessNca.cs @@ -79,7 +79,7 @@ namespace hactoolnet if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) { - NcaSection section = nca.Sections.FirstOrDefault(x => x.IsExefs); + NcaSection section = nca.Sections.FirstOrDefault(x => x?.IsExefs == true); if (section == null) { diff --git a/hactoolnet/ProcessNsp.cs b/hactoolnet/ProcessNsp.cs index b12d2c51..9675125c 100644 --- a/hactoolnet/ProcessNsp.cs +++ b/hactoolnet/ProcessNsp.cs @@ -1,11 +1,54 @@ using System.IO; using System.Reflection; +using System.Text; using LibHac; +using static hactoolnet.Print; namespace hactoolnet { internal static class ProcessNsp { + public static void Process(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + Pfs pfs = new Pfs(file); + ctx.Logger.LogMessage(pfs.Print()); + + if (ctx.Options.OutDir != null) + { + pfs.Extract(ctx.Options.OutDir, ctx.Logger); + } + } + } + + private static string Print(this Pfs pfs) + { + const int colLen = 36; + const int fileNameLen = 39; + + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("PFS0:"); + + PrintItem(sb, colLen, "Magic:", pfs.Header.Magic); + PrintItem(sb, colLen, "Number of files:", pfs.Header.NumFiles); + + for (int i = 0; i < pfs.Files.Length; i++) + { + PfsFileEntry file = pfs.Files[i]; + + string label = i == 0 ? "Files:" : ""; + string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}"; + string data = $"pfs0:/{file.Name}".PadRight(fileNameLen) + offsets; + + PrintItem(sb, colLen, label, data); + } + + return sb.ToString(); + } + public static void CreateNsp(Context ctx, SwitchFs switchFs) { ulong id = ctx.Options.TitleId; diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index cfd2daa7..6cc18d21 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -31,6 +31,8 @@ namespace hactoolnet ProcessNca.Process(ctx); break; case FileType.Pfs0: + case FileType.Nsp: + ProcessNsp.Process(ctx); break; case FileType.Romfs: ProcessRomfs.Process(ctx);