diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index ab43adcb..bfbc7048 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -388,8 +388,29 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 428,51,,ServiceNotInitialized, 428,1000,1999,InvalidData, -428,1001,1019,InvalidKip, -428,1002,,InvalidKipFileSize,The size of the KIP file was smaller than expected. -428,1003,,InvalidKipMagic,The magic value of the KIP file was not KIP1. -428,1004,,InvalidKipSegmentSize,The size of the compressed KIP segment was smaller than expected. -428,1005,,KipSegmentDecompressionFailed,An error occurred while decompressing a KIP segment. +428,1001,1019,InvalidInitialProcessData, +428,1002,1009,InvalidKip, +428,1003,,InvalidKipFileSize,The size of the KIP file was smaller than expected. +428,1004,,InvalidKipMagic,The magic value of the KIP file was not KIP1. +428,1005,,InvalidKipSegmentSize,The size of the compressed KIP segment was smaller than expected. +428,1006,,KipSegmentDecompressionFailed,An error occurred while decompressing a KIP segment. + +428,1010,1019,InvalidIni, +428,1011,,InvalidIniFileSize,The size of the INI file was smaller than expected. +428,1012,,InvalidIniMagic,The magic value of the INI file was not INI1. +428,1013,,InvalidIniProcessCount,The INI had an invalid process count. + +428,1020,1039,InvalidPackage2, +428,1021,,InvalidPackage2HeaderSignature, +428,1022,,InvalidPackage2MetaSizeA, +428,1023,,InvalidPackage2MetaSizeB, +428,1024,,InvalidPackage2MetaKeyGeneration, +428,1025,,InvalidPackage2MetaMagic, +428,1026,,InvalidPackage2MetaEntryPointAlignment, +428,1027,,InvalidPackage2MetaPayloadAlignment, +428,1028,,InvalidPackage2MetaPayloadSizeAlignment, +428,1029,,InvalidPackage2MetaTotalSize, +428,1030,,InvalidPackage2MetaPayloadSize, +428,1031,,InvalidPackage2MetaPayloadsOverlap, +428,1032,,InvalidPackage2MetaEntryPointNotFound, +428,1033,,InvalidPackage2PayloadCorrupted, diff --git a/src/LibHac/Boot/Package2.cs b/src/LibHac/Boot/Package2.cs new file mode 100644 index 00000000..6099ccd4 --- /dev/null +++ b/src/LibHac/Boot/Package2.cs @@ -0,0 +1,164 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Crypto; +using LibHac.Util; +#if DEBUG +using System.Diagnostics; +#endif + +namespace LibHac.Boot +{ + [StructLayout(LayoutKind.Explicit, Size = 0x200)] + public struct Package2Header + { + internal const int Package2SizeMax = (1024 * 1024 * 8) - (1024 * 16); // 8MB - 16KB + internal const int PayloadAlignment = 4; + internal const int PayloadCount = 3; + + internal const int SignatureSize = 0x100; + private ReadOnlySpan RsaPublicKeyExponent => new byte[] { 0x00, 0x01, 0x00, 0x01 }; + + [FieldOffset(0x00)] private byte _signature; + [FieldOffset(0x100)] public Package2Meta Meta; + + public ReadOnlySpan Signature => SpanHelpers.CreateSpan(ref _signature, SignatureSize); + + public Result VerifySignature(ReadOnlySpan modulus, ReadOnlySpan data) + { + if (!Rsa.VerifyRsa2048PssSha256(Signature, modulus, RsaPublicKeyExponent, data)) + { + return ResultLibHac.InvalidPackage2HeaderSignature.Log(); + } + + return Result.Success; + } + +#if DEBUG + [DebuggerBrowsable(DebuggerBrowsableState.Never)] [FieldOffset(0x00)] private readonly Padding100 PaddingForVsDebugging; +#endif + } + + [StructLayout(LayoutKind.Explicit, Size = 0x100)] + public struct Package2Meta + { + public const uint ExpectedMagicValue = 0x31324B50; // PK21 + + [FieldOffset(0x00)] private Buffer16 _headerIv; + + [FieldOffset(0x00)] private uint _package2Size; + [FieldOffset(0x04)] private byte _keyGeneration; + + [FieldOffset(0x06)] private byte _keyGenerationXor1; + [FieldOffset(0x07)] private byte _keyGenerationXor2; + [FieldOffset(0x08)] private uint _sizeXor1; + [FieldOffset(0x0C)] private uint _sizeXor2; + + [FieldOffset(0x10)] private Buffer16 _payloadIvs; + + [FieldOffset(0x50)] private readonly uint _magic; + [FieldOffset(0x54)] private readonly uint _entryPoint; + [FieldOffset(0x5C)] private readonly byte _package2Version; + [FieldOffset(0x5D)] private readonly byte _bootloaderVersion; + + [FieldOffset(0x60)] private uint _payloadSizes; + [FieldOffset(0x70)] private uint _payloadOffsets; + [FieldOffset(0x80)] private Buffer32 _payloadHashes; + + public uint Magic => _magic; + public uint EntryPoint => _entryPoint; + public byte Package2Version => _package2Version; + public byte BootloaderVersion => _bootloaderVersion; + + public Buffer16 HeaderIv => _headerIv; + public readonly uint Size => _package2Size ^ _sizeXor1 ^ _sizeXor2; + public byte KeyGeneration => (byte)Math.Max(0, (_keyGeneration ^ _keyGenerationXor1 ^ _keyGenerationXor2) - 1); + + public ReadOnlySpan PayloadIvs => SpanHelpers.CreateSpan(ref _payloadIvs, Package2Header.PayloadCount); + public ReadOnlySpan PayloadSizes => SpanHelpers.CreateSpan(ref _payloadSizes, Package2Header.PayloadCount); + public ReadOnlySpan PayloadOffsets => SpanHelpers.CreateSpan(ref _payloadOffsets, Package2Header.PayloadCount); + public ReadOnlySpan PayloadHashes => SpanHelpers.CreateSpan(ref _payloadHashes, Package2Header.PayloadCount); + + public int GetPayloadFileOffset(int index) + { + if ((uint)index >= Package2Header.PayloadCount) + throw new IndexOutOfRangeException("Invalid payload index."); + + int offset = Unsafe.SizeOf(); + + for (int i = 0; i < index; i++) + { + offset += (int)PayloadSizes[i]; + } + + return offset; + } + + public Result Verify() + { + // Get the obfuscated metadata. + uint size = Size; + byte keyGeneration = KeyGeneration; + + // Check that size is big enough for the header. + if (size < Unsafe.SizeOf()) + return ResultLibHac.InvalidPackage2MetaSizeA.Log(); + + // Check that the size isn't larger than what we allow. + if (size > Package2Header.Package2SizeMax) + return ResultLibHac.InvalidPackage2MetaSizeB.Log(); + + // Check that the key generation is one that we can use. + if (keyGeneration >= 0x20) + return ResultLibHac.InvalidPackage2MetaKeyGeneration.Log(); + + // Check the magic number. + if (Magic != ExpectedMagicValue) + return ResultLibHac.InvalidPackage2MetaMagic.Log(); + + // Check the payload alignments. + if (EntryPoint % Package2Header.PayloadAlignment != 0) + return ResultLibHac.InvalidPackage2MetaEntryPointAlignment.Log(); + + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (PayloadSizes[i] % Package2Header.PayloadAlignment != 0) + return ResultLibHac.InvalidPackage2MetaPayloadSizeAlignment.Log(); + } + + // Check that the sizes sum to the total. + if (Size != Unsafe.SizeOf() + PayloadSizes[0] + PayloadSizes[1] + PayloadSizes[2]) + return ResultLibHac.InvalidPackage2MetaTotalSize.Log(); + + // Check that the payloads do not overflow. + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (PayloadOffsets[i] > PayloadOffsets[i] + PayloadSizes[i]) + return ResultLibHac.InvalidPackage2MetaPayloadSize.Log(); + } + + // Verify that no payloads overlap. + for (int i = 0; i < Package2Header.PayloadCount - 1; i++) + for (int j = i + 1; j < Package2Header.PayloadCount; j++) + { + if (Overlap.HasOverlap(PayloadOffsets[i], PayloadSizes[i], PayloadOffsets[j], PayloadSizes[j])) + return ResultLibHac.InvalidPackage2MetaPayloadsOverlap.Log(); + } + + // Check whether any payload contains the entrypoint. + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (Overlap.Contains(PayloadOffsets[i], PayloadSizes[i], EntryPoint)) + return Result.Success; + } + + // No payload contains the entrypoint, so we're not valid. + return ResultLibHac.InvalidPackage2MetaEntryPointNotFound.Log(); + } + +#if DEBUG + [DebuggerBrowsable(DebuggerBrowsableState.Never)] [FieldOffset(0x00)] private readonly Padding100 PaddingForVsDebugging; +#endif + } +} diff --git a/src/LibHac/Boot/Package2StorageReader.cs b/src/LibHac/Boot/Package2StorageReader.cs new file mode 100644 index 00000000..e3a9cce6 --- /dev/null +++ b/src/LibHac/Boot/Package2StorageReader.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Crypto; +using LibHac.Fs; +using LibHac.FsSystem; + +namespace LibHac.Boot +{ + /// + /// Parses a package2 file and opens the payloads within. + /// + public class Package2StorageReader + { + private IStorage _storage; + private Package2Header _header; + private Keyset _keyset; + private byte[] _key; + + public ref readonly Package2Header Header => ref _header; + + /// + /// Initializes the . + /// + /// The keyset to use for decrypting the package. + /// An of the encrypted package2. + /// The of the operation. + public Result Initialize(Keyset keyset, IStorage storage) + { + Result rc = storage.Read(0, SpanHelpers.AsByteSpan(ref _header)); + if (rc.IsFailure()) return rc; + + _key = keyset.Package2Keys[_header.Meta.KeyGeneration]; + DecryptHeader(_key, ref _header.Meta, ref _header.Meta); + + _storage = storage; + _keyset = keyset; + return Result.Success; + } + + /// + /// Opens a decrypted of one of the payloads in the package. + /// + /// If the method returns successfully, contains an + /// of the specified payload. + /// The index of the payload to get. Must me less than + /// The of the operation. + public Result OpenPayload(out IStorage payloadStorage, int index) + { + payloadStorage = default; + + if ((uint)index >= Package2Header.PayloadCount) + return ResultLibHac.ArgumentOutOfRange.Log(); + + int offset = _header.Meta.GetPayloadFileOffset(index); + int size = (int)_header.Meta.PayloadSizes[index]; + + var payloadSubStorage = new SubStorage(_storage, offset, size); + + if (size == 0) + { + payloadStorage = payloadSubStorage; + return Result.Success; + } + + byte[] iv = _header.Meta.PayloadIvs[index].Bytes.ToArray(); + payloadStorage = new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key, iv, true), 0x4000, 1, true); + return Result.Success; + } + + /// + /// Verifies the signature, metadata and payloads in the package. + /// + /// The of the operation. + public Result Verify() + { + Result rc = VerifySignature(); + if (rc.IsFailure()) return rc; + + rc = VerifyMeta(); + if (rc.IsFailure()) return rc; + + return VerifyPayloads(); + } + + /// + /// Verifies the signature of the package. + /// + /// The of the operation. + /// if the signature is valid. + public Result VerifySignature() + { + Unsafe.SkipInit(out Package2Meta meta); + Span metaBytes = SpanHelpers.AsByteSpan(ref meta); + + Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes); + if (rc.IsFailure()) return rc; + + return _header.VerifySignature(_keyset.Package2FixedKeyModulus, metaBytes); + } + + /// + /// Verifies the package metadata. + /// + /// The of the operation. + /// if the metadata is valid. + public Result VerifyMeta() => _header.Meta.Verify(); + + /// + /// Verifies the hashes of all the payloads in the metadata. + /// + /// The of the operation. + /// if all the hashes are valid. + public Result VerifyPayloads() + { + using (var buffer = new RentedArray(0x10000)) + { + byte[] array = buffer.Array; + var hashBuffer = new Buffer32(); + var sha = new Sha256Generator(); + + // Verify hashes match for all payloads. + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (_header.Meta.PayloadSizes[i] == 0) + continue; + + int offset = _header.Meta.GetPayloadFileOffset(i); + int size = (int)_header.Meta.PayloadSizes[i]; + + var payloadSubStorage = new SubStorage(_storage, offset, size); + + offset = 0; + sha.Initialize(); + + while (size > 0) + { + int toRead = Math.Min(array.Length, size); + Span span = array.AsSpan(0, toRead); + + Result rc = payloadSubStorage.Read(offset, span); + if (rc.IsFailure()) return rc; + + sha.Update(span); + + offset += toRead; + size -= toRead; + } + + sha.GetHash(hashBuffer); + + if (!CryptoUtil.IsSameBytes(hashBuffer, _header.Meta.PayloadHashes[i], 0x20)) + { + return ResultLibHac.InvalidPackage2PayloadCorrupted.Log(); + } + } + } + + return Result.Success; + } + + /// + /// Opens a decrypted of the entire package. + /// + /// If the method returns successfully, contains a decrypted + /// of the package. + /// The of the operation. + public Result OpenDecryptedPackage(out IStorage packageStorage) + { + var storages = new List(4); + + // The signature and IV are unencrypted + int unencryptedHeaderSize = Package2Header.SignatureSize + Unsafe.SizeOf(); + int encryptedHeaderSize = Unsafe.SizeOf() - unencryptedHeaderSize; + + // Get signature and IV + storages.Add(new SubStorage(_storage, 0, unencryptedHeaderSize)); + + // Open decrypted meta + var encMetaStorage = new SubStorage(_storage, unencryptedHeaderSize, encryptedHeaderSize); + + // The counter starts counting at the beginning of the meta struct, but the first block in + // the struct isn't encrypted. Increase the counter by one to skip that block. + byte[] iv = _header.Meta.HeaderIv.Bytes.ToArray(); + Utilities.IncrementByteArray(iv); + + storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key, iv, true), 0x100, 1, true)); + + // Open all the payloads + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (_header.Meta.PayloadSizes[i] == 0) + continue; + + Result rc = OpenPayload(out IStorage payloadStorage, i); + if (rc.IsFailure()) + { + packageStorage = default; + return rc; + } + + storages.Add(payloadStorage); + } + + packageStorage = new ConcatenationStorage(storages, true); + return Result.Success; + } + + private void DecryptHeader(byte[] key, ref Package2Meta source, ref Package2Meta dest) + { + Buffer16 iv = source.HeaderIv; + + Aes.DecryptCtr128(SpanHelpers.AsByteSpan(ref source), SpanHelpers.AsByteSpan(ref dest), key, iv); + + // Copy the IV to the output because the IV field will be garbage after "decrypting" it + Unsafe.As(ref dest) = iv; + } + } +} diff --git a/src/LibHac/Common/ResultLibHac.cs b/src/LibHac/Common/ResultLibHac.cs index 4f0f1e82..c6c8b7e8 100644 --- a/src/LibHac/Common/ResultLibHac.cs +++ b/src/LibHac/Common/ResultLibHac.cs @@ -32,14 +32,54 @@ namespace LibHac.Common /// Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); } /// Error code: 2428-1001; Range: 1001-1019; Inner value: 0x7d3ac - public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1001, 1019); } - /// The size of the KIP file was smaller than expected.
Error code: 2428-1002; Inner value: 0x7d5ac
- public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1002); - /// The magic value of the KIP file was not KIP1.
Error code: 2428-1003; Inner value: 0x7d7ac
- public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1003); - /// The size of the compressed KIP segment was smaller than expected.
Error code: 2428-1004; Inner value: 0x7d9ac
- public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1004); - /// An error occurred while decompressing a KIP segment.
Error code: 2428-1005; Inner value: 0x7dbac
- public static Result.Base KipSegmentDecompressionFailed => new Result.Base(ModuleLibHac, 1005); + public static Result.Base InvalidInitialProcessData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1001, 1019); } + /// Error code: 2428-1002; Range: 1002-1009; Inner value: 0x7d5ac + public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1002, 1009); } + /// The size of the KIP file was smaller than expected.
Error code: 2428-1003; Inner value: 0x7d7ac
+ public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1003); + /// The magic value of the KIP file was not KIP1.
Error code: 2428-1004; Inner value: 0x7d9ac
+ public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1004); + /// The size of the compressed KIP segment was smaller than expected.
Error code: 2428-1005; Inner value: 0x7dbac
+ public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1005); + /// An error occurred while decompressing a KIP segment.
Error code: 2428-1006; Inner value: 0x7ddac
+ public static Result.Base KipSegmentDecompressionFailed => new Result.Base(ModuleLibHac, 1006); + + /// Error code: 2428-1010; Range: 1010-1019; Inner value: 0x7e5ac + public static Result.Base InvalidIni { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1010, 1019); } + /// The size of the INI file was smaller than expected.
Error code: 2428-1011; Inner value: 0x7e7ac
+ public static Result.Base InvalidIniFileSize => new Result.Base(ModuleLibHac, 1011); + /// The magic value of the INI file was not INI1.
Error code: 2428-1012; Inner value: 0x7e9ac
+ public static Result.Base InvalidIniMagic => new Result.Base(ModuleLibHac, 1012); + /// The INI had an invalid process count.
Error code: 2428-1013; Inner value: 0x7ebac
+ public static Result.Base InvalidIniProcessCount => new Result.Base(ModuleLibHac, 1013); + + /// Error code: 2428-1020; Range: 1020-1039; Inner value: 0x7f9ac + public static Result.Base InvalidPackage2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1020, 1039); } + /// Error code: 2428-1021; Inner value: 0x7fbac + public static Result.Base InvalidPackage2HeaderSignature => new Result.Base(ModuleLibHac, 1021); + /// Error code: 2428-1022; Inner value: 0x7fdac + public static Result.Base InvalidPackage2MetaSizeA => new Result.Base(ModuleLibHac, 1022); + /// Error code: 2428-1023; Inner value: 0x7ffac + public static Result.Base InvalidPackage2MetaSizeB => new Result.Base(ModuleLibHac, 1023); + /// Error code: 2428-1024; Inner value: 0x801ac + public static Result.Base InvalidPackage2MetaKeyGeneration => new Result.Base(ModuleLibHac, 1024); + /// Error code: 2428-1025; Inner value: 0x803ac + public static Result.Base InvalidPackage2MetaMagic => new Result.Base(ModuleLibHac, 1025); + /// Error code: 2428-1026; Inner value: 0x805ac + public static Result.Base InvalidPackage2MetaEntryPointAlignment => new Result.Base(ModuleLibHac, 1026); + /// Error code: 2428-1027; Inner value: 0x807ac + public static Result.Base InvalidPackage2MetaPayloadAlignment => new Result.Base(ModuleLibHac, 1027); + /// Error code: 2428-1028; Inner value: 0x809ac + public static Result.Base InvalidPackage2MetaPayloadSizeAlignment => new Result.Base(ModuleLibHac, 1028); + /// Error code: 2428-1029; Inner value: 0x80bac + public static Result.Base InvalidPackage2MetaTotalSize => new Result.Base(ModuleLibHac, 1029); + /// Error code: 2428-1030; Inner value: 0x80dac + public static Result.Base InvalidPackage2MetaPayloadSize => new Result.Base(ModuleLibHac, 1030); + /// Error code: 2428-1031; Inner value: 0x80fac + public static Result.Base InvalidPackage2MetaPayloadsOverlap => new Result.Base(ModuleLibHac, 1031); + /// Error code: 2428-1032; Inner value: 0x811ac + public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032); + /// Error code: 2428-1033; Inner value: 0x813ac + public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033); } } diff --git a/src/LibHac/Crypto/Rsa.cs b/src/LibHac/Crypto/Rsa.cs new file mode 100644 index 00000000..7370998f --- /dev/null +++ b/src/LibHac/Crypto/Rsa.cs @@ -0,0 +1,44 @@ +using System; +using System.Security.Cryptography; + +namespace LibHac.Crypto +{ + public static class Rsa + { + public static bool VerifyRsa2048PssSha256(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message) + { + try + { + var param = new RSAParameters { Modulus = modulus.ToArray(), Exponent = exponent.ToArray() }; + + using (var rsa = RSA.Create(param)) + { + return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } + } + catch (CryptographicException) + { + return false; + } + } + + public static bool VerifyRsa2048PssSha256WithHash(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message) + { + try + { + var param = new RSAParameters { Modulus = modulus.ToArray(), Exponent = exponent.ToArray() }; + + using (var rsa = RSA.Create(param)) + { + return rsa.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } + } + catch (CryptographicException) + { + return false; + } + } + } +} diff --git a/src/LibHac/Kernel/InitialProcessBinaryReader.cs b/src/LibHac/Kernel/InitialProcessBinaryReader.cs new file mode 100644 index 00000000..15b729a6 --- /dev/null +++ b/src/LibHac/Kernel/InitialProcessBinaryReader.cs @@ -0,0 +1,99 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; + +namespace LibHac.Kernel +{ + public class InitialProcessBinaryReader + { + private const uint ExpectedMagic = 0x31494E49; // INI1 + private const int MaxProcessCount = 80; + + private IStorage _storage; + private IniHeader _header; + private (int offset, int size)[] _offsets; + + public ref readonly IniHeader Header => ref _header; + public int ProcessCount => _header.ProcessCount; + + public Result Initialize(IStorage binaryStorage) + { + if (binaryStorage is null) + return ResultLibHac.NullArgument.Log(); + + // Verify there's enough data to read the header + Result rc = binaryStorage.GetSize(out long iniSize); + if (rc.IsFailure()) return rc; + + if (iniSize < Unsafe.SizeOf()) + return ResultLibHac.InvalidIniFileSize.Log(); + + // Read the INI file header and validate some of its values. + rc = binaryStorage.Read(0, SpanHelpers.AsByteSpan(ref _header)); + if (rc.IsFailure()) return rc; + + if (_header.Magic != ExpectedMagic) + return ResultLibHac.InvalidIniMagic.Log(); + + if ((uint)_header.ProcessCount > MaxProcessCount) + return ResultLibHac.InvalidIniProcessCount.Log(); + + // There's no metadata with the offsets of each KIP; they're all stored sequentially in the file. + // Read the size of each KIP to get their offsets. + rc = GetKipOffsets(out _offsets, binaryStorage, _header.ProcessCount); + if (rc.IsFailure()) return rc; + + _storage = binaryStorage; + return Result.Success; + } + + public Result OpenKipStorage(out IStorage storage, int index) + { + storage = default; + + if ((uint)index >= _header.ProcessCount) + return ResultLibHac.ArgumentOutOfRange.Log(); + + (int offset, int size) range = _offsets[index]; + storage = new SubStorage(_storage, range.offset, range.size); + return Result.Success; + } + + private static Result GetKipOffsets(out (int offset, int size)[] kipOffsets, IStorage iniStorage, + int processCount) + { + Assert.AssertTrue(processCount <= MaxProcessCount); + + kipOffsets = default; + + var offsets = new (int offset, int size)[processCount]; + int offset = Unsafe.SizeOf(); + var kipReader = new KipReader(); + + for (int i = 0; i < processCount; i++) + { + Result rc = kipReader.Initialize(new SubStorage(iniStorage, offset, int.MaxValue)); + if (rc.IsFailure()) return rc; + + int kipSize = kipReader.GetFileSize(); + offsets[i] = (offset, kipSize); + offset += kipSize; + } + + kipOffsets = offsets; + return Result.Success; + } + + + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct IniHeader + { + public uint Magic; + public int Size; + public int ProcessCount; + public uint Reserved; + } + } +} diff --git a/src/LibHac/Loader/KipHeader.cs b/src/LibHac/Kernel/KipHeader.cs similarity index 99% rename from src/LibHac/Loader/KipHeader.cs rename to src/LibHac/Kernel/KipHeader.cs index 580b6471..f1f376af 100644 --- a/src/LibHac/Loader/KipHeader.cs +++ b/src/LibHac/Kernel/KipHeader.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Loader +namespace LibHac.Kernel { [StructLayout(LayoutKind.Explicit, Size = 0x100)] public struct KipHeader diff --git a/src/LibHac/Loader/KipReader.cs b/src/LibHac/Kernel/KipReader.cs similarity index 99% rename from src/LibHac/Loader/KipReader.cs rename to src/LibHac/Kernel/KipReader.cs index 59ea7144..bf1ad5a5 100644 --- a/src/LibHac/Loader/KipReader.cs +++ b/src/LibHac/Kernel/KipReader.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; -namespace LibHac.Loader +namespace LibHac.Kernel { public class KipReader { diff --git a/src/LibHac/Kip.cs b/src/LibHac/Kip.cs index d5396ea7..5e42c8e7 100644 --- a/src/LibHac/Kip.cs +++ b/src/LibHac/Kip.cs @@ -2,7 +2,7 @@ using System.IO; using LibHac.Fs; using LibHac.FsSystem; -using LibHac.Loader; +using LibHac.Kernel; namespace LibHac { diff --git a/src/LibHac/Nso.cs b/src/LibHac/Nso.cs index 59dfa245..206f0247 100644 --- a/src/LibHac/Nso.cs +++ b/src/LibHac/Nso.cs @@ -1,10 +1,12 @@ -using System.Collections; +using System; +using System.Collections; using System.IO; using LibHac.Fs; using LibHac.FsSystem; namespace LibHac { + [Obsolete("This class has been deprecated. LibHac.Loader.NsoReader should be used instead.")] public class Nso { public NsoSection[] Sections { get; } diff --git a/src/LibHac/Package2.cs b/src/LibHac/Package2.cs index c3995935..e94149c6 100644 --- a/src/LibHac/Package2.cs +++ b/src/LibHac/Package2.cs @@ -5,6 +5,7 @@ using LibHac.FsSystem; namespace LibHac { + [Obsolete("This class has been deprecated. LibHac.Boot.Package2StorageReader should be used instead.")] public class Package2 { private const uint Pk21Magic = 0x31324B50; // PK21 diff --git a/src/LibHac/Util/Overlap.cs b/src/LibHac/Util/Overlap.cs new file mode 100644 index 00000000..1b05091d --- /dev/null +++ b/src/LibHac/Util/Overlap.cs @@ -0,0 +1,20 @@ +namespace LibHac.Util +{ + public static class Overlap + { + public static bool HasOverlap(ulong start0, ulong size0, ulong start1, ulong size1) + { + if (start0 <= start1) + { + return start1 < start0 + size0; + } + + return start0 < start1 + size1; + } + + public static bool Contains(ulong start, ulong size, ulong value) + { + return start <= value && value < start + size; + } + } +} diff --git a/src/hactoolnet/ProcessKip.cs b/src/hactoolnet/ProcessKip.cs index 17b99902..f7103a52 100644 --- a/src/hactoolnet/ProcessKip.cs +++ b/src/hactoolnet/ProcessKip.cs @@ -1,7 +1,7 @@ using System.IO; -using LibHac; +using LibHac.Fs; using LibHac.FsSystem; -using LibHac.Loader; +using LibHac.Kernel; namespace hactoolnet { @@ -19,7 +19,7 @@ namespace hactoolnet var uncompressed = new byte[kip.GetUncompressedSize()]; kip.ReadUncompressedKip(uncompressed).ThrowIfFailure(); - + File.WriteAllBytes(ctx.Options.UncompressedOut, uncompressed); } } @@ -29,21 +29,24 @@ namespace hactoolnet { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - var ini1 = new Ini1(file); + + var ini1 = new InitialProcessBinaryReader(); + ini1.Initialize(file).ThrowIfFailure(); string outDir = ctx.Options.OutDir; if (outDir != null) { Directory.CreateDirectory(outDir); + var kipReader = new KipReader(); - foreach (KipReader kip in ini1.Kips) + for (int i = 0; i < ini1.ProcessCount; i++) { - var uncompressed = new byte[kip.GetUncompressedSize()]; + ini1.OpenKipStorage(out IStorage kipStorage, i).ThrowIfFailure(); - kip.ReadUncompressedKip(uncompressed).ThrowIfFailure(); + kipReader.Initialize(kipStorage).ThrowIfFailure(); - File.WriteAllBytes(Path.Combine(outDir, $"{kip.Name.ToString()}.kip1"), uncompressed); + kipStorage.WriteAllBytes(Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1")); } } } diff --git a/src/hactoolnet/ProcessPackage.cs b/src/hactoolnet/ProcessPackage.cs index 7d2c043d..ce781ea8 100644 --- a/src/hactoolnet/ProcessPackage.cs +++ b/src/hactoolnet/ProcessPackage.cs @@ -1,6 +1,8 @@ using System.IO; using System.Text; using LibHac; +using LibHac.Boot; +using LibHac.Fs; using LibHac.FsSystem; using static hactoolnet.Print; @@ -31,7 +33,8 @@ namespace hactoolnet { using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false)) { - var package2 = new Package2(ctx.Keyset, file); + var package2 = new Package2StorageReader(); + package2.Initialize(ctx.Keyset, file).ThrowIfFailure(); ctx.Logger.LogMessage(package2.Print()); @@ -41,33 +44,43 @@ namespace hactoolnet { Directory.CreateDirectory(outDir); - package2.OpenKernel().WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger); - package2.OpenIni1().WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger); - package2.OpenDecryptedPackage().WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); + package2.OpenPayload(out IStorage kernelStorage, 0).ThrowIfFailure(); + kernelStorage.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger); + + package2.OpenPayload(out IStorage ini1Storage, 1).ThrowIfFailure(); + ini1Storage.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger); + + package2.OpenDecryptedPackage(out IStorage decPackageStorage).ThrowIfFailure(); + decPackageStorage.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); } } } private static readonly string[] Package2SectionNames = { "Kernel", "INI1", "Empty" }; - private static string Print(this Package2 package2) + private static string Print(this Package2StorageReader package2) { + Result rc = package2.VerifySignature(); + + Validity signatureValidity = rc.IsSuccess() ? Validity.Valid : Validity.Invalid; + int colLen = 36; var sb = new StringBuilder(); sb.AppendLine(); sb.AppendLine("PK21:"); - PrintItem(sb, colLen, $"Signature{package2.Header.SignatureValidity.GetValidityString()}:", package2.Header.Signature); - PrintItem(sb, colLen, "Header Version:", $"{package2.HeaderVersion:x2}"); + PrintItem(sb, colLen, $"Signature{signatureValidity.GetValidityString()}:", package2.Header.Signature.ToArray()); + PrintItem(sb, colLen, "Header Version:", $"{package2.Header.Meta.KeyGeneration:x2}"); for (int i = 0; i < 3; i++) { - sb.AppendLine($"Section {i} ({Package2SectionNames[i]}):"); + string name = package2.Header.Meta.PayloadSizes[i] != 0 ? Package2SectionNames[i] : "Empty"; + sb.AppendLine($"Section {i} ({name}):"); - PrintItem(sb, colLen, " Hash:", package2.Header.SectionHashes[i]); - PrintItem(sb, colLen, " CTR:", package2.Header.SectionCounters[i]); - PrintItem(sb, colLen, " Load Address:", $"{package2.Header.SectionOffsets[i] + 0x80000000:x8}"); - PrintItem(sb, colLen, " Size:", $"{package2.Header.SectionSizes[i]:x8}"); + PrintItem(sb, colLen, " Hash:", package2.Header.Meta.PayloadHashes[i]); + PrintItem(sb, colLen, " CTR:", package2.Header.Meta.PayloadIvs[i]); + PrintItem(sb, colLen, " Load Address:", $"{package2.Header.Meta.PayloadOffsets[i] + 0x80000000:x8}"); + PrintItem(sb, colLen, " Size:", $"{package2.Header.Meta.PayloadSizes[i]:x8}"); } return sb.ToString();