mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Merge pull request #126 from Thealexbarney/nso
Rewrite Nso and Kip classes
This commit is contained in:
commit
a5699182b0
14 changed files with 803 additions and 6 deletions
|
@ -1,4 +1,6 @@
|
|||
Name,Index
|
||||
Fs,2
|
||||
Loader,9
|
||||
Kvdb,20
|
||||
Sdmmc,24
|
||||
Sdmmc,24
|
||||
LibHac,428
|
|
|
@ -1,4 +1,6 @@
|
|||
Name,Namespace,Path
|
||||
Fs,LibHac.Fs,LibHac/Fs/ResultFs.cs
|
||||
Loader,LibHac.Loader,LibHac/Loader/ResultLoader.cs
|
||||
Kvdb,LibHac.Kvdb,LibHac/Kvdb/ResultKvdb.cs
|
||||
Sdmmc,LibHac.FsService,LibHac/FsService/ResultSdmmc.cs
|
||||
Sdmmc,LibHac.FsService,LibHac/FsService/ResultSdmmc.cs
|
||||
LibHac,LibHac.Common,LibHac/Common/ResultLibHac.cs
|
|
|
@ -229,6 +229,45 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
|||
2,6905,,NotMounted,
|
||||
2,6906,,SaveDataIsExtending,
|
||||
|
||||
9,1,,TooLongArgument,
|
||||
9,2,,TooManyArguments,
|
||||
9,3,,TooLargeMeta,
|
||||
9,4,,InvalidMeta,
|
||||
9,5,,InvalidNso,
|
||||
9,6,,InvalidPath,
|
||||
9,7,,TooManyProcesses,
|
||||
9,8,,NotPinned,
|
||||
9,9,,InvalidProgramId,
|
||||
9,10,,InvalidVersion,
|
||||
|
||||
9,51,,InsufficientAddressSpace,
|
||||
9,52,,InvalidNro,
|
||||
9,53,,InvalidNrr,
|
||||
9,54,,InvalidSignature,
|
||||
9,55,,InsufficientNroRegistrations,
|
||||
9,56,,InsufficientNrrRegistrations,
|
||||
9,57,,NroAlreadyLoaded,
|
||||
|
||||
9,81,,InvalidAddress,
|
||||
9,82,,InvalidSize,
|
||||
9,84,,NotLoaded,
|
||||
9,85,,NotRegistered,
|
||||
9,86,,InvalidSession,
|
||||
9,87,,InvalidProcess,
|
||||
|
||||
9,100,,UnknownCapability,
|
||||
9,103,,InvalidCapabilityKernelFlags,
|
||||
9,104,,InvalidCapabilitySyscallMask,
|
||||
9,106,,InvalidCapabilityMapRange,
|
||||
9,107,,InvalidCapabilityMapPage,
|
||||
9,111,,InvalidCapabilityInterruptPair,
|
||||
9,113,,InvalidCapabilityApplicationType,
|
||||
9,114,,InvalidCapabilityKernelVersion,
|
||||
9,115,,InvalidCapabilityHandleTable,
|
||||
9,116,,InvalidCapabilityDebugFlags,
|
||||
|
||||
9,200,,InternalError,
|
||||
|
||||
20,1,,TooLargeKeyOrDbFull,
|
||||
20,2,,KeyNotFound,
|
||||
20,4,,AllocationFailed,
|
||||
|
@ -261,4 +300,16 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
|||
205,110,,IrsensorUnconnected,
|
||||
205,111,,IrsensorUnsupported,
|
||||
205,120,,IrsensorNotReady,
|
||||
205,122,139,IrsensorDeviceError,
|
||||
205,122,139,IrsensorDeviceError,
|
||||
|
||||
428,1,49,InvalidArgument,
|
||||
428,2,,NullArgument,
|
||||
428,3,,ArgumentOutOfRange,
|
||||
428,4,,BufferTooSmall,
|
||||
|
||||
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.
|
||||
|
|
|
42
src/LibHac/Common/ResultLibHac.cs
Normal file
42
src/LibHac/Common/ResultLibHac.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// This file was automatically generated.
|
||||
// Changes to this file will be lost when the file is regenerated.
|
||||
//
|
||||
// To change this file, modify /build/CodeGen/results.csv at the root of this
|
||||
// repo and run the build script.
|
||||
//
|
||||
// The script can be run with the "codegen" option to run only the
|
||||
// code generation portion of the build.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
public static class ResultLibHac
|
||||
{
|
||||
public const int ModuleLibHac = 428;
|
||||
|
||||
/// <summary>Error code: 2428-0001; Range: 1-49; Inner value: 0x3ac</summary>
|
||||
public static Result.Base InvalidArgument => new Result.Base(ModuleLibHac, 1, 49);
|
||||
/// <summary>Error code: 2428-0002; Inner value: 0x5ac</summary>
|
||||
public static Result.Base NullArgument => new Result.Base(ModuleLibHac, 2);
|
||||
/// <summary>Error code: 2428-0003; Inner value: 0x7ac</summary>
|
||||
public static Result.Base ArgumentOutOfRange => new Result.Base(ModuleLibHac, 3);
|
||||
/// <summary>Error code: 2428-0004; Inner value: 0x9ac</summary>
|
||||
public static Result.Base BufferTooSmall => new Result.Base(ModuleLibHac, 4);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using LibHac.FsSystem;
|
|||
|
||||
namespace LibHac
|
||||
{
|
||||
[Obsolete("This class has been deprecated. LibHac.Loader.KipReader should be used instead.")]
|
||||
public class Kip
|
||||
{
|
||||
private const int HeaderSize = 0x100;
|
||||
|
|
76
src/LibHac/Loader/KipHeader.cs
Normal file
76
src/LibHac/Loader/KipHeader.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Loader
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x100)]
|
||||
public struct KipHeader
|
||||
{
|
||||
public const uint Kip1Magic = 0x3150494B; // KIP1
|
||||
public const int NameSize = 12;
|
||||
public const int SegmentCount = 6;
|
||||
|
||||
[FieldOffset(0x00)] public uint Magic;
|
||||
|
||||
[FieldOffset(0x04)] private byte _name;
|
||||
|
||||
[FieldOffset(0x10)] public ulong ProgramId;
|
||||
[FieldOffset(0x18)] public int Version;
|
||||
|
||||
[FieldOffset(0x1C)] public byte Priority;
|
||||
[FieldOffset(0x1D)] public byte IdealCoreId;
|
||||
[FieldOffset(0x1F)] public Flag Flags;
|
||||
|
||||
[FieldOffset(0x20)] public int TextMemoryOffset;
|
||||
[FieldOffset(0x24)] public int TextSize;
|
||||
[FieldOffset(0x28)] public int TextFileSize;
|
||||
|
||||
[FieldOffset(0x2C)] public int AffinityMask;
|
||||
|
||||
[FieldOffset(0x30)] public int RoMemoryOffset;
|
||||
[FieldOffset(0x34)] public int RoSize;
|
||||
[FieldOffset(0x38)] public int RoFileSize;
|
||||
|
||||
[FieldOffset(0x3C)] public int StackSize;
|
||||
|
||||
[FieldOffset(0x40)] public int DataMemoryOffset;
|
||||
[FieldOffset(0x44)] public int DataSize;
|
||||
[FieldOffset(0x48)] public int DataFileSize;
|
||||
|
||||
[FieldOffset(0x50)] public int BssMemoryOffset;
|
||||
[FieldOffset(0x54)] public int BssSize;
|
||||
[FieldOffset(0x58)] public int BssFileSize;
|
||||
|
||||
[FieldOffset(0x80)] private uint _capabilities;
|
||||
|
||||
public Span<byte> Name => SpanHelpers.CreateSpan(ref _name, NameSize);
|
||||
|
||||
public Span<SegmentHeader> Segments =>
|
||||
SpanHelpers.CreateSpan(ref Unsafe.As<int, SegmentHeader>(ref TextMemoryOffset), SegmentCount);
|
||||
|
||||
public Span<uint> Capabilities => SpanHelpers.CreateSpan(ref _capabilities, 0x80 / sizeof(uint));
|
||||
|
||||
public bool IsValid => Magic == Kip1Magic;
|
||||
|
||||
[Flags]
|
||||
public enum Flag : byte
|
||||
{
|
||||
TextCompress = 1 << 0,
|
||||
RoCompress = 1 << 1,
|
||||
DataCompress = 1 << 2,
|
||||
Is64BitInstruction = 1 << 3,
|
||||
ProcessAddressSpace64Bit = 1 << 4,
|
||||
UseSecureMemory = 1 << 5
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
public struct SegmentHeader
|
||||
{
|
||||
public int MemoryOffset;
|
||||
public int Size;
|
||||
public int FileSize;
|
||||
}
|
||||
}
|
||||
}
|
265
src/LibHac/Loader/KipReader.cs
Normal file
265
src/LibHac/Loader/KipReader.cs
Normal file
|
@ -0,0 +1,265 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.Loader
|
||||
{
|
||||
public class KipReader
|
||||
{
|
||||
private IFile KipFile { get; set; }
|
||||
|
||||
private KipHeader _header;
|
||||
|
||||
public ReadOnlySpan<uint> Capabilities => _header.Capabilities;
|
||||
public U8Span Name => new U8Span(_header.Name);
|
||||
|
||||
public ulong ProgramId => _header.ProgramId;
|
||||
public int Version => _header.Version;
|
||||
public byte Priority => _header.Priority;
|
||||
public byte IdealCoreId => _header.IdealCoreId;
|
||||
|
||||
public bool IsTextCompressed => _header.Flags.HasFlag(KipHeader.Flag.TextCompress);
|
||||
public bool IsRoCompressed => _header.Flags.HasFlag(KipHeader.Flag.RoCompress);
|
||||
public bool IsDataCompressed => _header.Flags.HasFlag(KipHeader.Flag.DataCompress);
|
||||
public bool Is64Bit => _header.Flags.HasFlag(KipHeader.Flag.Is64BitInstruction);
|
||||
public bool Is64BitAddressSpace => _header.Flags.HasFlag(KipHeader.Flag.ProcessAddressSpace64Bit);
|
||||
public bool UsesSecureMemory => _header.Flags.HasFlag(KipHeader.Flag.UseSecureMemory);
|
||||
|
||||
public ReadOnlySpan<KipHeader.SegmentHeader> Segments => _header.Segments;
|
||||
|
||||
public int AffinityMask => _header.AffinityMask;
|
||||
public int StackSize => _header.StackSize;
|
||||
|
||||
public Result Initialize(IFile kipFile)
|
||||
{
|
||||
if (kipFile is null)
|
||||
return ResultLibHac.NullArgument.Log();
|
||||
|
||||
Result rc = kipFile.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref _header), ReadOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (bytesRead != Unsafe.SizeOf<KipHeader>())
|
||||
return ResultLibHac.InvalidKipFileSize.Log();
|
||||
|
||||
if (!_header.IsValid)
|
||||
return ResultLibHac.InvalidKipMagic.Log();
|
||||
|
||||
KipFile = kipFile;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetSegmentSize(SegmentType segment, out int size)
|
||||
{
|
||||
switch (segment)
|
||||
{
|
||||
case SegmentType.Text:
|
||||
case SegmentType.Ro:
|
||||
case SegmentType.Data:
|
||||
case SegmentType.Bss:
|
||||
case SegmentType.Reserved1:
|
||||
case SegmentType.Reserved2:
|
||||
size = _header.Segments[(int)segment].Size;
|
||||
return Result.Success;
|
||||
default:
|
||||
size = default;
|
||||
return ResultLibHac.ArgumentOutOfRange.Log();
|
||||
}
|
||||
}
|
||||
|
||||
public int GetUncompressedSize()
|
||||
{
|
||||
int size = Unsafe.SizeOf<KipHeader>();
|
||||
|
||||
for (int i = 0; i < Segments.Length; i++)
|
||||
{
|
||||
if (Segments[i].FileSize != 0)
|
||||
{
|
||||
size += Segments[i].Size;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public Result ReadSegment(SegmentType segment, Span<byte> buffer)
|
||||
{
|
||||
Result rc = GetSegmentSize(segment, out int segmentSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (buffer.Length < segmentSize)
|
||||
return ResultLibHac.BufferTooSmall.Log();
|
||||
|
||||
KipHeader.SegmentHeader segmentHeader = Segments[(int)segment];
|
||||
|
||||
// Return early for empty segments.
|
||||
if (segmentHeader.Size == 0)
|
||||
return Result.Success;
|
||||
|
||||
// The segment is all zeros if it has no data.
|
||||
if (segmentHeader.FileSize == 0)
|
||||
{
|
||||
buffer.Slice(0, segmentHeader.Size).Clear();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
int offset = CalculateSegmentOffset((int)segment);
|
||||
|
||||
// Read the segment data.
|
||||
rc = KipFile.Read(out long bytesRead, offset, buffer.Slice(0, segmentHeader.FileSize), ReadOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (bytesRead != segmentHeader.FileSize)
|
||||
return ResultLibHac.InvalidKipFileSize.Log();
|
||||
|
||||
// Decompress if necessary.
|
||||
bool isCompressed = segment switch
|
||||
{
|
||||
SegmentType.Text => IsTextCompressed,
|
||||
SegmentType.Ro => IsRoCompressed,
|
||||
SegmentType.Data => IsDataCompressed,
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (isCompressed)
|
||||
{
|
||||
rc = DecompressBlz(buffer, segmentHeader.FileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result ReadUncompressedKip(Span<byte> buffer)
|
||||
{
|
||||
if (buffer.Length < GetUncompressedSize())
|
||||
return ResultLibHac.BufferTooSmall.Log();
|
||||
|
||||
Span<byte> segmentBuffer = buffer.Slice(Unsafe.SizeOf<KipHeader>());
|
||||
|
||||
// Read each of the segments into the buffer.
|
||||
for (int i = 0; i < Segments.Length; i++)
|
||||
{
|
||||
if (Segments[i].FileSize != 0)
|
||||
{
|
||||
Result rc = ReadSegment((SegmentType)i, segmentBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
segmentBuffer = segmentBuffer.Slice(Segments[i].Size);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the header to the buffer and update the sizes and flags.
|
||||
ref KipHeader header = ref Unsafe.As<byte, KipHeader>(ref MemoryMarshal.GetReference(buffer));
|
||||
header = _header;
|
||||
|
||||
// Remove any compression flags.
|
||||
const KipHeader.Flag compressFlagsMask =
|
||||
~(KipHeader.Flag.TextCompress | KipHeader.Flag.RoCompress | KipHeader.Flag.DataCompress);
|
||||
|
||||
header.Flags &= compressFlagsMask;
|
||||
|
||||
// Update each segment's uncompressed size.
|
||||
foreach (ref KipHeader.SegmentHeader segment in header.Segments)
|
||||
{
|
||||
if (segment.FileSize != 0)
|
||||
{
|
||||
segment.FileSize = segment.Size;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private int CalculateSegmentOffset(int index)
|
||||
{
|
||||
Debug.Assert((uint)index <= (uint)SegmentType.Reserved2);
|
||||
|
||||
int offset = Unsafe.SizeOf<KipHeader>();
|
||||
ReadOnlySpan<KipHeader.SegmentHeader> segments = Segments;
|
||||
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
offset += segments[i].FileSize;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static Result DecompressBlz(Span<byte> buffer, int compressedDataSize)
|
||||
{
|
||||
const int segmentFooterSize = 12;
|
||||
|
||||
if (buffer.Length < segmentFooterSize)
|
||||
return ResultLibHac.InvalidKipSegmentSize.Log();
|
||||
|
||||
// Parse the footer, endian agnostic.
|
||||
Span<byte> footer = buffer.Slice(compressedDataSize - segmentFooterSize);
|
||||
int totalCompSize = BinaryPrimitives.ReadInt32LittleEndian(footer);
|
||||
int footerSize = BinaryPrimitives.ReadInt32LittleEndian(footer.Slice(4));
|
||||
int additionalSize = BinaryPrimitives.ReadInt32LittleEndian(footer.Slice(8));
|
||||
|
||||
if (buffer.Length < totalCompSize + additionalSize)
|
||||
return ResultLibHac.BufferTooSmall.Log();
|
||||
|
||||
Span<byte> data = buffer;
|
||||
|
||||
int inOffset = totalCompSize - footerSize;
|
||||
int outOffset = totalCompSize + additionalSize;
|
||||
|
||||
while (outOffset != 0)
|
||||
{
|
||||
byte control = data[--inOffset];
|
||||
|
||||
// Each bit in the control byte is a flag indicating compressed or not compressed.
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if ((control & 0x80) != 0)
|
||||
{
|
||||
if (inOffset < 2)
|
||||
return ResultLibHac.KipSegmentDecompressionFailed.Log();
|
||||
|
||||
inOffset -= 2;
|
||||
ushort segmentValue = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(inOffset));
|
||||
int segmentOffset = (segmentValue & 0x0FFF) + 3;
|
||||
int segmentSize = Math.Min(((segmentValue >> 12) & 0xF) + 3, outOffset);
|
||||
|
||||
outOffset -= segmentSize;
|
||||
|
||||
for (int j = 0; j < segmentSize; j++)
|
||||
{
|
||||
data[outOffset + j] = data[outOffset + segmentOffset + j];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inOffset < 1)
|
||||
return ResultLibHac.KipSegmentDecompressionFailed.Log();
|
||||
|
||||
// Copy directly.
|
||||
data[--outOffset] = data[--inOffset];
|
||||
}
|
||||
control <<= 1;
|
||||
|
||||
if (outOffset == 0)
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public enum SegmentType
|
||||
{
|
||||
Text = 0,
|
||||
Ro = 1,
|
||||
Data = 2,
|
||||
Bss = 3,
|
||||
Reserved1 = 4,
|
||||
Reserved2 = 5
|
||||
}
|
||||
}
|
||||
}
|
84
src/LibHac/Loader/NsoHeader.cs
Normal file
84
src/LibHac/Loader/NsoHeader.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Loader
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x100)]
|
||||
public struct NsoHeader
|
||||
{
|
||||
public const int SegmentCount = 3;
|
||||
|
||||
[FieldOffset(0x00)] public uint Magic;
|
||||
[FieldOffset(0x04)] public uint Version;
|
||||
[FieldOffset(0x08)] public uint Reserved08;
|
||||
[FieldOffset(0x0C)] public Flag Flags;
|
||||
|
||||
[FieldOffset(0x10)] public uint TextFileOffset;
|
||||
[FieldOffset(0x14)] public uint TextMemoryOffset;
|
||||
[FieldOffset(0x18)] public uint TextSize;
|
||||
|
||||
[FieldOffset(0x1C)] public uint ModuleNameOffset;
|
||||
|
||||
[FieldOffset(0x20)] public uint RoFileOffset;
|
||||
[FieldOffset(0x24)] public uint RoMemoryOffset;
|
||||
[FieldOffset(0x28)] public uint RoSize;
|
||||
|
||||
[FieldOffset(0x2C)] public uint ModuleNameSize;
|
||||
|
||||
[FieldOffset(0x30)] public uint DataFileOffset;
|
||||
[FieldOffset(0x34)] public uint DataMemoryOffset;
|
||||
[FieldOffset(0x38)] public uint DataSize;
|
||||
|
||||
[FieldOffset(0x3C)] public uint BssSize;
|
||||
|
||||
[FieldOffset(0x40)] public Buffer32 ModuleId;
|
||||
|
||||
// Size of the sections in the NSO file
|
||||
[FieldOffset(0x60)] public uint TextFileSize;
|
||||
[FieldOffset(0x64)] public uint RoFileSize;
|
||||
[FieldOffset(0x68)] public uint DataFileSize;
|
||||
|
||||
[FieldOffset(0x68)] private byte _reserved6C;
|
||||
|
||||
[FieldOffset(0x88)] public uint ApiInfoOffset;
|
||||
[FieldOffset(0x8C)] public uint ApiInfoSize;
|
||||
[FieldOffset(0x90)] public uint DynStrOffset;
|
||||
[FieldOffset(0x94)] public uint DynStrSize;
|
||||
[FieldOffset(0x98)] public uint DynSymOffset;
|
||||
[FieldOffset(0x9C)] public uint DynSymSize;
|
||||
|
||||
[FieldOffset(0xA0)] public Buffer32 TextHash;
|
||||
[FieldOffset(0xC0)] public Buffer32 RoHash;
|
||||
[FieldOffset(0xE0)] public Buffer32 DataHash;
|
||||
|
||||
public Span<SegmentHeader> Segments =>
|
||||
SpanHelpers.CreateSpan(ref Unsafe.As<uint, SegmentHeader>(ref TextFileOffset), SegmentCount);
|
||||
|
||||
public Span<uint> CompressedSizes => SpanHelpers.CreateSpan(ref TextFileSize, SegmentCount);
|
||||
|
||||
public Span<Buffer32> SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount);
|
||||
|
||||
public Span<byte> Reserved6C => SpanHelpers.CreateSpan(ref _reserved6C, 0x1C);
|
||||
|
||||
[Flags]
|
||||
public enum Flag
|
||||
{
|
||||
TextCompress = 1 << 0,
|
||||
RoCompress = 1 << 1,
|
||||
DataCompress = 1 << 2,
|
||||
TextHash = 1 << 3,
|
||||
RoHash = 1 << 4,
|
||||
DataHash = 1 << 5
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
public struct SegmentHeader
|
||||
{
|
||||
public uint FileOffset;
|
||||
public uint MemoryOffset;
|
||||
public uint Size;
|
||||
}
|
||||
}
|
||||
}
|
104
src/LibHac/Loader/NsoReader.cs
Normal file
104
src/LibHac/Loader/NsoReader.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.Loader
|
||||
{
|
||||
public class NsoReader
|
||||
{
|
||||
private IFile NsoFile { get; set; }
|
||||
|
||||
public NsoHeader Header;
|
||||
|
||||
public Result Initialize(IFile nsoFile)
|
||||
{
|
||||
Result rc = nsoFile.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref Header), ReadOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (bytesRead != Unsafe.SizeOf<NsoHeader>())
|
||||
return ResultLoader.InvalidNso.Log();
|
||||
|
||||
NsoFile = nsoFile;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetSegmentSize(SegmentType segment, out uint size)
|
||||
{
|
||||
switch (segment)
|
||||
{
|
||||
case SegmentType.Text:
|
||||
case SegmentType.Ro:
|
||||
case SegmentType.Data:
|
||||
size = Header.Segments[(int)segment].Size;
|
||||
return Result.Success;
|
||||
default:
|
||||
size = default;
|
||||
return ResultLibHac.ArgumentOutOfRange.Log();
|
||||
}
|
||||
}
|
||||
|
||||
public Result ReadSegment(SegmentType segment, Span<byte> buffer)
|
||||
{
|
||||
Result rc = GetSegmentSize(segment, out uint segmentSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (buffer.Length < segmentSize)
|
||||
return ResultLibHac.BufferTooSmall.Log();
|
||||
|
||||
bool isCompressed = (((int)Header.Flags >> (int)segment) & 1) != 0;
|
||||
bool checkHash = (((int)Header.Flags >> (int)segment) & 8) != 0;
|
||||
|
||||
return ReadSegmentImpl(ref Header.Segments[(int)segment], Header.CompressedSizes[(int)segment],
|
||||
Header.SegmentHashes[(int)segment], isCompressed, checkHash, buffer);
|
||||
}
|
||||
|
||||
private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Buffer32 fileHash,
|
||||
bool isCompressed, bool checkHash, Span<byte> buffer)
|
||||
{
|
||||
// Select read size based on compression.
|
||||
if (!isCompressed)
|
||||
{
|
||||
fileSize = segment.Size;
|
||||
}
|
||||
|
||||
// Validate size.
|
||||
if (fileSize > segment.Size)
|
||||
return ResultLoader.InvalidNso.Log();
|
||||
|
||||
// Load data from file.
|
||||
uint loadAddress = isCompressed ? (uint)buffer.Length - fileSize : 0;
|
||||
|
||||
Result rc = NsoFile.Read(out long bytesRead, segment.FileOffset, buffer.Slice((int)loadAddress), ReadOption.None);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (bytesRead != fileSize)
|
||||
return ResultLoader.InvalidNso.Log();
|
||||
|
||||
// Uncompress if necessary.
|
||||
if (isCompressed)
|
||||
{
|
||||
Lz4.Decompress(buffer.Slice((int)loadAddress), buffer);
|
||||
}
|
||||
|
||||
// Check hash if necessary.
|
||||
if (checkHash)
|
||||
{
|
||||
Buffer32 hash = default;
|
||||
Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Bytes);
|
||||
|
||||
if (hash.Bytes.SequenceCompareTo(fileHash.Bytes) != 0)
|
||||
return ResultLoader.InvalidNso.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public enum SegmentType
|
||||
{
|
||||
Text = 0,
|
||||
Ro = 1,
|
||||
Data = 2
|
||||
}
|
||||
}
|
||||
}
|
87
src/LibHac/Loader/ResultLoader.cs
Normal file
87
src/LibHac/Loader/ResultLoader.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// This file was automatically generated.
|
||||
// Changes to this file will be lost when the file is regenerated.
|
||||
//
|
||||
// To change this file, modify /build/CodeGen/results.csv at the root of this
|
||||
// repo and run the build script.
|
||||
//
|
||||
// The script can be run with the "codegen" option to run only the
|
||||
// code generation portion of the build.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace LibHac.Loader
|
||||
{
|
||||
public static class ResultLoader
|
||||
{
|
||||
public const int ModuleLoader = 9;
|
||||
|
||||
/// <summary>Error code: 2009-0001; Inner value: 0x209</summary>
|
||||
public static Result.Base TooLongArgument => new Result.Base(ModuleLoader, 1);
|
||||
/// <summary>Error code: 2009-0002; Inner value: 0x409</summary>
|
||||
public static Result.Base TooManyArguments => new Result.Base(ModuleLoader, 2);
|
||||
/// <summary>Error code: 2009-0003; Inner value: 0x609</summary>
|
||||
public static Result.Base TooLargeMeta => new Result.Base(ModuleLoader, 3);
|
||||
/// <summary>Error code: 2009-0004; Inner value: 0x809</summary>
|
||||
public static Result.Base InvalidMeta => new Result.Base(ModuleLoader, 4);
|
||||
/// <summary>Error code: 2009-0005; Inner value: 0xa09</summary>
|
||||
public static Result.Base InvalidNso => new Result.Base(ModuleLoader, 5);
|
||||
/// <summary>Error code: 2009-0006; Inner value: 0xc09</summary>
|
||||
public static Result.Base InvalidPath => new Result.Base(ModuleLoader, 6);
|
||||
/// <summary>Error code: 2009-0007; Inner value: 0xe09</summary>
|
||||
public static Result.Base TooManyProcesses => new Result.Base(ModuleLoader, 7);
|
||||
/// <summary>Error code: 2009-0008; Inner value: 0x1009</summary>
|
||||
public static Result.Base NotPinned => new Result.Base(ModuleLoader, 8);
|
||||
/// <summary>Error code: 2009-0009; Inner value: 0x1209</summary>
|
||||
public static Result.Base InvalidProgramId => new Result.Base(ModuleLoader, 9);
|
||||
/// <summary>Error code: 2009-0010; Inner value: 0x1409</summary>
|
||||
public static Result.Base InvalidVersion => new Result.Base(ModuleLoader, 10);
|
||||
/// <summary>Error code: 2009-0051; Inner value: 0x6609</summary>
|
||||
public static Result.Base InsufficientAddressSpace => new Result.Base(ModuleLoader, 51);
|
||||
/// <summary>Error code: 2009-0052; Inner value: 0x6809</summary>
|
||||
public static Result.Base InvalidNro => new Result.Base(ModuleLoader, 52);
|
||||
/// <summary>Error code: 2009-0053; Inner value: 0x6a09</summary>
|
||||
public static Result.Base InvalidNrr => new Result.Base(ModuleLoader, 53);
|
||||
/// <summary>Error code: 2009-0054; Inner value: 0x6c09</summary>
|
||||
public static Result.Base InvalidSignature => new Result.Base(ModuleLoader, 54);
|
||||
/// <summary>Error code: 2009-0055; Inner value: 0x6e09</summary>
|
||||
public static Result.Base InsufficientNroRegistrations => new Result.Base(ModuleLoader, 55);
|
||||
/// <summary>Error code: 2009-0056; Inner value: 0x7009</summary>
|
||||
public static Result.Base InsufficientNrrRegistrations => new Result.Base(ModuleLoader, 56);
|
||||
/// <summary>Error code: 2009-0057; Inner value: 0x7209</summary>
|
||||
public static Result.Base NroAlreadyLoaded => new Result.Base(ModuleLoader, 57);
|
||||
/// <summary>Error code: 2009-0081; Inner value: 0xa209</summary>
|
||||
public static Result.Base InvalidAddress => new Result.Base(ModuleLoader, 81);
|
||||
/// <summary>Error code: 2009-0082; Inner value: 0xa409</summary>
|
||||
public static Result.Base InvalidSize => new Result.Base(ModuleLoader, 82);
|
||||
/// <summary>Error code: 2009-0084; Inner value: 0xa809</summary>
|
||||
public static Result.Base NotLoaded => new Result.Base(ModuleLoader, 84);
|
||||
/// <summary>Error code: 2009-0085; Inner value: 0xaa09</summary>
|
||||
public static Result.Base NotRegistered => new Result.Base(ModuleLoader, 85);
|
||||
/// <summary>Error code: 2009-0086; Inner value: 0xac09</summary>
|
||||
public static Result.Base InvalidSession => new Result.Base(ModuleLoader, 86);
|
||||
/// <summary>Error code: 2009-0087; Inner value: 0xae09</summary>
|
||||
public static Result.Base InvalidProcess => new Result.Base(ModuleLoader, 87);
|
||||
/// <summary>Error code: 2009-0100; Inner value: 0xc809</summary>
|
||||
public static Result.Base UnknownCapability => new Result.Base(ModuleLoader, 100);
|
||||
/// <summary>Error code: 2009-0103; Inner value: 0xce09</summary>
|
||||
public static Result.Base InvalidCapabilityKernelFlags => new Result.Base(ModuleLoader, 103);
|
||||
/// <summary>Error code: 2009-0104; Inner value: 0xd009</summary>
|
||||
public static Result.Base InvalidCapabilitySyscallMask => new Result.Base(ModuleLoader, 104);
|
||||
/// <summary>Error code: 2009-0106; Inner value: 0xd409</summary>
|
||||
public static Result.Base InvalidCapabilityMapRange => new Result.Base(ModuleLoader, 106);
|
||||
/// <summary>Error code: 2009-0107; Inner value: 0xd609</summary>
|
||||
public static Result.Base InvalidCapabilityMapPage => new Result.Base(ModuleLoader, 107);
|
||||
/// <summary>Error code: 2009-0111; Inner value: 0xde09</summary>
|
||||
public static Result.Base InvalidCapabilityInterruptPair => new Result.Base(ModuleLoader, 111);
|
||||
/// <summary>Error code: 2009-0113; Inner value: 0xe209</summary>
|
||||
public static Result.Base InvalidCapabilityApplicationType => new Result.Base(ModuleLoader, 113);
|
||||
/// <summary>Error code: 2009-0114; Inner value: 0xe409</summary>
|
||||
public static Result.Base InvalidCapabilityKernelVersion => new Result.Base(ModuleLoader, 114);
|
||||
/// <summary>Error code: 2009-0115; Inner value: 0xe609</summary>
|
||||
public static Result.Base InvalidCapabilityHandleTable => new Result.Base(ModuleLoader, 115);
|
||||
/// <summary>Error code: 2009-0116; Inner value: 0xe809</summary>
|
||||
public static Result.Base InvalidCapabilityDebugFlags => new Result.Base(ModuleLoader, 116);
|
||||
/// <summary>Error code: 2009-0200; Inner value: 0x19009</summary>
|
||||
public static Result.Base InternalError => new Result.Base(ModuleLoader, 200);
|
||||
}
|
||||
}
|
|
@ -76,5 +76,73 @@ namespace LibHac
|
|||
|
||||
return dec;
|
||||
}
|
||||
|
||||
public static void Decompress(ReadOnlySpan<byte> cmp, Span<byte> dec)
|
||||
{
|
||||
int cmpPos = 0;
|
||||
int decPos = 0;
|
||||
|
||||
// ReSharper disable once VariableHidesOuterVariable
|
||||
int GetLength(int length, ReadOnlySpan<byte> cmp)
|
||||
{
|
||||
byte sum;
|
||||
|
||||
if (length == 0xf)
|
||||
{
|
||||
do
|
||||
{
|
||||
length += sum = cmp[cmpPos++];
|
||||
}
|
||||
while (sum == 0xff);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
byte token = cmp[cmpPos++];
|
||||
|
||||
int encCount = (token >> 0) & 0xf;
|
||||
int litCount = (token >> 4) & 0xf;
|
||||
|
||||
//Copy literal chunk
|
||||
litCount = GetLength(litCount, cmp);
|
||||
|
||||
cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos));
|
||||
|
||||
cmpPos += litCount;
|
||||
decPos += litCount;
|
||||
|
||||
if (cmpPos >= cmp.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//Copy compressed chunk
|
||||
int back = cmp[cmpPos++] << 0 |
|
||||
cmp[cmpPos++] << 8;
|
||||
|
||||
encCount = GetLength(encCount, cmp) + 4;
|
||||
|
||||
int encPos = decPos - back;
|
||||
|
||||
if (encCount <= back)
|
||||
{
|
||||
dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos));
|
||||
|
||||
decPos += encCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (encCount-- > 0)
|
||||
{
|
||||
dec[decPos++] = dec[encPos++];
|
||||
}
|
||||
}
|
||||
}
|
||||
while (cmpPos < cmp.Length &&
|
||||
decPos < dec.Length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ namespace hactoolnet
|
|||
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
||||
new CliOption("outfile", 1, (o, a) => o.OutFile = a[0]),
|
||||
new CliOption("plaintext", 1, (o, a) => o.PlaintextOut = a[0]),
|
||||
new CliOption("uncompressed", 1, (o, a) => o.UncompressedOut = a[0]),
|
||||
new CliOption("nspout", 1, (o, a) => o.NspOut = a[0]),
|
||||
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
|
||||
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
|
||||
|
@ -221,6 +222,8 @@ namespace hactoolnet
|
|||
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
|
||||
sb.AppendLine(" --listromfs List files in RomFS.");
|
||||
sb.AppendLine(" --basenca Set Base NCA to use with update partitions.");
|
||||
sb.AppendLine("KIP1 options:");
|
||||
sb.AppendLine(" --uncompressed <f> Specify file path for saving uncompressed KIP1.");
|
||||
sb.AppendLine("RomFS options:");
|
||||
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
|
||||
sb.AppendLine(" --listromfs List files in RomFS.");
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace hactoolnet
|
|||
public string OutDir;
|
||||
public string OutFile;
|
||||
public string PlaintextOut;
|
||||
public string UncompressedOut;
|
||||
public string SdSeed;
|
||||
public string NspOut;
|
||||
public string SdPath;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.IO;
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
|
||||
namespace hactoolnet
|
||||
{
|
||||
|
@ -8,10 +10,19 @@ namespace hactoolnet
|
|||
{
|
||||
public static void ProcessKip1(Context ctx)
|
||||
{
|
||||
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read))
|
||||
using (var file = new LocalFile(ctx.Options.InFile, OpenMode.Read))
|
||||
{
|
||||
var kip = new Kip(file);
|
||||
kip.OpenRawFile();
|
||||
var kip = new KipReader();
|
||||
kip.Initialize(file).ThrowIfFailure();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ctx.Options.UncompressedOut))
|
||||
{
|
||||
var uncompressed = new byte[kip.GetUncompressedSize()];
|
||||
|
||||
kip.ReadUncompressedKip(uncompressed).ThrowIfFailure();
|
||||
|
||||
File.WriteAllBytes(ctx.Options.UncompressedOut, uncompressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue