From 8b8aa3277f978a5f489ec8dce5a41c01b062c9a3 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 17 Sep 2018 19:18:28 -0500 Subject: [PATCH] Read KIP1 files --- LibHac/Kip.cs | 164 +++++++++++++++++++++++++++++++++++++++ LibHac/Package2.cs | 2 + hactoolnet/Options.cs | 3 +- hactoolnet/ProcessKip.cs | 16 ++++ hactoolnet/Program.cs | 7 +- 5 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 LibHac/Kip.cs create mode 100644 hactoolnet/ProcessKip.cs diff --git a/LibHac/Kip.cs b/LibHac/Kip.cs new file mode 100644 index 00000000..9b06b3d7 --- /dev/null +++ b/LibHac/Kip.cs @@ -0,0 +1,164 @@ +using System; +using System.IO; +using LibHac.Streams; + +namespace LibHac +{ + public class Kip + { + private const int HeaderSize = 0x100; + + public KipHeader Header { get; } + + public int[] SectionOffsets { get; } = new int[6]; + public int Size { get; } + + private SharedStreamSource StreamSource { get; } + + public Kip(Stream stream) + { + StreamSource = new SharedStreamSource(stream); + Header = new KipHeader(StreamSource.CreateStream()); + + Size = HeaderSize; + + for (int index = 0; index < Header.Sections.Length; index++) + { + int sectionSize = Header.Sections[index].CompressedSize; + SectionOffsets[index] = Size; + Size += sectionSize; + } + } + + public Stream OpenSection(int index) + { + if (index < 0 || index > 5) + { + throw new ArgumentOutOfRangeException(nameof(index), "Section index must be between 0-5"); + } + + return StreamSource.CreateStream(SectionOffsets[index], Header.Sections[index].CompressedSize); + } + + public byte[] DecompressSection(int index) + { + Stream compStream = OpenSection(index); + var compressed = new byte[compStream.Length]; + compStream.Read(compressed, 0, compressed.Length); + + return DecompressBlz(compressed); + } + + private static byte[] DecompressBlz(byte[] compressed) + { + int additionalSize = BitConverter.ToInt32(compressed, compressed.Length - 4); + int headerSize = BitConverter.ToInt32(compressed, compressed.Length - 8); + int totalCompSize = BitConverter.ToInt32(compressed, compressed.Length - 12); + + var decompressed = new byte[totalCompSize + additionalSize]; + + int inOffset = totalCompSize - headerSize; + int outOffset = totalCompSize + additionalSize; + + while (outOffset > 0) + { + byte control = compressed[--inOffset]; + + for (int i = 0; i < 8; i++) + { + if ((control & 0x80) != 0) + { + if (inOffset < 2) throw new InvalidDataException("KIP1 decompression out of bounds!"); + + inOffset -= 2; + + ushort segmentValue = BitConverter.ToUInt16(compressed, inOffset); + int segmentSize = ((segmentValue >> 12) & 0xF) + 3; + int segmentOffset = (segmentValue & 0x0FFF) + 3; + + if (outOffset < segmentSize) + { + // Kernel restricts segment copy to stay in bounds. + segmentSize = outOffset; + } + outOffset -= segmentSize; + + for (int j = 0; j < segmentSize; j++) + { + decompressed[outOffset + j] = decompressed[outOffset + j + segmentOffset]; + } + } + else + { + // Copy directly. + if (inOffset < 1) throw new InvalidDataException("KIP1 decompression out of bounds!"); + + decompressed[--outOffset] = compressed[--inOffset]; + } + control <<= 1; + if (outOffset == 0) return decompressed; + } + } + + return decompressed; + } + } + + public class KipHeader + { + public string Magic { get; } + public string Name { get; } + public ulong TitleId { get; } + public int ProcessCategory { get; } + public byte MainThreadPriority { get; } + public byte DefaultCore { get; } + public byte Field1E { get; } + public byte Flags { get; } + public KipSectionHeader[] Sections { get; } = new KipSectionHeader[6]; + public byte[] Capabilities { get; } + + public KipHeader(Stream stream) + { + var reader = new BinaryReader(stream); + + Magic = reader.ReadAscii(4); + if (Magic != "KIP1") + { + throw new InvalidDataException("Invalid KIP file!"); + } + + Name = reader.ReadAsciiZ(0xC); + + reader.BaseStream.Position = 0x10; + TitleId = reader.ReadUInt64(); + ProcessCategory = reader.ReadInt32(); + MainThreadPriority = reader.ReadByte(); + DefaultCore = reader.ReadByte(); + Field1E = reader.ReadByte(); + Flags = reader.ReadByte(); + + for (int i = 0; i < Sections.Length; i++) + { + Sections[i] = new KipSectionHeader(reader); + } + + Capabilities = reader.ReadBytes(0x20); + } + } + + public class KipSectionHeader + { + public int OutOffset { get; } + public int DecompressedSize { get; } + public int CompressedSize { get; } + public int Attribute { get; } + + public KipSectionHeader(BinaryReader reader) + { + OutOffset = reader.ReadInt32(); + DecompressedSize = reader.ReadInt32(); + CompressedSize = reader.ReadInt32(); + Attribute = reader.ReadInt32(); + } + } +} diff --git a/LibHac/Package2.cs b/LibHac/Package2.cs index 0f6a3c1f..9ed02fb7 100644 --- a/LibHac/Package2.cs +++ b/LibHac/Package2.cs @@ -46,6 +46,8 @@ namespace LibHac { SharedStream encStream = StreamSource.CreateStream(0x110, 0xF0); + // The counter starts counting at 0x100, but the block at 0x100 isn't encrypted. + // Increase the counter by one and start decrypting at 0x110. var counter = new byte[0x10]; Array.Copy(Header.Counter, counter, 0x10); Util.IncrementByteArray(counter); diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs index 7e8ba384..ad791779 100644 --- a/hactoolnet/Options.cs +++ b/hactoolnet/Options.cs @@ -48,7 +48,8 @@ namespace hactoolnet Save, Keygen, Pk11, - Pk21 + Pk21, + Kip1 } internal class Context diff --git a/hactoolnet/ProcessKip.cs b/hactoolnet/ProcessKip.cs new file mode 100644 index 00000000..448851f4 --- /dev/null +++ b/hactoolnet/ProcessKip.cs @@ -0,0 +1,16 @@ +using System.IO; +using LibHac; + +namespace hactoolnet +{ + internal static class ProcessKip + { + public static void ProcessKip1(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var kip = new Kip(file); + } + } + } +} diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 0340636c..e18f30ef 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -56,6 +56,9 @@ namespace hactoolnet case FileType.Pk21: ProcessPackage.ProcessPk21(ctx); break; + case FileType.Kip1: + ProcessKip.ProcessKip1(ctx); + break; default: throw new ArgumentOutOfRangeException(); } @@ -155,8 +158,8 @@ namespace hactoolnet // For running random stuff // ReSharper disable once UnusedParameter.Local private static void CustomTask(Context ctx) - { - + { + } } }