Update package1 and package2 readers to use SharedRef<T>

This commit is contained in:
Alex Barney 2021-11-03 15:40:43 -07:00
parent 2370f76c62
commit 0a8fb8a5c0
6 changed files with 163 additions and 144 deletions

View file

@ -13,33 +13,38 @@ namespace LibHac.Boot
/// <summary>
/// Parses a package2 file and opens the payloads within.
/// </summary>
public class Package2StorageReader
public class Package2StorageReader : IDisposable
{
private const int KernelPayloadIndex = 0;
private const int IniPayloadIndex = 1;
private IStorage _storage;
private SharedRef<IStorage> _storage;
private Package2Header _header;
private KeySet _keySet;
private Crypto.AesKey _key;
public ref readonly Package2Header Header => ref _header;
public void Dispose()
{
_storage.Destroy();
}
/// <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)
public Result Initialize(KeySet keySet, in SharedRef<IStorage> storage)
{
Result rc = storage.Read(0, SpanHelpers.AsByteSpan(ref _header));
Result rc = storage.Get.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;
_storage.SetByCopy(in storage);
_keySet = keySet;
return Result.Success;
}
@ -47,14 +52,12 @@ namespace LibHac.Boot
/// <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"/>
/// <param name="outPayloadStorage">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)
public Result OpenPayload(ref UniqueRef<IStorage> outPayloadStorage, int index)
{
UnsafeHelpers.SkipParamInit(out payloadStorage);
if ((uint)index >= Package2Header.PayloadCount)
return ResultLibHac.ArgumentOutOfRange.Log();
@ -65,53 +68,52 @@ namespace LibHac.Boot
if (size == 0)
{
payloadStorage = payloadSubStorage;
outPayloadStorage.Reset(payloadSubStorage);
return Result.Success;
}
byte[] iv = _header.Meta.PayloadIvs[index].Bytes.ToArray();
payloadStorage = new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key.DataRo.ToArray(), iv, true), 0x4000, 1, true);
outPayloadStorage.Reset(new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key.DataRo.ToArray(), iv, true), 0x4000, 1, true));
return Result.Success;
}
/// <summary>
/// Opens an <see cref="IStorage"/> of the kernel payload.
/// </summary>
/// <param name="kernelStorage">If the method returns successfully, contains an <see cref="IStorage"/>
/// <param name="outKernelStorage">If the method returns successfully, contains an <see cref="IStorage"/>
/// of the kernel payload.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result OpenKernel(out IStorage kernelStorage)
public Result OpenKernel(ref UniqueRef<IStorage> outKernelStorage)
{
return OpenPayload(out kernelStorage, KernelPayloadIndex);
return OpenPayload(ref outKernelStorage, KernelPayloadIndex);
}
/// <summary>
/// Opens an <see cref="IStorage"/> of the initial process binary. If the binary is embedded in
/// the kernel, this method will attempt to locate and return the embedded binary.
/// </summary>
/// <param name="iniStorage">If the method returns successfully, contains an <see cref="IStorage"/>
/// <param name="outIniStorage">If the method returns successfully, contains an <see cref="IStorage"/>
/// of the initial process binary.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result OpenIni(out IStorage iniStorage)
public Result OpenIni(ref UniqueRef<IStorage> outIniStorage)
{
if (HasIniPayload())
{
return OpenPayload(out iniStorage, IniPayloadIndex);
return OpenPayload(ref outIniStorage, IniPayloadIndex);
}
// Ini is embedded in the kernel
UnsafeHelpers.SkipParamInit(out iniStorage);
Result rc = OpenKernel(out IStorage kernelStorage);
using var kernelStorage = new UniqueRef<IStorage>();
Result rc = OpenKernel(ref kernelStorage.Ref());
if (rc.IsFailure()) return rc;
if (!IniExtract.TryGetIni1Offset(out int offset, out int size, kernelStorage))
if (!IniExtract.TryGetIni1Offset(out int offset, out int size, kernelStorage.Get))
{
// Unable to find the ini. Could be a new, unsupported layout.
return ResultLibHac.NotImplemented.Log();
}
iniStorage = new SubStorage(kernelStorage, offset, size);
outIniStorage.Reset(new SubStorage(kernelStorage.Release(), offset, size));
return Result.Success;
}
@ -140,7 +142,7 @@ namespace LibHac.Boot
Unsafe.SkipInit(out Package2Meta meta);
Span<byte> metaBytes = SpanHelpers.AsByteSpan(ref meta);
Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes);
Result rc = _storage.Get.Read(Package2Header.SignatureSize, metaBytes);
if (rc.IsFailure()) return rc;
return _header.VerifySignature(_keySet.Package2SigningKeyParams.Modulus, metaBytes);
@ -209,10 +211,10 @@ namespace LibHac.Boot
/// <summary>
/// Opens a decrypted <see cref="IStorage"/> of the entire package.
/// </summary>
/// <param name="packageStorage">If the method returns successfully, contains a decrypted
/// <param name="outPackageStorage">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)
public Result OpenDecryptedPackage(ref UniqueRef<IStorage> outPackageStorage)
{
var storages = new List<IStorage>(4);
@ -239,17 +241,14 @@ namespace LibHac.Boot
if (_header.Meta.PayloadSizes[i] == 0)
continue;
Result rc = OpenPayload(out IStorage payloadStorage, i);
if (rc.IsFailure())
{
UnsafeHelpers.SkipParamInit(out packageStorage);
return rc;
}
using var payloadStorage = new UniqueRef<IStorage>();
Result rc = OpenPayload(ref payloadStorage.Ref(), i);
if (rc.IsFailure()) return rc.Miss();
storages.Add(payloadStorage);
storages.Add(payloadStorage.Release());
}
packageStorage = new ConcatenationStorage(storages, true);
outPackageStorage.Reset(new ConcatenationStorage(storages, true));
return Result.Success;
}

