mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Support extracting INI binaries embedded in the kernel
This commit is contained in:
parent
8491ec2117
commit
6bab1d9273
9 changed files with 196 additions and 17 deletions
|
@ -387,6 +387,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
||||||
|
|
||||||
428,51,,ServiceNotInitialized,
|
428,51,,ServiceNotInitialized,
|
||||||
|
|
||||||
|
428,101,,NotImplemented,
|
||||||
|
|
||||||
428,1000,1999,InvalidData,
|
428,1000,1999,InvalidData,
|
||||||
428,1001,1019,InvalidInitialProcessData,
|
428,1001,1019,InvalidInitialProcessData,
|
||||||
428,1002,1009,InvalidKip,
|
428,1002,1009,InvalidKip,
|
||||||
|
|
Can't render this file because it has a wrong number of fields in line 153.
|
|
@ -5,6 +5,7 @@ using LibHac.Common;
|
||||||
using LibHac.Crypto;
|
using LibHac.Crypto;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Kernel;
|
||||||
|
|
||||||
namespace LibHac.Boot
|
namespace LibHac.Boot
|
||||||
{
|
{
|
||||||
|
@ -13,6 +14,9 @@ namespace LibHac.Boot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Package2StorageReader
|
public class Package2StorageReader
|
||||||
{
|
{
|
||||||
|
private const int KernelPayloadIndex = 0;
|
||||||
|
private const int IniPayloadIndex = 1;
|
||||||
|
|
||||||
private IStorage _storage;
|
private IStorage _storage;
|
||||||
private Package2Header _header;
|
private Package2Header _header;
|
||||||
private Keyset _keyset;
|
private Keyset _keyset;
|
||||||
|
@ -69,6 +73,47 @@ namespace LibHac.Boot
|
||||||
return Result.Success;
|
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"/>
|
||||||
|
/// of the kernel payload.</param>
|
||||||
|
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||||
|
public Result OpenKernel(out IStorage kernelStorage)
|
||||||
|
{
|
||||||
|
return OpenPayload(out kernelStorage, 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"/>
|
||||||
|
/// of the initial process binary.</param>
|
||||||
|
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||||
|
public Result OpenIni(out IStorage iniStorage)
|
||||||
|
{
|
||||||
|
if (HasIniPayload())
|
||||||
|
{
|
||||||
|
return OpenPayload(out iniStorage, IniPayloadIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ini is embedded in the kernel
|
||||||
|
iniStorage = default;
|
||||||
|
|
||||||
|
Result rc = OpenKernel(out IStorage kernelStorage);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
if (!IniExtract.TryGetIni1Offset(out int offset, out int size, kernelStorage))
|
||||||
|
{
|
||||||
|
// Unable to find the ini. Could be a new, unsupported layout.
|
||||||
|
return ResultLibHac.NotImplemented.Log();
|
||||||
|
}
|
||||||
|
|
||||||
|
iniStorage = new SubStorage(kernelStorage, offset, size);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies the signature, metadata and payloads in the package.
|
/// Verifies the signature, metadata and payloads in the package.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -216,5 +261,10 @@ namespace LibHac.Boot
|
||||||
// Copy the IV to the output because the IV field will be garbage after "decrypting" it
|
// Copy the IV to the output because the IV field will be garbage after "decrypting" it
|
||||||
Unsafe.As<Package2Meta, Buffer16>(ref dest) = iv;
|
Unsafe.As<Package2Meta, Buffer16>(ref dest) = iv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasIniPayload()
|
||||||
|
{
|
||||||
|
return _header.Meta.PayloadSizes[IniPayloadIndex] != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ namespace LibHac.Common
|
||||||
|
|
||||||
/// <summary>Error code: 2428-0051; Inner value: 0x67ac</summary>
|
/// <summary>Error code: 2428-0051; Inner value: 0x67ac</summary>
|
||||||
public static Result.Base ServiceNotInitialized => new Result.Base(ModuleLibHac, 51);
|
public static Result.Base ServiceNotInitialized => new Result.Base(ModuleLibHac, 51);
|
||||||
|
/// <summary>Error code: 2428-0101; Inner value: 0xcbac</summary>
|
||||||
|
public static Result.Base NotImplemented => new Result.Base(ModuleLibHac, 101);
|
||||||
|
|
||||||
/// <summary>Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac</summary>
|
/// <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); }
|
public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); }
|
||||||
|
|
100
src/LibHac/Kernel/IniExtract.cs
Normal file
100
src/LibHac/Kernel/IniExtract.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
|
||||||
|
namespace LibHac.Kernel
|
||||||
|
{
|
||||||
|
public static class IniExtract
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Locates and returns the offset and size of the initial process binary embedded in the kernel.
|
||||||
|
/// The INI is only embedded in the kernel in system versions >= 8.0.0.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">When this method returns, contains the offset of
|
||||||
|
/// the INI inside the kernel if it was found.</param>
|
||||||
|
/// <param name="size">When this method returns, contains the size of the INI if it was found.</param>
|
||||||
|
/// <param name="kernelStorage">An <see cref="IStorage"/> containing the kernel to search.</param>
|
||||||
|
/// <returns><see langword="true"/> if the embedded INI was found.</returns>
|
||||||
|
public static bool TryGetIni1Offset(out int offset, out int size, IStorage kernelStorage)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
size = 0;
|
||||||
|
|
||||||
|
if (kernelStorage.GetSize(out long kernelSizeLong).IsFailure())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint kernelSize = (uint)kernelSizeLong;
|
||||||
|
|
||||||
|
using (var array = new RentedArray<byte>(0x1000 + Unsafe.SizeOf<KernelMap>()))
|
||||||
|
{
|
||||||
|
// The kernel map should be in the first 0x1000 bytes
|
||||||
|
if (kernelStorage.Read(0, array.Span).IsFailure())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ref byte start = ref array.Span[0];
|
||||||
|
|
||||||
|
// Search every 4 bytes for a valid kernel map
|
||||||
|
for (int i = 0; i < 0x1000; i += sizeof(int))
|
||||||
|
{
|
||||||
|
ref KernelMap map = ref Unsafe.As<byte, KernelMap>(ref Unsafe.Add(ref start, i));
|
||||||
|
|
||||||
|
if (IsValidKernelMap(in map, kernelSize))
|
||||||
|
{
|
||||||
|
// Verify the ini header at the offset in the found map
|
||||||
|
var header = new InitialProcessBinaryReader.IniHeader();
|
||||||
|
|
||||||
|
if (kernelStorage.Read(map.Ini1StartOffset, SpanHelpers.AsByteSpan(ref header)).IsFailure())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (header.Magic != InitialProcessBinaryReader.ExpectedMagic)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
offset = (int)map.Ini1StartOffset;
|
||||||
|
size = header.Size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidKernelMap(in KernelMap map, uint maxSize)
|
||||||
|
{
|
||||||
|
if (map.TextStartOffset != 0) return false;
|
||||||
|
if (map.TextStartOffset >= map.TextEndOffset) return false;
|
||||||
|
if ((map.TextEndOffset & 0xFFF) != 0) return false;
|
||||||
|
if (map.TextEndOffset > map.RodataStartOffset) return false;
|
||||||
|
if ((map.RodataStartOffset & 0xFFF) != 0) return false;
|
||||||
|
if (map.RodataStartOffset >= map.RodataEndOffset) return false;
|
||||||
|
if ((map.RodataEndOffset & 0xFFF) != 0) return false;
|
||||||
|
if (map.RodataEndOffset > map.DataStartOffset) return false;
|
||||||
|
if ((map.DataStartOffset & 0xFFF) != 0) return false;
|
||||||
|
if (map.DataStartOffset >= map.DataEndOffset) return false;
|
||||||
|
if (map.DataEndOffset > map.BssStartOffset) return false;
|
||||||
|
if (map.BssStartOffset > map.BssEndOffset) return false;
|
||||||
|
if (map.BssEndOffset > map.Ini1StartOffset) return false;
|
||||||
|
if (map.Ini1StartOffset > maxSize - Unsafe.SizeOf<KernelMap>()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct KernelMap
|
||||||
|
{
|
||||||
|
public uint TextStartOffset;
|
||||||
|
public uint TextEndOffset;
|
||||||
|
public uint RodataStartOffset;
|
||||||
|
public uint RodataEndOffset;
|
||||||
|
public uint DataStartOffset;
|
||||||
|
public uint DataEndOffset;
|
||||||
|
public uint BssStartOffset;
|
||||||
|
public uint BssEndOffset;
|
||||||
|
public uint Ini1StartOffset;
|
||||||
|
public uint DynamicOffset;
|
||||||
|
public uint InitArrayStartOffset;
|
||||||
|
public uint InitArrayEndOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ namespace LibHac.Kernel
|
||||||
{
|
{
|
||||||
public class InitialProcessBinaryReader
|
public class InitialProcessBinaryReader
|
||||||
{
|
{
|
||||||
private const uint ExpectedMagic = 0x31494E49; // INI1
|
internal const uint ExpectedMagic = 0x31494E49; // INI1
|
||||||
private const int MaxProcessCount = 80;
|
private const int MaxProcessCount = 80;
|
||||||
|
|
||||||
private IStorage _storage;
|
private IStorage _storage;
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace hactoolnet
|
||||||
new CliOption("debugoutdir", 1, (o, a) => o.DebugOutDir = a[0]),
|
new CliOption("debugoutdir", 1, (o, a) => o.DebugOutDir = a[0]),
|
||||||
new CliOption("savedir", 1, (o, a) => o.SaveOutDir = a[0]),
|
new CliOption("savedir", 1, (o, a) => o.SaveOutDir = a[0]),
|
||||||
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
||||||
|
new CliOption("ini1dir", 1, (o, a) => o.Ini1OutDir = a[0]),
|
||||||
new CliOption("outfile", 1, (o, a) => o.OutFile = a[0]),
|
new CliOption("outfile", 1, (o, a) => o.OutFile = a[0]),
|
||||||
new CliOption("plaintext", 1, (o, a) => o.PlaintextOut = a[0]),
|
new CliOption("plaintext", 1, (o, a) => o.PlaintextOut = a[0]),
|
||||||
new CliOption("ciphertext", 1, (o, a) => o.CiphertextOut = a[0]),
|
new CliOption("ciphertext", 1, (o, a) => o.CiphertextOut = a[0]),
|
||||||
|
@ -60,6 +61,7 @@ namespace hactoolnet
|
||||||
new CliOption("trim", 0, (o, a) => o.TrimSave = true),
|
new CliOption("trim", 0, (o, a) => o.TrimSave = true),
|
||||||
new CliOption("readbench", 0, (o, a) => o.ReadBench = true),
|
new CliOption("readbench", 0, (o, a) => o.ReadBench = true),
|
||||||
new CliOption("hashedfs", 0, (o, a) => o.BuildHfs = true),
|
new CliOption("hashedfs", 0, (o, a) => o.BuildHfs = true),
|
||||||
|
new CliOption("extractini1", 0, (o, a) => o.ExtractIni1 = true),
|
||||||
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
|
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
|
||||||
new CliOption("bench", 1, (o, a) => o.BenchType = a[0]),
|
new CliOption("bench", 1, (o, a) => o.BenchType = a[0]),
|
||||||
new CliOption("cpufreq", 1, (o, a) => o.CpuFrequencyGhz = ParseDouble(a[0])),
|
new CliOption("cpufreq", 1, (o, a) => o.CpuFrequencyGhz = ParseDouble(a[0])),
|
||||||
|
@ -254,6 +256,8 @@ namespace hactoolnet
|
||||||
sb.AppendLine(" --outdir <dir> Specify Package1 directory path.");
|
sb.AppendLine(" --outdir <dir> Specify Package1 directory path.");
|
||||||
sb.AppendLine("Package2 options:");
|
sb.AppendLine("Package2 options:");
|
||||||
sb.AppendLine(" --outdir <dir> Specify Package2 directory path.");
|
sb.AppendLine(" --outdir <dir> Specify Package2 directory path.");
|
||||||
|
sb.AppendLine(" --extractini1 Enable INI1 extraction to default directory (redundant with --ini1dir set).");
|
||||||
|
sb.AppendLine(" --ini1dir <dir> Specify INI1 directory path. Overrides default path, if present.");
|
||||||
sb.AppendLine("INI1 options:");
|
sb.AppendLine("INI1 options:");
|
||||||
sb.AppendLine(" --outdir <dir> Specify INI1 directory path.");
|
sb.AppendLine(" --outdir <dir> Specify INI1 directory path.");
|
||||||
sb.AppendLine("Switch FS options:");
|
sb.AppendLine("Switch FS options:");
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace hactoolnet
|
||||||
public string RomfsOutDir;
|
public string RomfsOutDir;
|
||||||
public string DebugOutDir;
|
public string DebugOutDir;
|
||||||
public string SaveOutDir;
|
public string SaveOutDir;
|
||||||
|
public string Ini1OutDir;
|
||||||
public string OutDir;
|
public string OutDir;
|
||||||
public string OutFile;
|
public string OutFile;
|
||||||
public string PlaintextOut;
|
public string PlaintextOut;
|
||||||
|
@ -54,6 +55,7 @@ namespace hactoolnet
|
||||||
public bool TrimSave;
|
public bool TrimSave;
|
||||||
public bool ReadBench;
|
public bool ReadBench;
|
||||||
public bool BuildHfs;
|
public bool BuildHfs;
|
||||||
|
public bool ExtractIni1;
|
||||||
public ulong TitleId;
|
public ulong TitleId;
|
||||||
public string BenchType;
|
public string BenchType;
|
||||||
public double CpuFrequencyGhz;
|
public double CpuFrequencyGhz;
|
||||||
|
|
|
@ -29,27 +29,31 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read))
|
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read))
|
||||||
{
|
{
|
||||||
|
|
||||||
var ini1 = new InitialProcessBinaryReader();
|
|
||||||
ini1.Initialize(file).ThrowIfFailure();
|
|
||||||
|
|
||||||
string outDir = ctx.Options.OutDir;
|
string outDir = ctx.Options.OutDir;
|
||||||
|
|
||||||
if (outDir != null)
|
if (outDir != null)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(outDir);
|
ExtractIni1(file, outDir);
|
||||||
var kipReader = new KipReader();
|
|
||||||
|
|
||||||
for (int i = 0; i < ini1.ProcessCount; i++)
|
|
||||||
{
|
|
||||||
ini1.OpenKipStorage(out IStorage kipStorage, i).ThrowIfFailure();
|
|
||||||
|
|
||||||
kipReader.Initialize(kipStorage).ThrowIfFailure();
|
|
||||||
|
|
||||||
kipStorage.WriteAllBytes(Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ExtractIni1(IStorage iniStorage, string outDir)
|
||||||
|
{
|
||||||
|
var ini1 = new InitialProcessBinaryReader();
|
||||||
|
ini1.Initialize(iniStorage).ThrowIfFailure();
|
||||||
|
|
||||||
|
Directory.CreateDirectory(outDir);
|
||||||
|
var kipReader = new KipReader();
|
||||||
|
|
||||||
|
for (int i = 0; i < ini1.ProcessCount; i++)
|
||||||
|
{
|
||||||
|
ini1.OpenKipStorage(out IStorage kipStorage, i).ThrowIfFailure();
|
||||||
|
|
||||||
|
kipReader.Initialize(kipStorage).ThrowIfFailure();
|
||||||
|
|
||||||
|
kipStorage.WriteAllBytes(Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ namespace hactoolnet
|
||||||
ctx.Logger.LogMessage(package2.Print());
|
ctx.Logger.LogMessage(package2.Print());
|
||||||
|
|
||||||
string outDir = ctx.Options.OutDir;
|
string outDir = ctx.Options.OutDir;
|
||||||
|
string iniDir = ctx.Options.Ini1OutDir;
|
||||||
|
|
||||||
|
if (iniDir == null && ctx.Options.ExtractIni1)
|
||||||
|
{
|
||||||
|
iniDir = Path.Combine(outDir, "INI1");
|
||||||
|
}
|
||||||
|
|
||||||
if (outDir != null)
|
if (outDir != null)
|
||||||
{
|
{
|
||||||
|
@ -47,12 +53,21 @@ namespace hactoolnet
|
||||||
package2.OpenPayload(out IStorage kernelStorage, 0).ThrowIfFailure();
|
package2.OpenPayload(out IStorage kernelStorage, 0).ThrowIfFailure();
|
||||||
kernelStorage.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger);
|
kernelStorage.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger);
|
||||||
|
|
||||||
package2.OpenPayload(out IStorage ini1Storage, 1).ThrowIfFailure();
|
package2.OpenIni(out IStorage ini1Storage).ThrowIfFailure();
|
||||||
ini1Storage.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger);
|
ini1Storage.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger);
|
||||||
|
|
||||||
package2.OpenDecryptedPackage(out IStorage decPackageStorage).ThrowIfFailure();
|
package2.OpenDecryptedPackage(out IStorage decPackageStorage).ThrowIfFailure();
|
||||||
decPackageStorage.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue