Support NCAs with sparse partitions

This commit is contained in:
Alex Barney 2021-12-04 02:00:46 -07:00
parent 921fbab17a
commit b5ccde1a29
5 changed files with 152 additions and 14 deletions

View file

@ -0,0 +1,26 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays;
[StructLayout(LayoutKind.Sequential)]
public struct Array6<T>
{
public const int Length = 6;
private T _1;
private T _2;
private T _3;
private T _4;
private T _5;
private T _6;
public ref T this[int i] => ref Items[i];
public Span<T> Items => SpanHelpers.CreateSpan(ref _1, Length);
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array6<T> value) => value.ItemsRo;
}

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using LibHac.Common; using LibHac.Common;
using LibHac.Common.Keys; using LibHac.Common.Keys;
using LibHac.Crypto; using LibHac.Crypto;
@ -148,12 +149,71 @@ public class Nca
long offset = Header.GetSectionStartOffset(index); long offset = Header.GetSectionStartOffset(index);
long size = Header.GetSectionSize(index); long size = Header.GetSectionSize(index);
BaseStorage.GetSize(out long baseSize).ThrowIfFailure(); BaseStorage.GetSize(out long ncaStorageSize).ThrowIfFailure();
if (!IsSubRange(offset, size, baseSize)) NcaFsHeader fsHeader = Header.GetFsHeader(index);
if (fsHeader.ExistsSparseLayer())
{
ref NcaSparseInfo sparseInfo = ref fsHeader.GetSparseInfo();
Unsafe.SkipInit(out BucketTree.Header header);
sparseInfo.MetaHeader.ItemsRo.CopyTo(SpanHelpers.AsByteSpan(ref header));
header.Verify().ThrowIfFailure();
var sparseStorage = new SparseStorage();
if (header.EntryCount == 0)
{
sparseStorage.Initialize(size);
}
else
{
long dataSize = sparseInfo.GetPhysicalSize();
if (!IsSubRange(sparseInfo.PhysicalOffset, dataSize, ncaStorageSize))
{
throw new InvalidDataException(
$"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{ncaStorageSize:x}).");
}
IStorage baseStorage = BaseStorage.Slice(sparseInfo.PhysicalOffset, dataSize);
baseStorage.GetSize(out long baseStorageSize).ThrowIfFailure();
long metaOffset = sparseInfo.MetaOffset;
long metaSize = sparseInfo.MetaSize;
if (metaOffset - sparseInfo.PhysicalOffset + metaSize > baseStorageSize)
ResultFs.NcaBaseStorageOutOfRangeB.Value.ThrowIfFailure();
IStorage metaStorageEncrypted = baseStorage.Slice(metaOffset, metaSize);
ulong upperCounter = sparseInfo.MakeAesCtrUpperIv(new NcaAesCtrUpperIv(fsHeader.Counter)).Value;
IStorage metaStorage = OpenAesCtrStorage(metaStorageEncrypted, index, sparseInfo.PhysicalOffset + metaOffset, upperCounter);
long nodeOffset = 0;
long nodeSize = IndirectStorage.QueryNodeStorageSize(header.EntryCount);
long entryOffset = nodeOffset + nodeSize;
long entrySize = IndirectStorage.QueryEntryStorageSize(header.EntryCount);
using var nodeStorage = new ValueSubStorage(metaStorage, nodeOffset, nodeSize);
using var entryStorage = new ValueSubStorage(metaStorage, entryOffset, entrySize);
new SubStorage(metaStorage, nodeOffset, nodeSize).WriteAllBytes("nodeStorage");
sparseStorage.Initialize(new ArrayPoolMemoryResource(), in nodeStorage, in entryStorage, header.EntryCount).ThrowIfFailure();
using var dataStorage = new ValueSubStorage(baseStorage, 0, sparseInfo.GetPhysicalSize());
sparseStorage.SetDataStorage(in dataStorage);
}
return sparseStorage;
}
if (!IsSubRange(offset, size, ncaStorageSize))
{ {
throw new InvalidDataException( throw new InvalidDataException(
$"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize:x})."); $"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{ncaStorageSize:x}).");
} }
return BaseStorage.Slice(offset, size); return BaseStorage.Slice(offset, size);
@ -170,7 +230,7 @@ public class Nca
case NcaEncryptionType.XTS: case NcaEncryptionType.XTS:
return OpenAesXtsStorage(baseStorage, index, decrypting); return OpenAesXtsStorage(baseStorage, index, decrypting);
case NcaEncryptionType.AesCtr: case NcaEncryptionType.AesCtr:
return OpenAesCtrStorage(baseStorage, index); return OpenAesCtrStorage(baseStorage, index, Header.GetSectionStartOffset(index), header.Counter);
case NcaEncryptionType.AesCtrEx: case NcaEncryptionType.AesCtrEx:
return OpenAesCtrExStorage(baseStorage, index, decrypting); return OpenAesCtrExStorage(baseStorage, index, decrypting);
default: default:
@ -191,13 +251,12 @@ public class Nca
} }
// ReSharper restore UnusedParameter.Local // ReSharper restore UnusedParameter.Local
private IStorage OpenAesCtrStorage(IStorage baseStorage, int index) private IStorage OpenAesCtrStorage(IStorage baseStorage, int index, long offset, ulong upperCounter)
{ {
NcaFsHeader fsHeader = GetFsHeader(index);
byte[] key = GetContentKey(NcaKeyType.AesCtr); byte[] key = GetContentKey(NcaKeyType.AesCtr);
byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, Header.GetSectionStartOffset(index)); byte[] counter = Aes128CtrStorage.CreateCounter(upperCounter, Header.GetSectionStartOffset(index));
var aesStorage = new Aes128CtrStorage(baseStorage, key, Header.GetSectionStartOffset(index), counter, true); var aesStorage = new Aes128CtrStorage(baseStorage, key, offset, counter, true);
return new CachedStorage(aesStorage, 0x4000, 4, true); return new CachedStorage(aesStorage, 0x4000, 4, true);
} }