View file

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
@ -6,32 +7,37 @@ using LibHac.Fs;
namespace LibHac.Kernel
{
public class InitialProcessBinaryReader
public class InitialProcessBinaryReader : IDisposable
{
internal const uint ExpectedMagic = 0x31494E49; // INI1
private const int MaxProcessCount = 80;
private IStorage _storage;
private SharedRef<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)
public void Dispose()
{
if (binaryStorage is null)
_storage.Destroy();
}
public Result Initialize(in SharedRef<IStorage> binaryStorage)
{
if (!binaryStorage.HasValue)
return ResultLibHac.NullArgument.Log();
// Verify there's enough data to read the header
Result rc = binaryStorage.GetSize(out long iniSize);
Result rc = binaryStorage.Get.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));
rc = binaryStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header));
if (rc.IsFailure()) return rc;
if (_header.Magic != ExpectedMagic)
@ -45,36 +51,40 @@ namespace LibHac.Kernel
rc = GetKipOffsets(out _offsets, binaryStorage, _header.ProcessCount);
if (rc.IsFailure()) return rc;
_storage = binaryStorage;
_storage.SetByCopy(in binaryStorage);
return Result.Success;
}
public Result OpenKipStorage(out IStorage storage, int index)
public Result OpenKipStorage(ref UniqueRef<IStorage> outStorage, int index)
{
UnsafeHelpers.SkipParamInit(out storage);
if ((uint)index >= _header.ProcessCount)
return ResultLibHac.ArgumentOutOfRange.Log();
(int offset, int size) range = _offsets[index];
storage = new SubStorage(_storage, range.offset, range.size);
outStorage.Reset(new SubStorage(in _storage, range.offset, range.size));
return Result.Success;
}
private static Result GetKipOffsets(out (int offset, int size)[] kipOffsets, IStorage iniStorage,
private static Result GetKipOffsets(out (int offset, int size)[] kipOffsets, in SharedRef<IStorage> iniStorage,
int processCount)
{
Assert.SdkRequiresLessEqual(processCount, MaxProcessCount);
UnsafeHelpers.SkipParamInit(out kipOffsets);
Result rc = iniStorage.Get.GetSize(out long iniStorageSize);
if (rc.IsFailure()) return rc.Miss();
var offsets = new (int offset, int size)[processCount];
int offset = Unsafe.SizeOf<IniHeader>();
var kipReader = new KipReader();
using var kipReader = new KipReader();
for (int i = 0; i < processCount; i++)
{
Result rc = kipReader.Initialize(new SubStorage(iniStorage, offset, int.MaxValue));
using var kipStorage =
new SharedRef<IStorage>(new SubStorage(in iniStorage, offset, iniStorageSize - offset));
rc = kipReader.Initialize(in kipStorage);
if (rc.IsFailure()) return rc;
int kipSize = kipReader.GetFileSize();

View file

@ -8,9 +8,9 @@ using LibHac.Fs;
namespace LibHac.Kernel
{
public class KipReader
public class KipReader : IDisposable
{
private IStorage KipStorage { get; set; }
private SharedRef<IStorage> _kipStorage;
private KipHeader _header;
@ -34,48 +34,51 @@ namespace LibHac.Kernel
public int AffinityMask => _header.AffinityMask;
public int StackSize => _header.StackSize;
public Result Initialize(IStorage kipData)
public void Dispose()
{
if (kipData is null)
_kipStorage.Destroy();
}
public Result Initialize(in SharedRef<IStorage> kipData)
{
if (!kipData.HasValue)
return ResultLibHac.NullArgument.Log();
// Verify there's enough data to read the header
Result rc = kipData.GetSize(out long kipSize);
Result rc = kipData.Get.GetSize(out long kipSize);
if (rc.IsFailure()) return rc;
if (kipSize < Unsafe.SizeOf<KipHeader>())
return ResultLibHac.InvalidKipFileSize.Log();
rc = kipData.Read(0, SpanHelpers.AsByteSpan(ref _header));
rc = kipData.Get.Read(0, SpanHelpers.AsByteSpan(ref _header));
if (rc.IsFailure()) return rc;
if (!_header.IsValid)
return ResultLibHac.InvalidKipMagic.Log();
KipStorage = kipData;
_kipStorage.SetByCopy(in kipData);
return Result.Success;
}
/// <summary>
/// Gets the raw input KIP file.
/// </summary>
/// <param name="kipData">If the operation returns successfully, an <see cref="IStorage"/>
/// <param name="outKipData">If the operation returns successfully, an <see cref="IStorage"/>
/// containing the KIP data.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result GetRawData(out IStorage kipData)
public Result GetRawData(ref UniqueRef<IStorage> outKipData)
{
UnsafeHelpers.SkipParamInit(out kipData);
int kipFileSize = GetFileSize();
Result rc = KipStorage.GetSize(out long inputFileSize);
Result rc = _kipStorage.Get.GetSize(out long inputFileSize);
if (rc.IsFailure()) return rc;
// Verify the input KIP file isn't truncated
if (inputFileSize < kipFileSize)
return ResultLibHac.InvalidKipFileSize.Log();
kipData = new SubStorage(KipStorage, 0, kipFileSize);
outKipData.Reset(new SubStorage(in _kipStorage, 0, kipFileSize));
return Result.Success;
}
@ -149,14 +152,14 @@ namespace LibHac.Kernel
int offset = CalculateSegmentOffset((int)segment);
// Verify the segment offset is in-range
rc = KipStorage.GetSize(out long kipSize);
rc = _kipStorage.Get.GetSize(out long kipSize);
if (rc.IsFailure()) return rc;
if (kipSize < offset + segmentHeader.FileSize)
return ResultLibHac.InvalidKipFileSize.Log();
// Read the segment data.
rc = KipStorage.Read(offset, buffer.Slice(0, segmentHeader.FileSize));
rc = _kipStorage.Get.Read(offset, buffer.Slice(0, segmentHeader.FileSize));
if (rc.IsFailure()) return rc;
// Decompress if necessary.

View file

@ -1,5 +1,6 @@
using System;
using System.IO;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Kernel;
@ -200,8 +201,10 @@ namespace LibHac
for (int i = 0; i < KipCount; i++)
{
using var sharedStorage = new SharedRef<IStorage>(Storage.Slice(offset));
Kips[i] = new KipReader();
Kips[i].Initialize(Storage.Slice(offset)).ThrowIfFailure();
Kips[i].Initialize(in sharedStorage).ThrowIfFailure();
offset += Kips[i].GetFileSize();
}

View file

@ -1,4 +1,5 @@
using System.IO;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Kernel;
@ -9,50 +10,50 @@ namespace hactoolnet
{
public static void ProcessKip1(Context ctx)
{
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read))
using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read));
using var kip = new KipReader();
kip.Initialize(in file).ThrowIfFailure();
if (!string.IsNullOrWhiteSpace(ctx.Options.UncompressedOut))
{
var kip = new KipReader();
kip.Initialize(file).ThrowIfFailure();
byte[] uncompressed = new byte[kip.GetUncompressedSize()];
if (!string.IsNullOrWhiteSpace(ctx.Options.UncompressedOut))
{
byte[] uncompressed = new byte[kip.GetUncompressedSize()];
kip.ReadUncompressedKip(uncompressed).ThrowIfFailure();
kip.ReadUncompressedKip(uncompressed).ThrowIfFailure();
File.WriteAllBytes(ctx.Options.UncompressedOut, uncompressed);
}
File.WriteAllBytes(ctx.Options.UncompressedOut, uncompressed);
}
}
public static void ProcessIni1(Context ctx)
{
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read))
{
string outDir = ctx.Options.OutDir;
using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read));
if (outDir != null)
{
ExtractIni1(file, outDir);
}
string outDir = ctx.Options.OutDir;
if (outDir != null)
{
ExtractIni1(in file, outDir);
}
}
public static void ExtractIni1(IStorage iniStorage, string outDir)
public static void ExtractIni1(in SharedRef<IStorage> iniStorage, string outDir)
{
var ini1 = new InitialProcessBinaryReader();
using var ini1 = new InitialProcessBinaryReader();
ini1.Initialize(iniStorage).ThrowIfFailure();
Directory.CreateDirectory(outDir);
var kipReader = new KipReader();
using var kipReader = new KipReader();
for (int i = 0; i < ini1.ProcessCount; i++)
{
ini1.OpenKipStorage(out IStorage kipStorage, i).ThrowIfFailure();
using var kipStorage = new UniqueRef<IStorage>();
ini1.OpenKipStorage(ref kipStorage.Ref(), i).ThrowIfFailure();
kipReader.Initialize(kipStorage).ThrowIfFailure();
using SharedRef<IStorage> sharedKipStorage = SharedRef<IStorage>.Create(ref kipStorage.Ref());
kipReader.Initialize(in sharedKipStorage).ThrowIfFailure();
kipStorage.WriteAllBytes(System.IO.Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1"));
sharedKipStorage.Get.WriteAllBytes(System.IO.Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1"));
}
}
}

View file

@ -15,43 +15,42 @@ namespace hactoolnet
{
public static void ProcessPk11(Context ctx)
{
using (var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)))
using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read));
var package1 = new LibHac.Boot.Package1();
package1.Initialize(ctx.KeySet, in file).ThrowIfFailure();
ctx.Logger.LogMessage(package1.Print());
string outDir = ctx.Options.OutDir;
if (package1.IsDecrypted && outDir != null)
{
var package1 = new LibHac.Boot.Package1();
package1.Initialize(ctx.KeySet, in file).ThrowIfFailure();
Directory.CreateDirectory(outDir);
ctx.Logger.LogMessage(package1.Print());
IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage();
string outDir = ctx.Options.OutDir;
WriteFile(decryptedStorage, "Decrypted.bin");
WriteFile(package1.OpenWarmBootStorage(), "Warmboot.bin");
WriteFile(package1.OpenNxBootloaderStorage(), "NX_Bootloader.bin");
WriteFile(package1.OpenSecureMonitorStorage(), "Secure_Monitor.bin");
if (package1.IsDecrypted && outDir != null)
if (package1.IsMariko)
{
Directory.CreateDirectory(outDir);
WriteFile(package1.OpenDecryptedWarmBootStorage(), "Warmboot_Decrypted.bin");
IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage();
var marikoOemLoader = new SubStorage(decryptedStorage, Unsafe.SizeOf<Package1MarikoOemHeader>(),
package1.MarikoOemHeader.Size);
WriteFile(decryptedStorage, "Decrypted.bin");
WriteFile(package1.OpenWarmBootStorage(), "Warmboot.bin");
WriteFile(package1.OpenNxBootloaderStorage(), "NX_Bootloader.bin");
WriteFile(package1.OpenSecureMonitorStorage(), "Secure_Monitor.bin");
if (package1.IsMariko)
{
WriteFile(package1.OpenDecryptedWarmBootStorage(), "Warmboot_Decrypted.bin");
var marikoOemLoader = new SubStorage(decryptedStorage, Unsafe.SizeOf<Package1MarikoOemHeader>(),
package1.MarikoOemHeader.Size);
WriteFile(marikoOemLoader, "Mariko_OEM_Bootloader.bin");
}
WriteFile(marikoOemLoader, "Mariko_OEM_Bootloader.bin");
}
}
void WriteFile(IStorage storage, string filename)
{
string path = Path.Combine(outDir, filename);
ctx.Logger.LogMessage($"Writing {path}...");
storage.WriteAllBytes(path, ctx.Logger);
}
void WriteFile(IStorage storage, string filename)
{
string path = Path.Combine(outDir, filename);
ctx.Logger.LogMessage($"Writing {path}...");
storage.WriteAllBytes(path, ctx.Logger);
}
}
@ -105,43 +104,47 @@ namespace hactoolnet
public static void ProcessPk21(Context ctx)
{
using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false))
using var file = new SharedRef<IStorage>(new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false));
using var package2 = new Package2StorageReader();
package2.Initialize(ctx.KeySet, in file).ThrowIfFailure();
ctx.Logger.LogMessage(package2.Print());
string outDir = ctx.Options.OutDir;
string iniDir = ctx.Options.Ini1OutDir;
if (iniDir == null && ctx.Options.ExtractIni1)
{
var package2 = new Package2StorageReader();
package2.Initialize(ctx.KeySet, file).ThrowIfFailure();
iniDir = Path.Combine(outDir, "INI1");
}
ctx.Logger.LogMessage(package2.Print());
if (outDir != null)
{
Directory.CreateDirectory(outDir);
string outDir = ctx.Options.OutDir;
string iniDir = ctx.Options.Ini1OutDir;
using var kernelStorage = new UniqueRef<IStorage>();
package2.OpenPayload(ref kernelStorage.Ref(), 0).ThrowIfFailure();
kernelStorage.Get.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger);
if (iniDir == null && ctx.Options.ExtractIni1)
{
iniDir = Path.Combine(outDir, "INI1");
}
using var ini1Storage = new UniqueRef<IStorage>();
package2.OpenIni(ref ini1Storage.Ref()).ThrowIfFailure();
ini1Storage.Get.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger);
if (outDir != null)
{
Directory.CreateDirectory(outDir);
using var decPackageStorage = new UniqueRef<IStorage>();
package2.OpenDecryptedPackage(ref decPackageStorage.Ref()).ThrowIfFailure();
decPackageStorage.Get.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger);
}
package2.OpenPayload(out IStorage kernelStorage, 0).ThrowIfFailure();
kernelStorage.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger);
if (iniDir != null)
{
Directory.CreateDirectory(iniDir);
package2.OpenIni(out IStorage ini1Storage).ThrowIfFailure();
ini1Storage.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger);
using var ini1Storage = new UniqueRef<IStorage>();
package2.OpenIni(ref ini1Storage.Ref()).ThrowIfFailure();
package2.OpenDecryptedPackage(out IStorage decPackageStorage).ThrowIfFailure();
decPackageStorage.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger);
}
if (iniDir != null)
{
Directory.CreateDirectory(iniDir);
package2.OpenIni(out IStorage ini1Storage).ThrowIfFailure();
ProcessKip.ExtractIni1(ini1Storage, iniDir);
}
using SharedRef<IStorage> sharedIni1Storage = SharedRef<IStorage>.Create(ref ini1Storage.Ref());
ProcessKip.ExtractIni1(in sharedIni1Storage, iniDir);
}
}