Add Package2StorageReader and InitialProcessBinaryReader

This commit is contained in:
Alex Barney 2020-08-10 17:17:57 -07:00
parent 81340027fc
commit 8491ec2117
14 changed files with 665 additions and 38 deletions

View File

@ -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
View 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
}
}

View 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;
}
}
}

View File

@ -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
View 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;
}
}
}
}

View 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;
}
}
}

View File

@ -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

View File

@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.Loader
namespace LibHac.Kernel
{
public class KipReader
{

View File

@ -2,7 +2,7 @@
using System.IO;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Loader;
using LibHac.Kernel;
namespace LibHac
{

View File

@ -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; }

View File

@ -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

View 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;
}
}
}

View File

@ -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"));
}
}
}

View File

@ -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();