From caccc5a67711ab228444a85cae23570a4454131e Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 26 Feb 2019 22:13:47 -0600 Subject: [PATCH] Support hashed partition FS building --- src/LibHac/IO/PartitionFileSystemBuilder.cs | 55 +++++++++++++++++++-- src/hactoolnet/CliParser.cs | 9 +++- src/hactoolnet/Options.cs | 1 + src/hactoolnet/ProcessFsBuild.cs | 6 ++- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/LibHac/IO/PartitionFileSystemBuilder.cs b/src/LibHac/IO/PartitionFileSystemBuilder.cs index b4613940..4f96e2aa 100644 --- a/src/LibHac/IO/PartitionFileSystemBuilder.cs +++ b/src/LibHac/IO/PartitionFileSystemBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; namespace LibHac.IO @@ -38,7 +39,9 @@ namespace LibHac.IO File = file, Length = file.GetSize(), Offset = CurrentOffset, - NameLength = Encoding.UTF8.GetByteCount(filename) + NameLength = Encoding.UTF8.GetByteCount(filename), + HashOffset = 0, + HashLength = 0x200 }; CurrentOffset += entry.Length; @@ -60,8 +63,10 @@ namespace LibHac.IO private byte[] BuildMetaData(PartitionFileSystemType type) { + if (type == PartitionFileSystemType.Hashed) CalculateHashes(); + int entryTableSize = Entries.Count * PartitionFileEntry.GetEntrySize(type); - int stringTableSize = CalcStringTableSize(HeaderSize + entryTableSize); + int stringTableSize = CalcStringTableSize(HeaderSize + entryTableSize, type); int metaDataSize = HeaderSize + entryTableSize + stringTableSize; var metaData = new byte[metaDataSize]; @@ -79,7 +84,17 @@ namespace LibHac.IO writer.Write(entry.Offset); writer.Write(entry.Length); writer.Write(stringOffset); - writer.Write(0); + + if (type == PartitionFileSystemType.Standard) + { + writer.Write(0); + } + else + { + writer.Write(entry.HashLength); + writer.Write(entry.HashOffset); + writer.Write(entry.Hash); + } stringOffset += entry.NameLength + 1; } @@ -92,7 +107,7 @@ namespace LibHac.IO return metaData; } - private int CalcStringTableSize(int startOffset) + private int CalcStringTableSize(int startOffset, PartitionFileSystemType type) { int size = 0; @@ -101,7 +116,7 @@ namespace LibHac.IO size += entry.NameLength + 1; } - int endOffset = Util.AlignUp(startOffset + size, MetaDataAlignment); + int endOffset = Util.AlignUp(startOffset + size, GetMetaDataAlignment(type)); return endOffset - startOffset; } @@ -115,6 +130,32 @@ namespace LibHac.IO } } + private int GetMetaDataAlignment(PartitionFileSystemType type) + { + switch (type) + { + case PartitionFileSystemType.Standard: return 0x20; + case PartitionFileSystemType.Hashed: return 0x200; + default: throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + private void CalculateHashes() + { + using (SHA256 sha = SHA256.Create()) + { + foreach (Entry entry in Entries) + { + if (entry.HashLength == 0) entry.HashLength = 0x200; + + var data = new byte[entry.HashLength]; + entry.File.Read(data, entry.HashOffset); + + entry.Hash = sha.ComputeHash(data); + } + } + } + private class Entry { public string Name; @@ -122,6 +163,10 @@ namespace LibHac.IO public long Length; public long Offset; public int NameLength; + + public int HashLength; + public long HashOffset; + public byte[] Hash; } } } diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index 694b508b..47fc86ad 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -51,6 +51,7 @@ namespace hactoolnet new CliOption("listfiles", 0, (o, a) => o.ListFiles = true), new CliOption("sign", 0, (o, a) => o.SignSave = true), new CliOption("readbench", 0, (o, a) => o.ReadBench = true), + new CliOption("hashedfs", 0, (o, a) => o.BuildHfs = true), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), new CliOption("bench", 1, (o, a) => o.BenchType = a[0]), @@ -162,7 +163,7 @@ namespace hactoolnet sb.AppendLine(" -y, --verify Verify all hashes in the input file."); sb.AppendLine(" -h, --enablehash Enable hash checks when reading the input file."); sb.AppendLine(" -k, --keyset Load keys from an external file."); - sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, ndv0, keygen, romfsbuild]"); + sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pfs0, pk11, pk21, ini1, kip1, switchfs, save, ndv0, keygen, romfsbuild, pfsbuild]"); sb.AppendLine(" --titlekeys Load title keys from an external file."); sb.AppendLine("NCA options:"); sb.AppendLine(" --plaintext Specify file path for saving a decrypted copy of the NCA."); @@ -186,6 +187,12 @@ namespace hactoolnet sb.AppendLine("RomFS creation options:"); sb.AppendLine(" Input path must be a directory"); sb.AppendLine(" --outfile Specify created RomFS file path."); + sb.AppendLine("Partition FS options:"); + sb.AppendLine(" --outdir Specify extracted FS directory path."); + sb.AppendLine("Partition FS creation options:"); + sb.AppendLine(" Input path must be a directory"); + sb.AppendLine(" --outfile Specify created Partition FS file path."); + sb.AppendLine(" --hashedfs Create a hashed Partition FS (HFS0)."); sb.AppendLine("XCI options:"); sb.AppendLine(" --rootdir Specify root XCI directory path."); sb.AppendLine(" --updatedir Specify update XCI directory path."); diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index b60d7d2a..fd0cbc05 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -44,6 +44,7 @@ namespace hactoolnet public bool ListFiles; public bool SignSave; public bool ReadBench; + public bool BuildHfs; public ulong TitleId; public string BenchType; diff --git a/src/hactoolnet/ProcessFsBuild.cs b/src/hactoolnet/ProcessFsBuild.cs index f7b68d73..9db07728 100644 --- a/src/hactoolnet/ProcessFsBuild.cs +++ b/src/hactoolnet/ProcessFsBuild.cs @@ -37,10 +37,14 @@ namespace hactoolnet return; } + PartitionFileSystemType type = ctx.Options.BuildHfs + ? PartitionFileSystemType.Hashed + : PartitionFileSystemType.Standard; + var localFs = new LocalFileSystem(ctx.Options.InFile); var builder = new PartitionFileSystemBuilder(localFs); - IStorage partitionFs = builder.Build(PartitionFileSystemType.Standard); + IStorage partitionFs = builder.Build(type); ctx.Logger.LogMessage($"Building Partition FS as {ctx.Options.OutFile}");