diff --git a/build/CodeGen/result_modules.csv b/build/CodeGen/result_modules.csv
index ffd4d6e4..3edc8e78 100644
--- a/build/CodeGen/result_modules.csv
+++ b/build/CodeGen/result_modules.csv
@@ -1,4 +1,6 @@
Name,Index
Fs,2
+Loader,9
Kvdb,20
-Sdmmc,24
\ No newline at end of file
+Sdmmc,24
+LibHac,428
\ No newline at end of file
diff --git a/build/CodeGen/result_paths.csv b/build/CodeGen/result_paths.csv
index cca028c2..d29d8f20 100644
--- a/build/CodeGen/result_paths.csv
+++ b/build/CodeGen/result_paths.csv
@@ -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
\ No newline at end of file
+Sdmmc,LibHac.FsService,LibHac/FsService/ResultSdmmc.cs
+LibHac,LibHac.Common,LibHac/Common/ResultLibHac.cs
\ No newline at end of file
diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv
index 64ad0bfb..10e9eeb6 100644
--- a/build/CodeGen/results.csv
+++ b/build/CodeGen/results.csv
@@ -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,
\ No newline at end of file
+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.
diff --git a/src/LibHac/Common/ResultLibHac.cs b/src/LibHac/Common/ResultLibHac.cs
new file mode 100644
index 00000000..ddb27727
--- /dev/null
+++ b/src/LibHac/Common/ResultLibHac.cs
@@ -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;
+
+ /// Error code: 2428-0001; Range: 1-49; Inner value: 0x3ac
+ public static Result.Base InvalidArgument => new Result.Base(ModuleLibHac, 1, 49);
+ /// Error code: 2428-0002; Inner value: 0x5ac
+ public static Result.Base NullArgument => new Result.Base(ModuleLibHac, 2);
+ /// Error code: 2428-0003; Inner value: 0x7ac
+ public static Result.Base ArgumentOutOfRange => new Result.Base(ModuleLibHac, 3);
+ /// Error code: 2428-0004; Inner value: 0x9ac
+ public static Result.Base BufferTooSmall => new Result.Base(ModuleLibHac, 4);
+
+ /// Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac
+ public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); }
+ /// Error code: 2428-1001; Range: 1001-1019; Inner value: 0x7d3ac
+ public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1001, 1019); }
+ /// The size of the KIP file was smaller than expected.
Error code: 2428-1002; Inner value: 0x7d5ac
+ public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1002);
+ /// The magic value of the KIP file was not KIP1.
Error code: 2428-1003; Inner value: 0x7d7ac
+ public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1003);
+ /// The size of the compressed KIP segment was smaller than expected.
Error code: 2428-1004; Inner value: 0x7d9ac
+ public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1004);
+ /// An error occurred while decompressing a KIP segment.
Error code: 2428-1005; Inner value: 0x7dbac
+ public static Result.Base KipSegmentDecompressionFailed => new Result.Base(ModuleLibHac, 1005);
+ }
+}
diff --git a/src/LibHac/Kip.cs b/src/LibHac/Kip.cs
index 839bc544..ceaf5f55 100644
--- a/src/LibHac/Kip.cs
+++ b/src/LibHac/Kip.cs
@@ -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;
diff --git a/src/LibHac/Loader/KipHeader.cs b/src/LibHac/Loader/KipHeader.cs
new file mode 100644
index 00000000..580b6471
--- /dev/null
+++ b/src/LibHac/Loader/KipHeader.cs
@@ -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 Name => SpanHelpers.CreateSpan(ref _name, NameSize);
+
+ public Span Segments =>
+ SpanHelpers.CreateSpan(ref Unsafe.As(ref TextMemoryOffset), SegmentCount);
+
+ public Span 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;
+ }
+ }
+}
diff --git a/src/LibHac/Loader/KipReader.cs b/src/LibHac/Loader/KipReader.cs
new file mode 100644
index 00000000..649d7f00
--- /dev/null
+++ b/src/LibHac/Loader/KipReader.cs
@@ -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 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 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())
+ 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();
+
+ 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 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 buffer)
+ {
+ if (buffer.Length < GetUncompressedSize())
+ return ResultLibHac.BufferTooSmall.Log();
+
+ Span segmentBuffer = buffer.Slice(Unsafe.SizeOf());
+
+ // 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(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();
+ ReadOnlySpan segments = Segments;
+
+ for (int i = 0; i < index; i++)
+ {
+ offset += segments[i].FileSize;
+ }
+
+ return offset;
+ }
+
+ private static Result DecompressBlz(Span buffer, int compressedDataSize)
+ {
+ const int segmentFooterSize = 12;
+
+ if (buffer.Length < segmentFooterSize)
+ return ResultLibHac.InvalidKipSegmentSize.Log();
+
+ // Parse the footer, endian agnostic.
+ Span 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 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
+ }
+ }
+}
diff --git a/src/LibHac/Loader/NsoHeader.cs b/src/LibHac/Loader/NsoHeader.cs
new file mode 100644
index 00000000..7e9125bb
--- /dev/null
+++ b/src/LibHac/Loader/NsoHeader.cs
@@ -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 Segments =>
+ SpanHelpers.CreateSpan(ref Unsafe.As(ref TextFileOffset), SegmentCount);
+
+ public Span CompressedSizes => SpanHelpers.CreateSpan(ref TextFileSize, SegmentCount);
+
+ public Span SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount);
+
+ public Span 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;
+ }
+ }
+}
diff --git a/src/LibHac/Loader/NsoReader.cs b/src/LibHac/Loader/NsoReader.cs
new file mode 100644
index 00000000..a761d766
--- /dev/null
+++ b/src/LibHac/Loader/NsoReader.cs
@@ -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())
+ 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 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 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
+ }
+ }
+}
diff --git a/src/LibHac/Loader/ResultLoader.cs b/src/LibHac/Loader/ResultLoader.cs
new file mode 100644
index 00000000..1fe998d5
--- /dev/null
+++ b/src/LibHac/Loader/ResultLoader.cs
@@ -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;
+
+ /// Error code: 2009-0001; Inner value: 0x209
+ public static Result.Base TooLongArgument => new Result.Base(ModuleLoader, 1);
+ /// Error code: 2009-0002; Inner value: 0x409
+ public static Result.Base TooManyArguments => new Result.Base(ModuleLoader, 2);
+ /// Error code: 2009-0003; Inner value: 0x609
+ public static Result.Base TooLargeMeta => new Result.Base(ModuleLoader, 3);
+ /// Error code: 2009-0004; Inner value: 0x809
+ public static Result.Base InvalidMeta => new Result.Base(ModuleLoader, 4);
+ /// Error code: 2009-0005; Inner value: 0xa09
+ public static Result.Base InvalidNso => new Result.Base(ModuleLoader, 5);
+ /// Error code: 2009-0006; Inner value: 0xc09
+ public static Result.Base InvalidPath => new Result.Base(ModuleLoader, 6);
+ /// Error code: 2009-0007; Inner value: 0xe09
+ public static Result.Base TooManyProcesses => new Result.Base(ModuleLoader, 7);
+ /// Error code: 2009-0008; Inner value: 0x1009
+ public static Result.Base NotPinned => new Result.Base(ModuleLoader, 8);
+ /// Error code: 2009-0009; Inner value: 0x1209
+ public static Result.Base InvalidProgramId => new Result.Base(ModuleLoader, 9);
+ /// Error code: 2009-0010; Inner value: 0x1409
+ public static Result.Base InvalidVersion => new Result.Base(ModuleLoader, 10);
+ /// Error code: 2009-0051; Inner value: 0x6609
+ public static Result.Base InsufficientAddressSpace => new Result.Base(ModuleLoader, 51);
+ /// Error code: 2009-0052; Inner value: 0x6809
+ public static Result.Base InvalidNro => new Result.Base(ModuleLoader, 52);
+ /// Error code: 2009-0053; Inner value: 0x6a09
+ public static Result.Base InvalidNrr => new Result.Base(ModuleLoader, 53);
+ /// Error code: 2009-0054; Inner value: 0x6c09
+ public static Result.Base InvalidSignature => new Result.Base(ModuleLoader, 54);
+ /// Error code: 2009-0055; Inner value: 0x6e09
+ public static Result.Base InsufficientNroRegistrations => new Result.Base(ModuleLoader, 55);
+ /// Error code: 2009-0056; Inner value: 0x7009
+ public static Result.Base InsufficientNrrRegistrations => new Result.Base(ModuleLoader, 56);
+ /// Error code: 2009-0057; Inner value: 0x7209
+ public static Result.Base NroAlreadyLoaded => new Result.Base(ModuleLoader, 57);
+ /// Error code: 2009-0081; Inner value: 0xa209
+ public static Result.Base InvalidAddress => new Result.Base(ModuleLoader, 81);
+ /// Error code: 2009-0082; Inner value: 0xa409
+ public static Result.Base InvalidSize => new Result.Base(ModuleLoader, 82);
+ /// Error code: 2009-0084; Inner value: 0xa809
+ public static Result.Base NotLoaded => new Result.Base(ModuleLoader, 84);
+ /// Error code: 2009-0085; Inner value: 0xaa09
+ public static Result.Base NotRegistered => new Result.Base(ModuleLoader, 85);
+ /// Error code: 2009-0086; Inner value: 0xac09
+ public static Result.Base InvalidSession => new Result.Base(ModuleLoader, 86);
+ /// Error code: 2009-0087; Inner value: 0xae09
+ public static Result.Base InvalidProcess => new Result.Base(ModuleLoader, 87);
+ /// Error code: 2009-0100; Inner value: 0xc809
+ public static Result.Base UnknownCapability => new Result.Base(ModuleLoader, 100);
+ /// Error code: 2009-0103; Inner value: 0xce09
+ public static Result.Base InvalidCapabilityKernelFlags => new Result.Base(ModuleLoader, 103);
+ /// Error code: 2009-0104; Inner value: 0xd009
+ public static Result.Base InvalidCapabilitySyscallMask => new Result.Base(ModuleLoader, 104);
+ /// Error code: 2009-0106; Inner value: 0xd409
+ public static Result.Base InvalidCapabilityMapRange => new Result.Base(ModuleLoader, 106);
+ /// Error code: 2009-0107; Inner value: 0xd609
+ public static Result.Base InvalidCapabilityMapPage => new Result.Base(ModuleLoader, 107);
+ /// Error code: 2009-0111; Inner value: 0xde09
+ public static Result.Base InvalidCapabilityInterruptPair => new Result.Base(ModuleLoader, 111);
+ /// Error code: 2009-0113; Inner value: 0xe209
+ public static Result.Base InvalidCapabilityApplicationType => new Result.Base(ModuleLoader, 113);
+ /// Error code: 2009-0114; Inner value: 0xe409
+ public static Result.Base InvalidCapabilityKernelVersion => new Result.Base(ModuleLoader, 114);
+ /// Error code: 2009-0115; Inner value: 0xe609
+ public static Result.Base InvalidCapabilityHandleTable => new Result.Base(ModuleLoader, 115);
+ /// Error code: 2009-0116; Inner value: 0xe809
+ public static Result.Base InvalidCapabilityDebugFlags => new Result.Base(ModuleLoader, 116);
+ /// Error code: 2009-0200; Inner value: 0x19009
+ public static Result.Base InternalError => new Result.Base(ModuleLoader, 200);
+ }
+}
diff --git a/src/LibHac/Lz4.cs b/src/LibHac/Lz4.cs
index 06620347..0ac165aa 100644
--- a/src/LibHac/Lz4.cs
+++ b/src/LibHac/Lz4.cs
@@ -76,5 +76,73 @@ namespace LibHac
return dec;
}
+
+ public static void Decompress(ReadOnlySpan cmp, Span dec)
+ {
+ int cmpPos = 0;
+ int decPos = 0;
+
+ // ReSharper disable once VariableHidesOuterVariable
+ int GetLength(int length, ReadOnlySpan 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);
+ }
}
}
\ No newline at end of file
diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs
index 386aa032..e0c4f28a 100644
--- a/src/hactoolnet/CliParser.cs
+++ b/src/hactoolnet/CliParser.cs
@@ -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 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 Specify file path for saving uncompressed KIP1.");
sb.AppendLine("RomFS options:");
sb.AppendLine(" --romfsdir Specify RomFS directory path.");
sb.AppendLine(" --listromfs List files in RomFS.");
diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs
index 152a3c80..4a633aab 100644
--- a/src/hactoolnet/Options.cs
+++ b/src/hactoolnet/Options.cs
@@ -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;
diff --git a/src/hactoolnet/ProcessKip.cs b/src/hactoolnet/ProcessKip.cs
index 1cbfacbc..1b36225c 100644
--- a/src/hactoolnet/ProcessKip.cs
+++ b/src/hactoolnet/ProcessKip.cs
@@ -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);
+ }
}
}