diff --git a/src/LibHac/Common/FixedArrays/Array6.cs b/src/LibHac/Common/FixedArrays/Array6.cs new file mode 100644 index 00000000..0fe6087c --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array6.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array6 +{ + 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 Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array6 value) => value.ItemsRo; +} diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 05bb8412..71452022 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.Common.Keys; using LibHac.Crypto; @@ -148,12 +149,71 @@ public class Nca 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); @@ -170,7 +230,7 @@ public class Nca 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: @@ -191,13 +251,12 @@ public class Nca } // 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); } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs index 670e0b85..3c9909ff 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs @@ -61,6 +61,17 @@ public struct NcaFsHeader return GetPatchInfo().RelocationTreeSize != 0; } + public ref NcaSparseInfo GetSparseInfo() + { + return ref MemoryMarshal.Cast(_header.Span.Slice(FsHeaderStruct.SparseInfoOffset, + FsHeaderStruct.SparseInfoSize))[0]; + } + + public bool ExistsSparseLayer() + { + return GetSparseInfo().Generation != 0; + } + public ulong Counter { get => Header.UpperCounter; @@ -86,6 +97,8 @@ public struct NcaFsHeader 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; diff --git a/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs b/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs index c4f75b33..e7d58fb9 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs @@ -2,9 +2,9 @@ internal enum NcaKeyType { - AesXts0, - AesXts1, - AesCtr, - Type3, - Type4 + AesXts0 = 0, + AesXts1 = 1, + AesCtr = 2, + AesCtrEx = 3, + AesCtrHw = 4 } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs b/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs index 621c549f..8ef6757d 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs @@ -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 { @@ -34,6 +38,42 @@ public class TitleVersion } } +public struct NcaSparseInfo +{ + public long MetaOffset; + public long MetaSize; + public Array16 MetaHeader; + public long PhysicalOffset; + public ushort Generation; + private Array6 _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,