mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Implement NcaReader from FS 17.0.0
This commit is contained in:
parent
8027310320
commit
b88d283fbc
9 changed files with 880 additions and 15 deletions
|
@ -19,12 +19,12 @@ public struct QueryRangeInfo
|
|||
AesCtrKeyType |= other.AesCtrKeyType;
|
||||
SpeedEmulationType |= other.SpeedEmulationType;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AesCtrKeyTypeFlag
|
||||
{
|
||||
InternalKeyForSoftwareAes = 1 << 0,
|
||||
InternalKeyForHardwareAes = 1 << 1,
|
||||
ExternalKeyForHardwareAes = 1 << 2
|
||||
}
|
||||
[Flags]
|
||||
public enum AesCtrKeyTypeFlag
|
||||
{
|
||||
InternalKeyForSoftwareAes = 1 << 0,
|
||||
InternalKeyForHardwareAes = 1 << 1,
|
||||
ExternalKeyForHardwareAes = 1 << 2
|
||||
}
|
|
@ -404,8 +404,8 @@ public class AesCtrCounterExtendedStorage : IStorage
|
|||
Unsafe.SkipInit(out QueryRangeInfo info);
|
||||
info.Clear();
|
||||
info.AesCtrKeyType = (int)(_decryptor.Get.HasExternalDecryptionKey()
|
||||
? QueryRangeInfo.AesCtrKeyTypeFlag.ExternalKeyForHardwareAes
|
||||
: QueryRangeInfo.AesCtrKeyTypeFlag.InternalKeyForHardwareAes);
|
||||
? AesCtrKeyTypeFlag.ExternalKeyForHardwareAes
|
||||
: AesCtrKeyTypeFlag.InternalKeyForHardwareAes);
|
||||
|
||||
outInfo.Merge(in info);
|
||||
|
||||
|
|
|
@ -215,7 +215,7 @@ public class AesCtrStorage : IStorage
|
|||
|
||||
Unsafe.SkipInit(out QueryRangeInfo info);
|
||||
info.Clear();
|
||||
info.AesCtrKeyType = (int)QueryRangeInfo.AesCtrKeyTypeFlag.InternalKeyForSoftwareAes;
|
||||
info.AesCtrKeyType = (int)AesCtrKeyTypeFlag.InternalKeyForSoftwareAes;
|
||||
|
||||
outInfo.Merge(in info);
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ file struct StorageNode
|
|||
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
|
||||
public partial class BucketTree : IDisposable
|
||||
{
|
||||
private const uint Signature = 0x52544B42; // BKTR
|
||||
public const uint Signature = 0x52544B42; // BKTR
|
||||
private const int MaxVersion = 1;
|
||||
|
||||
private const int NodeSizeMin = 1024;
|
||||
|
|
|
@ -10,7 +10,14 @@ public enum CompressionType : byte
|
|||
Unknown = 4
|
||||
}
|
||||
|
||||
public delegate Result DecompressorFunction(Span<byte> destination, ReadOnlySpan<byte> source);
|
||||
public ref struct DecompressionTask
|
||||
{
|
||||
public Span<byte> Destination;
|
||||
public ReadOnlySpan<byte> Source;
|
||||
}
|
||||
|
||||
public delegate Result DecompressorFunction(DecompressionTask task);
|
||||
|
||||
public delegate DecompressorFunction GetDecompressorFunction(CompressionType compressionType);
|
||||
|
||||
public static class CompressionTypeUtility
|
||||
|
|
13
src/LibHac/FsSystem/IAesCtrDecryptor.cs
Normal file
13
src/LibHac/FsSystem/IAesCtrDecryptor.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Spl;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public interface IAesCtrDecryptor : IDisposable
|
||||
{
|
||||
Result Decrypt(Span<byte> destination, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
|
||||
void PrioritizeSw();
|
||||
void SetExternalKeySource(in Spl.AccessKey keySource);
|
||||
AesCtrKeyTypeFlag GetKeyTypeFlag();
|
||||
}
|
|
@ -1,12 +1,51 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
using static LibHac.FsSystem.Anonymous;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
file static class Anonymous
|
||||
{
|
||||
public static bool IsZero<T>(ReadOnlySpan<T> value) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> valueBytes = MemoryMarshal.Cast<T, byte>(value);
|
||||
Span<byte> zero = stackalloc byte[valueBytes.Length];
|
||||
zero.Clear();
|
||||
|
||||
return CryptoUtil.IsSameBytes(valueBytes, zero, valueBytes.Length);
|
||||
}
|
||||
|
||||
public static bool IsZero<T>(in T value) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> valueBytes = SpanHelpers.AsReadOnlyByteSpan(in value);
|
||||
Span<byte> zero = stackalloc byte[Unsafe.SizeOf<T>()];
|
||||
zero.Clear();
|
||||
|
||||
return CryptoUtil.IsSameBytes(valueBytes, zero, Unsafe.SizeOf<T>());
|
||||
}
|
||||
|
||||
public static bool IsZero<T>(in T value, int startOffset) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> valueBytes = SpanHelpers.AsReadOnlyByteSpan(in value).Slice(startOffset);
|
||||
Span<byte> zero = stackalloc byte[Unsafe.SizeOf<T>() - startOffset];
|
||||
zero.Clear();
|
||||
|
||||
return CryptoUtil.IsSameBytes(valueBytes, zero, Unsafe.SizeOf<T>() - startOffset);
|
||||
}
|
||||
|
||||
public static bool IsIncluded<T>(T value, T min, T max) where T : IComparisonOperators<T, T, bool>
|
||||
{
|
||||
return min <= value && value <= max;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The structure used as the header for an NCA file.
|
||||
/// </summary>
|
||||
|
@ -96,6 +135,76 @@ public struct NcaHeader
|
|||
public static uint ByteToSector(ulong byteIndex) => (uint)(byteIndex >> SectorShift);
|
||||
|
||||
public readonly byte GetProperKeyGeneration() => Math.Max(KeyGeneration1, KeyGeneration2);
|
||||
|
||||
public readonly Result Verify()
|
||||
{
|
||||
const uint magicBodyMask = 0xFFFFFF;
|
||||
const uint magicVersionMask = 0xFF000000;
|
||||
const uint magicBodyValue = 0x41434E; // NCA
|
||||
const uint magicVersionMax = 0x33000000; // \0\0\03
|
||||
|
||||
if ((Magic & magicBodyMask) != magicBodyValue || (Magic & magicVersionMask) > magicVersionMax)
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (!IsIncluded((int)DistributionTypeValue, (int)DistributionType.Download, (int)DistributionType.GameCard))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (!IsIncluded((int)ContentTypeValue, (int)ContentType.Program, (int)ContentType.PublicData))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (KeyAreaEncryptionKeyIndex >= NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount &&
|
||||
KeyAreaEncryptionKeyIndex != NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexZeroKey)
|
||||
{
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
}
|
||||
|
||||
if (ProgramId == 0)
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (SdkAddonVersion == 0)
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (!IsZero(in Reserved222))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (!IsZero(in Reserved224))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
long es = long.MaxValue;
|
||||
|
||||
for (int i = 0; i < FsCountMax; i++)
|
||||
{
|
||||
if (FsInfos[i].StartSector != 0 || FsInfos[i].EndSector != 0)
|
||||
{
|
||||
if (es == long.MaxValue)
|
||||
es = FsInfos[i].EndSector;
|
||||
|
||||
if (es < FsInfos[i].EndSector || FsInfos[i].StartSector >= FsInfos[i].EndSector)
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
es = FsInfos[i].StartSector;
|
||||
|
||||
if (FsInfos[i].HashSectors != ByteToSector(0x200))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
}
|
||||
else if (FsInfos[i].HashSectors != 0)
|
||||
{
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
}
|
||||
|
||||
if (FsInfos[i].Reserved != 0)
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
}
|
||||
|
||||
const int offset = (int)DecryptionKey.Count * Aes.KeySize128;
|
||||
if (!IsZero(EncryptedKeys[..][offset..]))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
if (!IsZero(EncryptedKeys[offset..]))
|
||||
return ResultFs.InvalidNcaHeader.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NcaPatchInfo
|
||||
|
@ -107,8 +216,15 @@ public struct NcaPatchInfo
|
|||
public long AesCtrExSize;
|
||||
public Array16<byte> AesCtrExHeader;
|
||||
|
||||
public readonly bool HasIndirectTable() => IndirectSize != 0;
|
||||
public readonly bool HasAesCtrExTable() => AesCtrExSize != 0;
|
||||
public readonly bool HasIndirectTable()
|
||||
{
|
||||
return Unsafe.As<Array16<byte>, uint>(ref Unsafe.AsRef(in IndirectHeader)) == BucketTree.Signature;
|
||||
}
|
||||
|
||||
public readonly bool HasAesCtrExTable()
|
||||
{
|
||||
return Unsafe.As<Array16<byte>, uint>(ref Unsafe.AsRef(in AesCtrExHeader)) == BucketTree.Signature;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NcaSparseInfo
|
||||
|
@ -129,6 +245,11 @@ public struct NcaSparseInfo
|
|||
sparseUpperIv.Generation = GetGeneration();
|
||||
return sparseUpperIv;
|
||||
}
|
||||
|
||||
public readonly bool HasSparseTable()
|
||||
{
|
||||
return Unsafe.As<Array16<byte>, uint>(ref Unsafe.AsRef(in MetaHeader)) == BucketTree.Signature;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NcaCompressionInfo
|
||||
|
@ -219,7 +340,8 @@ public struct NcaFsHeader
|
|||
public enum MetaDataHashType : byte
|
||||
{
|
||||
None = 0,
|
||||
HierarchicalIntegrity = 1
|
||||
HierarchicalIntegrity = 1,
|
||||
HierarchicalIntegritySha3 = 2
|
||||
}
|
||||
|
||||
public struct Region
|
||||
|
@ -240,10 +362,45 @@ public struct NcaFsHeader
|
|||
public int BlockSize;
|
||||
public int LayerCount;
|
||||
public Array5<Region> LayerRegions;
|
||||
|
||||
public readonly Result Verify()
|
||||
{
|
||||
if (IsZero(in MasterHash))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (BlockSize <= 0 || !BitUtil.IsPowerOfTwo(BlockSize))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (LayerCount != 2)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
long currentOffset = 0;
|
||||
|
||||
for (int i = 0; i < LayerCount; i++)
|
||||
{
|
||||
if (currentOffset > LayerRegions[i].Offset)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (LayerRegions[i].Size <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
currentOffset = LayerRegions[i].Offset + LayerRegions[i].Size;
|
||||
}
|
||||
|
||||
for (int i = LayerCount; i < 5; i++)
|
||||
{
|
||||
if (!IsZero(in LayerRegions[i]))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public struct IntegrityMetaInfo
|
||||
{
|
||||
public const int HashSize = Sha256Generator.HashSize;
|
||||
|
||||
public uint Magic;
|
||||
public uint Version;
|
||||
public uint MasterHashSize;
|
||||
|
@ -269,9 +426,194 @@ public struct NcaFsHeader
|
|||
public Array32<byte> Value;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Result Verify()
|
||||
{
|
||||
if (Magic != HierarchicalIntegrityVerificationStorage.IntegrityVerificationStorageMagic)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (Version != HierarchicalIntegrityVerificationStorage.IntegrityVerificationStorageVersion)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (MasterHashSize != HashSize)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsIncluded(LevelHashInfo.MaxLayers, Constants.IntegrityMinLayerCount, Constants.IntegrityMaxLayerCount))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
long currentOffset = 0;
|
||||
|
||||
for (int i = 0; i < LevelHashInfo.MaxLayers - 1; i++)
|
||||
{
|
||||
ref readonly InfoLevelHash.HierarchicalIntegrityVerificationLevelInformation layer = ref LevelHashInfo.Layers[i];
|
||||
|
||||
if (layer.OrderBlock <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (currentOffset > layer.Offset || !Alignment.IsAligned(layer.Offset.Get(), (ulong)(1 << layer.OrderBlock)))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (layer.Size <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsZero<byte>(layer.Reserved))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
currentOffset = layer.Offset + layer.Size;
|
||||
}
|
||||
|
||||
for (int i = LevelHashInfo.MaxLayers - 1; i < 6; i++)
|
||||
{
|
||||
if (!IsZero(in LevelHashInfo.Layers[i]))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Result Verify()
|
||||
{
|
||||
if (Version != 2)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsIncluded((int)FsTypeValue, (int)FsType.RomFs, (int)FsType.PartitionFs))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsIncluded((int)HashTypeValue, (int)HashType.None, (int)HashType.HierarchicalIntegritySha3Hash) || HashTypeValue == HashType.AutoSha3)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsIncluded((int)EncryptionTypeValue, (int)EncryptionType.None, (int)EncryptionType.AesCtrExSkipLayerHash))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsIncluded((int)MetaDataHashTypeValue, (int)MetaDataHashType.None, (int)MetaDataHashType.HierarchicalIntegritySha3))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsZero(in Reserved))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
switch (HashTypeValue)
|
||||
{
|
||||
case HashType.HierarchicalSha256Hash:
|
||||
case HashType.HierarchicalSha3256Hash:
|
||||
{
|
||||
Result res = HashDataValue.HierarchicalSha256.Verify();
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
if (!IsZero(in HashDataValue, Unsafe.SizeOf<HashData.HierarchicalSha256Data>()))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
break;
|
||||
}
|
||||
case HashType.HierarchicalIntegrityHash:
|
||||
case HashType.HierarchicalIntegritySha3Hash:
|
||||
{
|
||||
Result res = HashDataValue.IntegrityMeta.Verify();
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
if (!IsZero(in HashDataValue, Unsafe.SizeOf<HashData.IntegrityMetaInfo>()))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!IsZero(in HashDataValue))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (EncryptionTypeValue == EncryptionType.AesCtrEx || EncryptionTypeValue == EncryptionType.AesCtrExSkipLayerHash)
|
||||
{
|
||||
if (PatchInfo.IndirectOffset < 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (PatchInfo.IndirectSize <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (IsZero(in PatchInfo.IndirectHeader))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (PatchInfo.AesCtrExOffset < 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (MetaDataHashTypeValue == MetaDataHashType.None)
|
||||
{
|
||||
if (PatchInfo.AesCtrExSize <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
else if (PatchInfo.IndirectOffset == 0)
|
||||
{
|
||||
if (PatchInfo.AesCtrExSize != 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
else if (PatchInfo.AesCtrExSize <= 0)
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
if (IsZero(in PatchInfo.AesCtrExHeader))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
else if (EncryptionTypeValue != EncryptionType.None && !IsZero(in PatchInfo))
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
if (EncryptionTypeValue != EncryptionType.AesCtr
|
||||
&& EncryptionTypeValue != EncryptionType.AesCtrEx
|
||||
&& EncryptionTypeValue != EncryptionType.AesCtrSkipLayerHash
|
||||
&& EncryptionTypeValue != EncryptionType.AesCtrExSkipLayerHash
|
||||
&& !IsZero(in AesCtrUpperIv))
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
if (SparseInfo.Generation != 0)
|
||||
{
|
||||
if (SparseInfo.MetaOffset < 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (SparseInfo.MetaSize < 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (SparseInfo.PhysicalOffset < 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (!IsZero(SparseInfo.Reserved))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
var header = SpanHelpers.AsStruct<BucketTree.Header>(SparseInfo.MetaHeader);
|
||||
Result res = header.Verify();
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
}
|
||||
else if (!IsZero(in SparseInfo))
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
if (MetaDataHashTypeValue != MetaDataHashType.None)
|
||||
{
|
||||
if (MetaDataHashDataInfo.Offset <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
if (MetaDataHashDataInfo.Size <= 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
else if (!IsZero(MetaDataHashDataInfo))
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
if (!IsZero(Padding))
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public readonly Result GetHashTargetOffset(out long outOffset)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out outOffset);
|
||||
|
|
454
src/LibHac/FsSystem/NcaReader17.cs
Normal file
454
src/LibHac/FsSystem/NcaReader17.cs
Normal file
|
@ -0,0 +1,454 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Spl;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Handles reading information from an NCA's header.
|
||||
/// </summary>
|
||||
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
|
||||
public class NcaReader17 : IDisposable
|
||||
{
|
||||
private RuntimeNcaHeader _header;
|
||||
private SharedRef<IStorage> _bodyStorage;
|
||||
private SharedRef<IStorage> _headerStorage;
|
||||
private SharedRef<IAesCtrDecryptor> _aesCtrDecryptor;
|
||||
private GetDecompressorFunction _getDecompressorFunc;
|
||||
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
|
||||
|
||||
public NcaReader17(in RuntimeNcaHeader runtimeNcaHeader, ref readonly SharedRef<IStorage> notVerifiedHeaderStorage,
|
||||
ref readonly SharedRef<IStorage> bodyStorage, ref readonly SharedRef<IAesCtrDecryptor> aesCtrDecryptor,
|
||||
in NcaCompressionConfiguration compressionConfig, IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(in notVerifiedHeaderStorage);
|
||||
Assert.SdkRequiresNotNull(in bodyStorage);
|
||||
Assert.SdkRequiresNotNull(hashGeneratorFactorySelector);
|
||||
|
||||
_header = runtimeNcaHeader;
|
||||
|
||||
_headerStorage = SharedRef<IStorage>.CreateCopy(in notVerifiedHeaderStorage);
|
||||
_bodyStorage = SharedRef<IStorage>.CreateCopy(in bodyStorage);
|
||||
_aesCtrDecryptor = SharedRef<IAesCtrDecryptor>.CreateCopy(in aesCtrDecryptor);
|
||||
|
||||
_getDecompressorFunc = compressionConfig.GetDecompressorFunc;
|
||||
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_bodyStorage.Destroy();
|
||||
_headerStorage.Destroy();
|
||||
_aesCtrDecryptor.Destroy();
|
||||
}
|
||||
|
||||
public Result ReadHeader(out NcaFsHeader outHeader, int index)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out outHeader);
|
||||
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
long offset = _header.FsHeadersOffset + Unsafe.SizeOf<NcaFsHeader>() * (long)index;
|
||||
return _headerStorage.Get.Read(offset, SpanHelpers.AsByteSpan(ref outHeader)).Ret();
|
||||
}
|
||||
|
||||
public Result GetHeaderSign2(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequiresGreaterEqual((uint)outBuffer.Length, _header.Header2SignInfo.Size);
|
||||
|
||||
return _headerStorage.Get
|
||||
.Read(_header.Header2SignInfo.Size, outBuffer.Slice(0, (int)_header.Header2SignInfo.Size)).Ret();
|
||||
}
|
||||
|
||||
public void GetHeaderSign2TargetHash(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequiresEqual(outBuffer.Length, Unsafe.SizeOf<Hash>());
|
||||
|
||||
_header.Header2SignInfo.Hash.Value[..].CopyTo(outBuffer);
|
||||
}
|
||||
|
||||
public SharedRef<IStorage> GetSharedBodyStorage()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
|
||||
return SharedRef<IStorage>.CreateCopy(in _bodyStorage);
|
||||
}
|
||||
|
||||
public NcaHeader.DistributionType GetDistributionType()
|
||||
{
|
||||
return _header.DistributionType;
|
||||
}
|
||||
|
||||
public NcaHeader.ContentType GetContentType()
|
||||
{
|
||||
return _header.ContentType;
|
||||
}
|
||||
|
||||
public byte GetKeyGeneration()
|
||||
{
|
||||
return _header.KeyGeneration;
|
||||
}
|
||||
|
||||
public ulong GetProgramId()
|
||||
{
|
||||
return _header.ProgramId;
|
||||
}
|
||||
|
||||
public void GetRightsId(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequiresGreaterEqual(outBuffer.Length, NcaHeader.RightsIdSize);
|
||||
|
||||
_header.RightsId[..].CopyTo(outBuffer);
|
||||
}
|
||||
|
||||
public bool HasFsInfo(int index)
|
||||
{
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
return _header.FsInfos[index].StartSector != 0 || _header.FsInfos[index].EndSector != 0;
|
||||
}
|
||||
|
||||
public void GetFsHeaderHash(out Hash outHash, int index)
|
||||
{
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
outHash = _header.FsInfos[index].Hash;
|
||||
}
|
||||
|
||||
public void GetFsInfo(out NcaHeader.FsInfo outFsInfo, int index)
|
||||
{
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
outFsInfo = new NcaHeader.FsInfo
|
||||
{
|
||||
StartSector = _header.FsInfos[index].StartSector,
|
||||
EndSector = _header.FsInfos[index].EndSector,
|
||||
HashSectors = _header.FsInfos[index].HashSectors,
|
||||
Reserved = 0
|
||||
};
|
||||
}
|
||||
|
||||
public ulong GetFsOffset(int index)
|
||||
{
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
return NcaHeader.SectorToByte(_header.FsInfos[index].StartSector);
|
||||
}
|
||||
|
||||
public ulong GetFsEndOffset(int index)
|
||||
{
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector);
|
||||
}
|
||||
|
||||
public ulong GetFsSize(int index)
|
||||
{
|
||||
Assert.SdkRequiresInRange(index, 0, NcaHeader.FsCountMax);
|
||||
|
||||
return NcaHeader.SectorToByte(_header.FsInfos[index].EndSector - _header.FsInfos[index].StartSector);
|
||||
}
|
||||
|
||||
public void PrioritizeSwAes()
|
||||
{
|
||||
if (_aesCtrDecryptor.HasValue)
|
||||
{
|
||||
_aesCtrDecryptor.Get.PrioritizeSw();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetExternalDecryptionKey(in AccessKey keySource)
|
||||
{
|
||||
if (_aesCtrDecryptor.HasValue)
|
||||
{
|
||||
_aesCtrDecryptor.Get.SetExternalKeySource(in keySource);
|
||||
}
|
||||
}
|
||||
|
||||
public RuntimeNcaHeader GetHeader()
|
||||
{
|
||||
return _header;
|
||||
}
|
||||
|
||||
public SharedRef<IAesCtrDecryptor> GetDecryptor()
|
||||
{
|
||||
return SharedRef<IAesCtrDecryptor>.CreateCopy(in _aesCtrDecryptor);
|
||||
}
|
||||
|
||||
public GetDecompressorFunction GetDecompressor()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_getDecompressorFunc);
|
||||
return _getDecompressorFunc;
|
||||
}
|
||||
|
||||
public IHash256GeneratorFactorySelector GetHashGeneratorFactorySelector()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactorySelector);
|
||||
return _hashGeneratorFactorySelector;
|
||||
}
|
||||
|
||||
public Result Verify()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_bodyStorage);
|
||||
|
||||
for (int fsIndex = 0; fsIndex < NcaHeader.FsCountMax; fsIndex++)
|
||||
{
|
||||
var reader = new NcaFsHeaderReader17();
|
||||
if (HasFsInfo(fsIndex))
|
||||
{
|
||||
Result res = reader.Initialize(this, fsIndex);
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
res = reader.Verify(_header.ContentType);
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
}
|
||||
else
|
||||
{
|
||||
Result res = ReadHeader(out NcaFsHeader header, fsIndex);
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
NcaFsHeader zero = default;
|
||||
if (!CryptoUtil.IsSameBytes(SpanHelpers.AsReadOnlyByteSpan(in header),
|
||||
SpanHelpers.AsReadOnlyByteSpan(in zero), Unsafe.SizeOf<NcaFsHeader>()))
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles reading information from the <see cref="NcaFsHeader"/> of a file system inside an NCA file.
|
||||
/// </summary>
|
||||
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
|
||||
public class NcaFsHeaderReader17
|
||||
{
|
||||
private NcaFsHeader _header;
|
||||
private int _fsIndex;
|
||||
|
||||
public NcaFsHeaderReader17()
|
||||
{
|
||||
_fsIndex = -1;
|
||||
}
|
||||
|
||||
public bool IsInitialized()
|
||||
{
|
||||
return _fsIndex >= 0;
|
||||
}
|
||||
|
||||
public Result Initialize(NcaReader17 reader, int index)
|
||||
{
|
||||
_fsIndex = -1;
|
||||
|
||||
Result res = reader.ReadHeader(out _header, index);
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
Unsafe.SkipInit(out Hash hash);
|
||||
IHash256GeneratorFactory generator = reader.GetHashGeneratorFactorySelector().GetFactory(HashAlgorithmType.Sha2);
|
||||
generator.GenerateHash(hash.Value, SpanHelpers.AsReadOnlyByteSpan(in _header));
|
||||
|
||||
reader.GetFsHeaderHash(out Hash fsHeaderHash, index);
|
||||
|
||||
if (!CryptoUtil.IsSameBytes(fsHeaderHash.Value, hash.Value, Unsafe.SizeOf<Hash>()))
|
||||
{
|
||||
return ResultFs.NcaFsHeaderHashVerificationFailed.Log();
|
||||
}
|
||||
|
||||
_fsIndex = index;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public ref readonly NcaFsHeader.HashData GetHashData()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.HashDataValue;
|
||||
}
|
||||
|
||||
public ushort GetVersion()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.Version;
|
||||
}
|
||||
|
||||
public int GetFsIndex()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _fsIndex;
|
||||
}
|
||||
|
||||
public NcaFsHeader.FsType GetFsType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.FsTypeValue;
|
||||
}
|
||||
|
||||
public NcaFsHeader.HashType GetHashType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.HashTypeValue;
|
||||
}
|
||||
|
||||
public NcaFsHeader.EncryptionType GetEncryptionType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.EncryptionTypeValue;
|
||||
}
|
||||
|
||||
public NcaFsHeader.MetaDataHashType GetPatchMetaHashType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashTypeValue;
|
||||
}
|
||||
|
||||
public NcaFsHeader.MetaDataHashType GetSparseMetaHashType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashTypeValue;
|
||||
}
|
||||
|
||||
public Result GetHashTargetOffset(out long outOffset)
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
|
||||
Result res = _header.GetHashTargetOffset(out outOffset);
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public bool IsSkipLayerHashEncryption()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.IsSkipLayerHashEncryption();
|
||||
}
|
||||
|
||||
public ref readonly NcaPatchInfo GetPatchInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.PatchInfo;
|
||||
}
|
||||
|
||||
public NcaAesCtrUpperIv GetAesCtrUpperIv()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.AesCtrUpperIv;
|
||||
}
|
||||
|
||||
public bool ExistsSparseLayer()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.SparseInfo.Generation != 0;
|
||||
}
|
||||
|
||||
public ref readonly NcaSparseInfo GetSparseInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.SparseInfo;
|
||||
}
|
||||
|
||||
public bool ExistsCompressionLayer()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.CompressionInfo.TableOffset != 0 && _header.CompressionInfo.TableSize != 0;
|
||||
}
|
||||
|
||||
public ref readonly NcaCompressionInfo GetCompressionInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.CompressionInfo;
|
||||
}
|
||||
|
||||
public bool ExistsPatchMetaHashLayer()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashDataInfo.Size != 0 && GetPatchInfo().HasIndirectTable();
|
||||
}
|
||||
|
||||
public bool ExistsSparseMetaHashLayer()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashDataInfo.Size != 0 && ExistsSparseLayer();
|
||||
}
|
||||
|
||||
public ref readonly NcaMetaDataHashDataInfo GetPatchMetaDataHashDataInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.MetaDataHashDataInfo;
|
||||
}
|
||||
|
||||
public ref readonly NcaMetaDataHashDataInfo GetSparseMetaDataHashDataInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.MetaDataHashDataInfo;
|
||||
}
|
||||
|
||||
public void GetRawData(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
Assert.SdkRequiresLessEqual(Unsafe.SizeOf<NcaFsHeader>(), outBuffer.Length);
|
||||
|
||||
SpanHelpers.AsReadOnlyByteSpan(in _header).CopyTo(outBuffer);
|
||||
}
|
||||
|
||||
public Result Verify(NcaHeader.ContentType contentType)
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
Assert.SdkRequiresWithinMinMax((int)contentType, (int)NcaHeader.ContentType.Program, (int)NcaHeader.ContentType.PublicData);
|
||||
|
||||
Result res = _header.Verify();
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
|
||||
const uint programSecureValue = 1;
|
||||
const uint dataSecureValue = 2;
|
||||
const uint htmlDocumentSecureValue = 4;
|
||||
const uint legalInformationSecureValue = 5;
|
||||
|
||||
// Mask out the program index part of the secure value
|
||||
uint secureValue = _header.AesCtrUpperIv.SecureValue & 0xFFFFFF;
|
||||
|
||||
if (GetEncryptionType() == NcaFsHeader.EncryptionType.None)
|
||||
{
|
||||
if (secureValue != 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
switch (contentType)
|
||||
{
|
||||
case NcaHeader.ContentType.Program:
|
||||
switch (_fsIndex)
|
||||
{
|
||||
case 0:
|
||||
if (secureValue != programSecureValue)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
break;
|
||||
case 1:
|
||||
if (secureValue != dataSecureValue)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
break;
|
||||
default:
|
||||
if (secureValue != 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case NcaHeader.ContentType.Manual:
|
||||
if (secureValue != htmlDocumentSecureValue && secureValue != legalInformationSecureValue)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
break;
|
||||
default:
|
||||
if (secureValue != 0)
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
49
src/LibHac/FsSystem/RuntimeNcaHeader.cs
Normal file
49
src/LibHac/FsSystem/RuntimeNcaHeader.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using LibHac.Common.FixedArrays;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public struct RuntimeKeySourceInfo
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Size;
|
||||
public Hash Hash;
|
||||
}
|
||||
|
||||
public struct RuntimeNcaHeader
|
||||
{
|
||||
public struct FsInfo
|
||||
{
|
||||
public uint StartSector;
|
||||
public uint EndSector;
|
||||
public uint HashSectors;
|
||||
public Hash Hash;
|
||||
}
|
||||
|
||||
public struct SignInfo
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Size;
|
||||
public Hash Hash;
|
||||
}
|
||||
|
||||
public NcaHeader.DistributionType DistributionType;
|
||||
public NcaHeader.ContentType ContentType;
|
||||
public byte KeyGeneration;
|
||||
public ulong ProgramId;
|
||||
public Array16<byte> RightsId;
|
||||
public uint FsHeadersOffset;
|
||||
public Array4<FsInfo> FsInfos;
|
||||
public SignInfo Header2SignInfo;
|
||||
public RuntimeKeySourceInfo KeySourceInfo;
|
||||
|
||||
public static Result CheckUnsupportedVersion(uint magicValue)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result InitializeCommonForV3(in NcaHeader header, IHash256GeneratorFactorySelector hashGeneratorFactorySelector)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue