mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add Package2StorageReader and InitialProcessBinaryReader
This commit is contained in:
parent
81340027fc
commit
8491ec2117
@ -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,
|
||||
|
Can't render this file because it has a wrong number of fields in line 153.
|
164
src/LibHac/Boot/Package2.cs
Normal file
164
src/LibHac/Boot/Package2.cs
Normal file
@ -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<byte> RsaPublicKeyExponent => new byte[] { 0x00, 0x01, 0x00, 0x01 };
|
||||
|
||||
[FieldOffset(0x00)] private byte _signature;
|
||||
[FieldOffset(0x100)] public Package2Meta Meta;
|
||||
|
||||
public ReadOnlySpan<byte> Signature => SpanHelpers.CreateSpan(ref _signature, SignatureSize);
|
||||
|
||||
public Result VerifySignature(ReadOnlySpan<byte> modulus, ReadOnlySpan<byte> 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<Buffer16> PayloadIvs => SpanHelpers.CreateSpan(ref _payloadIvs, Package2Header.PayloadCount);
|
||||
public ReadOnlySpan<uint> PayloadSizes => SpanHelpers.CreateSpan(ref _payloadSizes, Package2Header.PayloadCount);
|
||||
public ReadOnlySpan<uint> PayloadOffsets => SpanHelpers.CreateSpan(ref _payloadOffsets, Package2Header.PayloadCount);
|
||||
public ReadOnlySpan<Buffer32> 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<Package2Header>();
|
||||
|
||||
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<Package2Header>())
|
||||
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<Package2Header>() + 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
|
||||
}
|
||||
}
|
220
src/LibHac/Boot/Package2StorageReader.cs
Normal file
220
src/LibHac/Boot/Package2StorageReader.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a package2 file and opens the payloads within.
|
||||
/// </summary>
|
||||
public class Package2StorageReader
|
||||
{
|
||||
private IStorage _storage;
|
||||
private Package2Header _header;
|
||||
private Keyset _keyset;
|
||||
private byte[] _key;
|
||||
|
||||
public ref readonly Package2Header Header => ref _header;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="Package2StorageReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="keyset">The keyset to use for decrypting the package.</param>
|
||||
/// <param name="storage">An <see cref="IStorage"/> of the encrypted package2.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a decrypted <see cref="IStorage"/> of one of the payloads in the package.
|
||||
/// </summary>
|
||||
/// <param name="payloadStorage">If the method returns successfully, contains an <see cref="IStorage"/>
|
||||
/// of the specified payload.</param>
|
||||
/// <param name="index">The index of the payload to get. Must me less than <see cref="Package2Header.PayloadCount"/></param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the signature, metadata and payloads in the package.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public Result Verify()
|
||||
{
|
||||
Result rc = VerifySignature();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = VerifyMeta();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return VerifyPayloads();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the signature of the package.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Result"/> of the operation.
|
||||
/// <see cref="Result.Success"/> if the signature is valid.</returns>
|
||||
public Result VerifySignature()
|
||||
{
|
||||
Unsafe.SkipInit(out Package2Meta meta);
|
||||
Span<byte> metaBytes = SpanHelpers.AsByteSpan(ref meta);
|
||||
|
||||
Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return _header.VerifySignature(_keyset.Package2FixedKeyModulus, metaBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the package metadata.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Result"/> of the operation.
|
||||
/// <see cref="Result.Success"/> if the metadata is valid.</returns>
|
||||
public Result VerifyMeta() => _header.Meta.Verify();
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the hashes of all the payloads in the metadata.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Result"/> of the operation.
|
||||
/// <see cref="Result.Success"/> if all the hashes are valid.</returns>
|
||||
public Result VerifyPayloads()
|
||||
{
|
||||
using (var buffer = new RentedArray<byte>(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<byte> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a decrypted <see cref="IStorage"/> of the entire package.
|
||||
/// </summary>
|
||||
/// <param name="packageStorage">If the method returns successfully, contains a decrypted
|
||||
/// <see cref="IStorage"/> of the package.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public Result OpenDecryptedPackage(out IStorage packageStorage)
|
||||
{
|
||||
var storages = new List<IStorage>(4);
|
||||
|
||||
// The signature and IV are unencrypted
|
||||
int unencryptedHeaderSize = Package2Header.SignatureSize + Unsafe.SizeOf<Buffer16>();
|
||||
int encryptedHeaderSize = Unsafe.SizeOf<Package2Header>() - 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<Package2Meta, Buffer16>(ref dest) = iv;
|
||||
}
|
||||
}
|
||||
}
|
@ -32,14 +32,54 @@ namespace LibHac.Common
|
||||
/// <summary>Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac</summary>
|
||||
public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); }
|
||||
/// <summary>Error code: 2428-1001; Range: 1001-1019; Inner value: 0x7d3ac</summary>
|
||||
public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1001, 1019); }
|
||||
/// <summary>The size of the KIP file was smaller than expected.<br/>Error code: 2428-1002; Inner value: 0x7d5ac</summary>
|
||||
public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1002);
|
||||
/// <summary>The magic value of the KIP file was not KIP1.<br/>Error code: 2428-1003; Inner value: 0x7d7ac</summary>
|
||||
public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1003);
|
||||
/// <summary>The size of the compressed KIP segment was smaller than expected.<br/>Error code: 2428-1004; Inner value: 0x7d9ac</summary>
|
||||
public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1004);
|
||||
/// <summary>An error occurred while decompressing a KIP segment.<br/>Error code: 2428-1005; Inner value: 0x7dbac</summary>
|
||||
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); }
|
||||
/// <summary>Error code: 2428-1002; Range: 1002-1009; Inner value: 0x7d5ac</summary>
|
||||
public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1002, 1009); }
|
||||
/// <summary>The size of the KIP file was smaller than expected.<br/>Error code: 2428-1003; Inner value: 0x7d7ac</summary>
|
||||
public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1003);
|
||||
/// <summary>The magic value of the KIP file was not KIP1.<br/>Error code: 2428-1004; Inner value: 0x7d9ac</summary>
|
||||
public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1004);
|
||||
/// <summary>The size of the compressed KIP segment was smaller than expected.<br/>Error code: 2428-1005; Inner value: 0x7dbac</summary>
|
||||
public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1005);
|
||||
/// <summary>An error occurred while decompressing a KIP segment.<br/>Error code: 2428-1006; Inner value: 0x7ddac</summary>
|
||||
public static Result.Base KipSegmentDecompressionFailed => new Result.Base(ModuleLibHac, 1006);
|
||||
|
||||
/// <summary>Error code: 2428-1010; Range: 1010-1019; Inner value: 0x7e5ac</summary>
|
||||
public static Result.Base InvalidIni { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1010, 1019); }
|
||||
/// <summary>The size of the INI file was smaller than expected.<br/>Error code: 2428-1011; Inner value: 0x7e7ac</summary>
|
||||
public static Result.Base InvalidIniFileSize => new Result.Base(ModuleLibHac, 1011);
|
||||
/// <summary>The magic value of the INI file was not INI1.<br/>Error code: 2428-1012; Inner value: 0x7e9ac</summary>
|
||||
public static Result.Base InvalidIniMagic => new Result.Base(ModuleLibHac, 1012);
|
||||
/// <summary>The INI had an invalid process count.<br/>Error code: 2428-1013; Inner value: 0x7ebac</summary>
|
||||
public static Result.Base InvalidIniProcessCount => new Result.Base(ModuleLibHac, 1013);
|
||||
|
||||
/// <summary>Error code: 2428-1020; Range: 1020-1039; Inner value: 0x7f9ac</summary>
|
||||
public static Result.Base InvalidPackage2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1020, 1039); }
|
||||
/// <summary>Error code: 2428-1021; Inner value: 0x7fbac</summary>
|
||||
public static Result.Base InvalidPackage2HeaderSignature => new Result.Base(ModuleLibHac, 1021);
|
||||
/// <summary>Error code: 2428-1022; Inner value: 0x7fdac</summary>
|
||||
public static Result.Base InvalidPackage2MetaSizeA => new Result.Base(ModuleLibHac, 1022);
|
||||
/// <summary>Error code: 2428-1023; Inner value: 0x7ffac</summary>
|
||||
public static Result.Base InvalidPackage2MetaSizeB => new Result.Base(ModuleLibHac, 1023);
|
||||
/// <summary>Error code: 2428-1024; Inner value: 0x801ac</summary>
|
||||
public static Result.Base InvalidPackage2MetaKeyGeneration => new Result.Base(ModuleLibHac, 1024);
|
||||
/// <summary>Error code: 2428-1025; Inner value: 0x803ac</summary>
|
||||
public static Result.Base InvalidPackage2MetaMagic => new Result.Base(ModuleLibHac, 1025);
|
||||
/// <summary>Error code: 2428-1026; Inner value: 0x805ac</summary>
|
||||
public static Result.Base InvalidPackage2MetaEntryPointAlignment => new Result.Base(ModuleLibHac, 1026);
|
||||
/// <summary>Error code: 2428-1027; Inner value: 0x807ac</summary>
|
||||
public static Result.Base InvalidPackage2MetaPayloadAlignment => new Result.Base(ModuleLibHac, 1027);
|
||||
/// <summary>Error code: 2428-1028; Inner value: 0x809ac</summary>
|
||||
public static Result.Base InvalidPackage2MetaPayloadSizeAlignment => new Result.Base(ModuleLibHac, 1028);
|
||||
/// <summary>Error code: 2428-1029; Inner value: 0x80bac</summary>
|
||||
public static Result.Base InvalidPackage2MetaTotalSize => new Result.Base(ModuleLibHac, 1029);
|
||||
/// <summary>Error code: 2428-1030; Inner value: 0x80dac</summary>
|
||||
public static Result.Base InvalidPackage2MetaPayloadSize => new Result.Base(ModuleLibHac, 1030);
|
||||
/// <summary>Error code: 2428-1031; Inner value: 0x80fac</summary>
|
||||
public static Result.Base InvalidPackage2MetaPayloadsOverlap => new Result.Base(ModuleLibHac, 1031);
|
||||
/// <summary>Error code: 2428-1032; Inner value: 0x811ac</summary>
|
||||
public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032);
|
||||
/// <summary>Error code: 2428-1033; Inner value: 0x813ac</summary>
|
||||
public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033);
|
||||
}
|
||||
}
|
||||
|
44
src/LibHac/Crypto/Rsa.cs
Normal file
44
src/LibHac/Crypto/Rsa.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace LibHac.Crypto
|
||||
{
|
||||
public static class Rsa
|
||||
{
|
||||
public static bool VerifyRsa2048PssSha256(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> modulus,
|
||||
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> 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<byte> signature, ReadOnlySpan<byte> modulus,
|
||||
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
src/LibHac/Kernel/InitialProcessBinaryReader.cs
Normal file
99
src/LibHac/Kernel/InitialProcessBinaryReader.cs
Normal file
@ -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<IniHeader>())
|
||||
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<IniHeader>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.Loader
|
||||
namespace LibHac.Kernel
|
||||
{
|
||||
public class KipReader
|
||||
{
|
@ -2,7 +2,7 @@
|
||||
using System.IO;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Kernel;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
|
20
src/LibHac/Util/Overlap.cs
Normal file
20
src/LibHac/Util/Overlap.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user