Skip to content

Commit

Permalink
Support NCAs with sparse partitions
Browse files Browse the repository at this point in the history
  • Loading branch information
Thealexbarney committed Dec 4, 2021
1 parent 921fbab commit b5ccde1
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 14 deletions.
26 changes: 26 additions & 0 deletions src/LibHac/Common/FixedArrays/Array6.cs
Original file line number Diff line number Diff line change
@@ -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;
}
75 changes: 67 additions & 8 deletions src/LibHac/FsSystem/NcaUtils/Nca.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Crypto;
Expand Down Expand Up @@ -148,12 +149,71 @@ private IStorage OpenSectionStorage(int index)
long offset = Header.GetSectionStartOffset(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(
$"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);
Expand All @@ -170,7 +230,7 @@ private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decr
case NcaEncryptionType.XTS:
return OpenAesXtsStorage(baseStorage, index, decrypting);
case NcaEncryptionType.AesCtr:
return OpenAesCtrStorage(baseStorage, index);
return OpenAesCtrStorage(baseStorage, index, Header.GetSectionStartOffset(index), header.Counter);
case NcaEncryptionType.AesCtrEx:
return OpenAesCtrExStorage(baseStorage, index, decrypting);
default:
Expand All @@ -191,13 +251,12 @@ private IStorage OpenAesXtsStorage(IStorage baseStorage, int index, bool decrypt
}
// 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[] 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);
}

Expand Down
13 changes: 13 additions & 0 deletions src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ public bool IsPatchSection()
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
{
get => Header.UpperCounter;
Expand All @@ -86,6 +97,8 @@ private struct FsHeaderStruct
public const int IntegrityInfoSize = 0xF8;
public const int PatchInfoOffset = 0x100;
public const int PatchInfoSize = 0x40;
public const int SparseInfoOffset = 0x148;
public const int SparseInfoSize = 0x30;

[FieldOffset(0)] public short Version;
[FieldOffset(2)] public byte FormatType;
Expand Down
10 changes: 5 additions & 5 deletions src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

internal enum NcaKeyType
{
AesXts0,
AesXts1,
AesCtr,
Type3,
Type4
AesXts0 = 0,
AesXts1 = 1,
AesCtr = 2,
AesCtrEx = 3,
AesCtrHw = 4
}
42 changes: 41 additions & 1 deletion src/LibHac/FsSystem/NcaUtils/NcaStructs.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -34,6 +38,42 @@ public override string ToString()
}
}

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
{
Code,
Expand Down

0 comments on commit b5ccde1

Please sign in to comment.