From 3e6d2a776186b2b46c64e31f70143149b1bb3ff2 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 28 Jun 2018 16:55:36 -0500 Subject: [PATCH] Add romfs superblock validation --- hactoolnet/Program.cs | 4 +-- libhac/Nca.cs | 15 +++++---- libhac/NcaStructs.cs | 3 +- libhac/Substream.cs | 75 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 libhac/Substream.cs diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index f1b2805d..203a1e8f 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -30,7 +30,7 @@ namespace hactoolnet static void ReadNca() { var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt"); - using (var file = new FileStream("bf8b106a2e68df3c3f1694636423585a.nca", FileMode.Open, FileAccess.Read)) + using (var file = new FileStream("671d172e7993ee033d1be25ee76378e3.nca", FileMode.Open, FileAccess.Read)) { var nca = new Nca(keyset, file, false); var romfs = nca.OpenSection(0, false); @@ -107,7 +107,7 @@ namespace hactoolnet { Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}"); - foreach (var sect in nca.Sections) + foreach (var sect in nca.Sections.Where(x => x != null)) { Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}"); } diff --git a/libhac/Nca.cs b/libhac/Nca.cs index 36bea252..0149b0df 100644 --- a/libhac/Nca.cs +++ b/libhac/Nca.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using libhac.XTSSharp; @@ -17,7 +16,7 @@ namespace libhac public Stream Stream { get; private set; } private bool KeepOpen { get; } - public List Sections = new List(); + public NcaSection[] Sections { get; } = new NcaSection[4]; public Nca(Keyset keyset, Stream stream, bool keepOpen) { @@ -47,14 +46,14 @@ namespace libhac { var section = ParseSection(i); if (section == null) continue; - Sections.Add(section); + Sections[i] = section; ValidateSuperblockHash(i); } } public Stream OpenSection(int index, bool raw) { - if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index)); + if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index)); var sect = Sections[index]; long offset = sect.Offset; @@ -83,7 +82,7 @@ namespace libhac switch (sect.Header.CryptType) { case SectionCryptType.None: - break; + return new SubStream(Stream, offset, size); case SectionCryptType.XTS: break; case SectionCryptType.CTR: @@ -144,7 +143,7 @@ namespace libhac private void ValidateSuperblockHash(int index) { - if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index)); + if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index)); var sect = Sections[index]; var stream = OpenSection(index, true); @@ -164,6 +163,10 @@ namespace libhac size = pfs0.HashTableSize; break; case SectionType.Romfs: + var ivfc = sect.Header.Romfs.IvfcHeader; + expected = ivfc.MasterHash; + offset = (long)ivfc.LevelHeaders[0].LogicalOffset; + size = 1 << (int)ivfc.LevelHeaders[0].BlockSize; break; case SectionType.Bktr: break; diff --git a/libhac/NcaStructs.cs b/libhac/NcaStructs.cs index eafa0666..0b26eae6 100644 --- a/libhac/NcaStructs.cs +++ b/libhac/NcaStructs.cs @@ -101,6 +101,7 @@ namespace libhac public NcaFsHeader(BinaryReader reader) { + var start = reader.BaseStream.Position; Field0 = reader.ReadByte(); Field1 = reader.ReadByte(); PartitionType = (SectionPartitionType)reader.ReadByte(); @@ -128,7 +129,7 @@ namespace libhac } Ctr = reader.ReadBytes(8).Reverse().ToArray(); - reader.BaseStream.Position += 184; + reader.BaseStream.Position = start + 512; } } diff --git a/libhac/Substream.cs b/libhac/Substream.cs new file mode 100644 index 00000000..a46e8872 --- /dev/null +++ b/libhac/Substream.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; + +namespace libhac +{ + public class SubStream : Stream + { + private Stream BaseStream { get; } + private long Offset { get; } + + public SubStream(Stream baseStream, long offset, long length) + { + if (baseStream == null) throw new ArgumentNullException(nameof(baseStream)); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if (!baseStream.CanSeek || !baseStream.CanRead) throw new NotSupportedException(); + + BaseStream = baseStream; + Length = length; + Offset = offset; + + baseStream.Seek(offset, SeekOrigin.Current); + } + public override int Read(byte[] buffer, int offset, int count) + { + long remaining = Length - Position; + if (remaining <= 0) return 0; + if (remaining < count) count = (int)remaining; + return BaseStream.Read(buffer, offset, count); + } + + public override long Length { get; } + public override bool CanRead => BaseStream.CanRead; + public override bool CanWrite => BaseStream.CanWrite; + public override bool CanSeek => BaseStream.CanSeek; + + public override long Position + { + get => BaseStream.Position - Offset; + set + { + if (value < 0 || value >= Length) + throw new ArgumentOutOfRangeException(nameof(value)); + + BaseStream.Position = Offset + value; + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + case SeekOrigin.End: + Position = Length - offset; + break; + } + + return Position; + } + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Flush() => BaseStream.Flush(); + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } +}