mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Support NCAs with sparse partitions
This commit is contained in:
parent
921fbab17a
commit
b5ccde1a29
5 changed files with 152 additions and 14 deletions
26
src/LibHac/Common/FixedArrays/Array6.cs
Normal file
26
src/LibHac/Common/FixedArrays/Array6.cs
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,17 @@ public struct NcaFsHeader
|
|||
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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
internal enum NcaKeyType
|
||||
{
|
||||
AesXts0,
|
||||
AesXts1,
|
||||
AesCtr,
|
||||
Type3,
|
||||
Type4
|
||||
AesXts0 = 0,
|
||||
AesXts1 = 1,
|
||||
AesCtr = 2,
|
||||
AesCtrEx = 3,
|
||||
AesCtrHw = 4
|
||||
}
|
||||
|
|
|
@ -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<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,
|
||||
|
|
Loading…
Reference in a new issue