View file

@ -61,6 +61,17 @@ public struct NcaFsHeader
return GetPatchInfo().RelocationTreeSize != 0; return GetPatchInfo().RelocationTreeSize != 0;
} }
public ref NcaSparseInfo GetSparseInfo()
{
return ref MemoryMarshal.Cast<byte, NcaSparseInfo>(_header.Span.Slice(FsHeaderStruct.SparseInfoOffset,
FsHeaderStruct.SparseInfoSize))[0];
}
public bool ExistsSparseLayer()
{
return GetSparseInfo().Generation != 0;
}
public ulong Counter public ulong Counter
{ {
get => Header.UpperCounter; get => Header.UpperCounter;
@ -86,6 +97,8 @@ public struct NcaFsHeader
public const int IntegrityInfoSize = 0xF8; public const int IntegrityInfoSize = 0xF8;
public const int PatchInfoOffset = 0x100; public const int PatchInfoOffset = 0x100;
public const int PatchInfoSize = 0x40; public const int PatchInfoSize = 0x40;
public const int SparseInfoOffset = 0x148;
public const int SparseInfoSize = 0x30;
[FieldOffset(0)] public short Version; [FieldOffset(0)] public short Version;
[FieldOffset(2)] public byte FormatType; [FieldOffset(2)] public byte FormatType;

View file

@ -2,9 +2,9 @@
internal enum NcaKeyType internal enum NcaKeyType
{ {
AesXts0, AesXts0 = 0,
AesXts1, AesXts1 = 1,
AesCtr, AesCtr = 2,
Type3, AesCtrEx = 3,
Type4 AesCtrHw = 4
} }

View file

@ -1,4 +1,8 @@
namespace LibHac.FsSystem.NcaUtils; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common.FixedArrays;
namespace LibHac.FsSystem.NcaUtils;
public class TitleVersion public class TitleVersion
{ {
@ -34,6 +38,42 @@ public class TitleVersion
} }
} }
public struct NcaSparseInfo
{
public long MetaOffset;
public long MetaSize;
public Array16<byte> MetaHeader;
public long PhysicalOffset;
public ushort Generation;
private Array6<byte> _reserved;
public readonly uint GetGeneration() => (uint)(Generation << 16);
public readonly long GetPhysicalSize() => MetaOffset + MetaSize;
public readonly NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upperIv)
{
NcaAesCtrUpperIv sparseUpperIv = upperIv;
sparseUpperIv.Generation = GetGeneration();
return sparseUpperIv;
}
}
[StructLayout(LayoutKind.Explicit)]
public struct NcaAesCtrUpperIv
{
[FieldOffset(0)] public ulong Value;
[FieldOffset(0)] public uint Generation;
[FieldOffset(4)] public uint SecureValue;
internal NcaAesCtrUpperIv(ulong value)
{
Unsafe.SkipInit(out Generation);
Unsafe.SkipInit(out SecureValue);
Value = value;
}
}
public enum NcaSectionType public enum NcaSectionType
{ {
Code, Code,