Fix opening decrypted NCAs

Fixes an issue where the decrypted NCA could be incorrect if the first section didn't start at offset 0x4000
This commit is contained in:
Alex Barney 2019-03-15 19:29:08 -05:00
parent a1bdadb89b
commit 56c4554d81
3 changed files with 82 additions and 13 deletions

View file

@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace LibHac.IO
{
public class ConcatenationStorageBuilder
{
private List<ConcatenationStorageSegment> Segments { get; }
public ConcatenationStorageBuilder()
{
Segments = new List<ConcatenationStorageSegment>();
}
public ConcatenationStorageBuilder(IEnumerable<ConcatenationStorageSegment> segments)
{
Segments = segments.ToList();
}
public void Add(IStorage storage, long offset)
{
Segments.Add(new ConcatenationStorageSegment(storage, offset));
}
public ConcatenationStorage Build()
{
List<ConcatenationStorageSegment> segments = Segments.OrderBy(x => x.Offset).ToList();
var sources = new List<IStorage>();
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;
}
}
}

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using LibHac.IO.RomFs; using LibHac.IO.RomFs;
@ -314,14 +313,15 @@ namespace LibHac.IO.NcaUtils
public IStorage OpenDecryptedNca() public IStorage OpenDecryptedNca()
{ {
var list = new List<IStorage> { 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() private NcaHeader DecryptHeader()
@ -336,11 +336,13 @@ namespace LibHac.IO.NcaUtils
private CachedStorage OpenHeaderStorage() private CachedStorage OpenHeaderStorage()
{ {
int size = 0x4000; long size = 0xc00;
// Support reading headers that are only 0xC00 bytes long, but still return // Encrypted portion continues until the first section
// the entire header if available. if (Sections.Any(x => x != null))
if (BaseStorage.Length >= 0xC00 && BaseStorage.Length < 0x4000) size = 0xC00; {
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); return new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, size), Keyset.HeaderKey, 0x200, true), 1, true);
} }

View file

@ -4,13 +4,15 @@ namespace LibHac.IO.NcaUtils
{ {
public static class NcaExtensions 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); if (openRaw) return nca.OpenRawStorage(index);
return nca.OpenStorage(index, integrityCheckLevel); 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); if (openRaw) return nca.OpenRawStorage(type);
return nca.OpenStorage(type, integrityCheckLevel); return nca.OpenStorage(type, integrityCheckLevel);
@ -23,7 +25,8 @@ namespace LibHac.IO.NcaUtils
.WriteAllBytes(filename, logger); .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 (index < 0 || index > 3) throw new IndexOutOfRangeException();
if (!nca.SectionIsDecryptable(index)) return; if (!nca.SectionIsDecryptable(index)) return;
@ -55,7 +58,8 @@ namespace LibHac.IO.NcaUtils
NcaHashType hashType = sect.Header.HashType; NcaHashType hashType = sect.Header.HashType;
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; 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 (stream == null) return Validity.Unchecked;
if (!quiet) logger?.LogMessage($"Verifying section {index}..."); if (!quiet) logger?.LogMessage($"Verifying section {index}...");