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,6 +19,7 @@ public struct QueryRangeInfo
|
||||||
AesCtrKeyType |= other.AesCtrKeyType;
|
AesCtrKeyType |= other.AesCtrKeyType;
|
||||||
SpeedEmulationType |= other.SpeedEmulationType;
|
SpeedEmulationType |= other.SpeedEmulationType;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum AesCtrKeyTypeFlag
|
public enum AesCtrKeyTypeFlag
|
||||||
|
@ -27,4 +28,3 @@ public struct QueryRangeInfo
|
||||||
InternalKeyForHardwareAes = 1 << 1,
|
InternalKeyForHardwareAes = 1 << 1,
|
||||||
ExternalKeyForHardwareAes = 1 << 2
|
ExternalKeyForHardwareAes = 1 << 2
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -404,8 +404,8 @@ public class AesCtrCounterExtendedStorage : IStorage
|
||||||
Unsafe.SkipInit(out QueryRangeInfo info);
|
Unsafe.SkipInit(out QueryRangeInfo info);
|
||||||
info.Clear();
|
info.Clear();
|
||||||
info.AesCtrKeyType = (int)(_decryptor.Get.HasExternalDecryptionKey()
|
info.AesCtrKeyType = (int)(_decryptor.Get.HasExternalDecryptionKey()
|
||||||
? QueryRangeInfo.AesCtrKeyTypeFlag.ExternalKeyForHardwareAes
|
? AesCtrKeyTypeFlag.ExternalKeyForHardwareAes
|
||||||
: QueryRangeInfo.AesCtrKeyTypeFlag.InternalKeyForHardwareAes);
|
: AesCtrKeyTypeFlag.InternalKeyForHardwareAes);
|
||||||
|
|
||||||
outInfo.Merge(in info);
|
outInfo.Merge(in info);
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ public class AesCtrStorage : IStorage
|
||||||
|
|
||||||
Unsafe.SkipInit(out QueryRangeInfo info);
|
Unsafe.SkipInit(out QueryRangeInfo info);
|
||||||
info.Clear();
|
info.Clear();
|
||||||
info.AesCtrKeyType = (int)QueryRangeInfo.AesCtrKeyTypeFlag.InternalKeyForSoftwareAes;
|
info.AesCtrKeyType = (int)AesCtrKeyTypeFlag.InternalKeyForSoftwareAes;
|
||||||
|
|
||||||
outInfo.Merge(in info);
|
outInfo.Merge(in info);
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ file struct StorageNode
|
||||||
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
|
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
|
||||||
public partial class BucketTree : IDisposable
|
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 MaxVersion = 1;
|
||||||
|
|
||||||
private const int NodeSizeMin = 1024;
|
private const int NodeSizeMin = 1024;
|
||||||
|
|
|
@ -10,7 +10,14 @@ public enum CompressionType : byte
|
||||||
Unknown = 4
|
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 delegate DecompressorFunction GetDecompressorFunction(CompressionType compressionType);
|
||||||
|
|
||||||
public static class CompressionTypeUtility
|
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;
|
||||||
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Common.FixedArrays;
|
using LibHac.Common.FixedArrays;
|
||||||
|
using LibHac.Crypto;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
|
using LibHac.Util;
|
||||||
|
using static LibHac.FsSystem.Anonymous;
|
||||||
|
|
||||||
namespace LibHac.FsSystem;
|
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>
|
/// <summary>
|
||||||
/// The structure used as the header for an NCA file.
|
/// The structure used as the header for an NCA file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -96,6 +135,76 @@ public struct NcaHeader
|
||||||
public static uint ByteToSector(ulong byteIndex) => (uint)(byteIndex >> SectorShift);
|
public static uint ByteToSector(ulong byteIndex) => (uint)(byteIndex >> SectorShift);
|
||||||
|
|
||||||
public readonly byte GetProperKeyGeneration() => Math.Max(KeyGeneration1, KeyGeneration2);
|
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
|
public struct NcaPatchInfo
|
||||||
|
@ -107,8 +216,15 @@ public struct NcaPatchInfo
|
||||||
public long AesCtrExSize;
|
public long AesCtrExSize;
|
||||||
public Array16<byte> AesCtrExHeader;
|
public Array16<byte> AesCtrExHeader;
|
||||||
|
|
||||||
public readonly bool HasIndirectTable() => IndirectSize != 0;
|
public readonly bool HasIndirectTable()
|
||||||
public readonly bool HasAesCtrExTable() => AesCtrExSize != 0;
|
{
|
||||||
|
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
|
public struct NcaSparseInfo
|
||||||
|
@ -129,6 +245,11 @@ public struct NcaSparseInfo
|
||||||
sparseUpperIv.Generation = GetGeneration();
|
sparseUpperIv.Generation = GetGeneration();
|
||||||
return sparseUpperIv;
|
return sparseUpperIv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly bool HasSparseTable()
|
||||||
|
{
|
||||||
|
return Unsafe.As<Array16<byte>, uint>(ref Unsafe.AsRef(in MetaHeader)) == BucketTree.Signature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct NcaCompressionInfo
|
public struct NcaCompressionInfo
|
||||||
|
@ -219,7 +340,8 @@ public struct NcaFsHeader
|
||||||
public enum MetaDataHashType : byte
|
public enum MetaDataHashType : byte
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
HierarchicalIntegrity = 1
|
HierarchicalIntegrity = 1,
|
||||||
|
HierarchicalIntegritySha3 = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Region
|
public struct Region
|
||||||
|
@ -240,10 +362,45 @@ public struct NcaFsHeader
|
||||||
public int BlockSize;
|
public int BlockSize;
|
||||||
public int LayerCount;
|
public int LayerCount;
|
||||||
public Array5<Region> LayerRegions;
|
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 struct IntegrityMetaInfo
|
||||||
{
|
{
|
||||||
|
public const int HashSize = Sha256Generator.HashSize;
|
||||||
|
|
||||||
public uint Magic;
|
public uint Magic;
|
||||||
public uint Version;
|
public uint Version;
|
||||||
public uint MasterHashSize;
|
public uint MasterHashSize;
|
||||||
|
@ -269,7 +426,192 @@ public struct NcaFsHeader
|
||||||
public Array32<byte> Value;
|
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)
|
public readonly Result GetHashTargetOffset(out long 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