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); + } } }