diff --git a/src/LibHac/IO/ConcatenationStorageBuilder.cs b/src/LibHac/IO/ConcatenationStorageBuilder.cs new file mode 100644 index 00000000..ee8e124d --- /dev/null +++ b/src/LibHac/IO/ConcatenationStorageBuilder.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace LibHac.IO +{ + public class ConcatenationStorageBuilder + { + private List Segments { get; } + + public ConcatenationStorageBuilder() + { + Segments = new List(); + } + + public ConcatenationStorageBuilder(IEnumerable segments) + { + Segments = segments.ToList(); + } + + public void Add(IStorage storage, long offset) + { + Segments.Add(new ConcatenationStorageSegment(storage, offset)); + } + + public ConcatenationStorage Build() + { + List segments = Segments.OrderBy(x => x.Offset).ToList(); + var sources = new List(); + + long offset = 0; + + foreach (ConcatenationStorageSegment segment in segments) + { + long paddingNeeded = segment.Offset - offset; + + if (paddingNeeded < 0) throw new InvalidDataException("Builder has segments that overlap."); + + if (paddingNeeded > 0) + { + sources.Add(new NullStorage(paddingNeeded)); + } + + sources.Add(segment.Storage); + offset = segment.Offset + segment.Storage.Length; + } + + return new ConcatenationStorage(sources, true); + } + } + + public class ConcatenationStorageSegment + { + public IStorage Storage { get; } + public long Offset { get; } + + public ConcatenationStorageSegment(IStorage storage, long offset) + { + Storage = storage; + Offset = offset; + } + } +} diff --git a/src/LibHac/IO/NcaUtils/Nca.cs b/src/LibHac/IO/NcaUtils/Nca.cs index d2cf3084..b72c0031 100644 --- a/src/LibHac/IO/NcaUtils/Nca.cs +++ b/src/LibHac/IO/NcaUtils/Nca.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using LibHac.IO.RomFs; @@ -314,14 +313,15 @@ namespace LibHac.IO.NcaUtils public IStorage OpenDecryptedNca() { - var list = new List { OpenHeaderStorage() }; + var builder = new ConcatenationStorageBuilder(); + builder.Add(OpenHeaderStorage(), 0); - foreach (NcaSection section in Sections.Where(x => x != null).OrderBy(x => x.Offset)) + foreach (NcaSection section in Sections.Where(x => x != null)) { - list.Add(OpenRawStorage(section.SectionNum)); + builder.Add(OpenRawStorage(section.SectionNum), section.Offset); } - return new ConcatenationStorage(list, true); + return builder.Build(); } private NcaHeader DecryptHeader() @@ -336,11 +336,13 @@ namespace LibHac.IO.NcaUtils private CachedStorage OpenHeaderStorage() { - int size = 0x4000; + long size = 0xc00; - // Support reading headers that are only 0xC00 bytes long, but still return - // the entire header if available. - if (BaseStorage.Length >= 0xC00 && BaseStorage.Length < 0x4000) size = 0xC00; + // Encrypted portion continues until the first section + if (Sections.Any(x => x != null)) + { + size = Sections.Where(x => x != null).Min(x => x.Offset); + } return new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, size), Keyset.HeaderKey, 0x200, true), 1, true); } diff --git a/src/LibHac/IO/NcaUtils/NcaExtensions.cs b/src/LibHac/IO/NcaUtils/NcaExtensions.cs index 858ae1bc..115a8c0b 100644 --- a/src/LibHac/IO/NcaUtils/NcaExtensions.cs +++ b/src/LibHac/IO/NcaUtils/NcaExtensions.cs @@ -4,13 +4,15 @@ namespace LibHac.IO.NcaUtils { public static class NcaExtensions { - public static IStorage OpenStorage(this Nca nca, int index, IntegrityCheckLevel integrityCheckLevel, bool openRaw) + public static IStorage OpenStorage(this Nca nca, int index, IntegrityCheckLevel integrityCheckLevel, + bool openRaw) { if (openRaw) return nca.OpenRawStorage(index); return nca.OpenStorage(index, integrityCheckLevel); } - public static IStorage OpenStorage(this Nca nca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel, bool openRaw) + public static IStorage OpenStorage(this Nca nca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel, + bool openRaw) { if (openRaw) return nca.OpenRawStorage(type); return nca.OpenStorage(type, integrityCheckLevel); @@ -23,7 +25,8 @@ namespace LibHac.IO.NcaUtils .WriteAllBytes(filename, logger); } - public static void ExtractSection(this Nca nca, int index, string outputDir, IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + public static void ExtractSection(this Nca nca, int index, string outputDir, + IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) { if (index < 0 || index > 3) throw new IndexOutOfRangeException(); if (!nca.SectionIsDecryptable(index)) return; @@ -55,7 +58,8 @@ namespace LibHac.IO.NcaUtils NcaHashType hashType = sect.Header.HashType; if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; - var stream = nca.OpenStorage(index, IntegrityCheckLevel.IgnoreOnInvalid, false) as HierarchicalIntegrityVerificationStorage; + var stream = nca.OpenStorage(index, IntegrityCheckLevel.IgnoreOnInvalid, false) + as HierarchicalIntegrityVerificationStorage; if (stream == null) return Validity.Unchecked; if (!quiet) logger?.LogMessage($"Verifying section {index}...");