diff --git a/LibHac/Keyset.cs b/LibHac/Keyset.cs index 68c256bd..c8c15777 100644 --- a/LibHac/Keyset.cs +++ b/LibHac/Keyset.cs @@ -269,9 +269,10 @@ namespace LibHac public static class ExternalKeys { private const int TitleKeySize = 0x10; - private static readonly Dictionary CommonKeyDict; - private static readonly Dictionary UniqueKeyDict; - private static readonly Dictionary AllKeyDict; + + public static readonly Dictionary CommonKeyDict; + public static readonly Dictionary UniqueKeyDict; + public static readonly Dictionary AllKeyDict; static ExternalKeys() { @@ -393,12 +394,12 @@ namespace LibHac } } - public static string PrintKeys(Keyset keyset) + public static string PrintKeys(Keyset keyset, Dictionary dict) { var sb = new StringBuilder(); - int maxNameLength = CommonKeyDict.Values.Max(x => x.Name.Length); + int maxNameLength = dict.Values.Max(x => x.Name.Length); - foreach (KeyValue keySlot in CommonKeyDict.Values.OrderBy(x => x.Name)) + foreach (KeyValue keySlot in dict.Values.OrderBy(x => x.Name)) { byte[] key = keySlot.GetKey(keyset); if (key.IsEmpty()) continue; @@ -410,6 +411,37 @@ namespace LibHac return sb.ToString(); } + public static string PrintCommonKeys(Keyset keyset) + { + return PrintKeys(keyset, CommonKeyDict); + } + + public static string PrintUniqueKeys(Keyset keyset) + { + return PrintKeys(keyset, UniqueKeyDict); + } + + public static string PrintAllKeys(Keyset keyset) + { + return PrintKeys(keyset, AllKeyDict); + } + + public static string PrintTitleKeys(Keyset keyset) + { + var sb = new StringBuilder(); + int maxNameLength = keyset.TitleKeys.Values.Max(x => x.Length); + + foreach (KeyValuePair kv in keyset.TitleKeys) + { + byte[] key = kv.Key; + byte[] value = kv.Value; + var line = $"{key.ToHexString().PadRight(maxNameLength)} = {value.ToHexString()}"; + sb.AppendLine(line); + } + + return sb.ToString(); + } + private static List CreateCommonKeyList() { var keys = new List @@ -489,7 +521,7 @@ namespace LibHac return keys; } - private class KeyValue + public class KeyValue { public readonly string Name; public readonly int Size; diff --git a/libhac/Lz4.cs b/libhac/Lz4.cs new file mode 100644 index 00000000..cfb49551 --- /dev/null +++ b/libhac/Lz4.cs @@ -0,0 +1,78 @@ +using System; + +namespace Ryujinx.HLE.Loaders.Compression +{ + static class Lz4 + { + public static byte[] Decompress(byte[] Cmp, int DecLength) + { + byte[] Dec = new byte[DecLength]; + + int CmpPos = 0; + int DecPos = 0; + + int GetLength(int Length) + { + 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 chunck + LitCount = GetLength(LitCount); + + Buffer.BlockCopy(Cmp, CmpPos, Dec, DecPos, LitCount); + + CmpPos += LitCount; + DecPos += LitCount; + + if (CmpPos >= Cmp.Length) + { + break; + } + + //Copy compressed chunck + int Back = Cmp[CmpPos++] << 0 | + Cmp[CmpPos++] << 8; + + EncCount = GetLength(EncCount) + 4; + + int EncPos = DecPos - Back; + + if (EncCount <= Back) + { + Buffer.BlockCopy(Dec, EncPos, Dec, DecPos, EncCount); + + DecPos += EncCount; + } + else + { + while (EncCount-- > 0) + { + Dec[DecPos++] = Dec[EncPos++]; + } + } + } + while (CmpPos < Cmp.Length && + DecPos < Dec.Length); + + return Dec; + } + } +} \ No newline at end of file diff --git a/libhac/Nso.cs b/libhac/Nso.cs new file mode 100644 index 00000000..c777c12b --- /dev/null +++ b/libhac/Nso.cs @@ -0,0 +1,114 @@ +using System.Collections; +using System.IO; +using LibHac.Streams; +using Ryujinx.HLE.Loaders.Compression; + +namespace LibHac +{ + public class Nso + { + public NsoSection[] Sections { get; } + public RodataRelativeExtent[] RodataRelativeExtents { get; } + public uint BssSize { get; } + public byte[] BuildId { get; } = new byte[0x20]; + + private SharedStreamSource StreamSource { get; } + + public Nso(Stream stream) + { + StreamSource = new SharedStreamSource(stream); + BinaryReader reader = new BinaryReader(StreamSource.CreateStream()); + if (reader.ReadAscii(4) != "NSO0") + throw new InvalidDataException("NSO magic is incorrect!"); + reader.ReadUInt32(); // Version + reader.ReadUInt32(); // Reserved/Unused + BitArray flags = new BitArray(new[] { (int)reader.ReadUInt32() }); + NsoSection textSection = new NsoSection(StreamSource); + NsoSection rodataSection = new NsoSection(StreamSource); + NsoSection dataSection = new NsoSection(StreamSource); + textSection.IsCompressed = flags[0]; + rodataSection.IsCompressed = flags[1]; + dataSection.IsCompressed = flags[2]; + textSection.CheckHash = flags[3]; + rodataSection.CheckHash = flags[4]; + dataSection.CheckHash = flags[5]; + + textSection.ReadSegmentHeader(reader); + reader.ReadUInt32(); // Module offset (TODO) + rodataSection.ReadSegmentHeader(reader); + reader.ReadUInt32(); // Module file size + dataSection.ReadSegmentHeader(reader); + BssSize = reader.ReadUInt32(); + reader.Read(BuildId, 0, 0x20); + textSection.CompressedSize = reader.ReadUInt32(); + rodataSection.CompressedSize = reader.ReadUInt32(); + dataSection.CompressedSize = reader.ReadUInt32(); + reader.ReadBytes(0x1C); // Padding + RodataRelativeExtents = new[] + { + new RodataRelativeExtent(reader), new RodataRelativeExtent(reader), new RodataRelativeExtent(reader) + }; + + reader.Read(textSection.Hash, 0, 0x20); + reader.Read(rodataSection.Hash, 0, 0x20); + reader.Read(dataSection.Hash, 0, 0x20); + + Sections = new[] { textSection, rodataSection, dataSection }; + reader.Close(); + } + + public class NsoSection + { + private SharedStreamSource StreamSource { get; } + + public bool IsCompressed { get; set; } + public bool CheckHash { get; set; } + public uint FileOffset { get; set; } + public uint MemoryOffset { get; set; } + public uint DecompressedSize { get; set; } + public uint CompressedSize { get; set; } + + public byte[] Hash { get; } = new byte[0x20]; + + public NsoSection(SharedStreamSource streamSource) + { + StreamSource = streamSource; + } + + public Stream OpenSection() + { + return StreamSource.CreateStream(FileOffset, CompressedSize); + } + + public byte[] DecompressSection() + { + byte[] compressed = new byte[CompressedSize]; + OpenSection().Read(compressed, 0, (int)CompressedSize); + + if (IsCompressed) + return Lz4.Decompress(compressed, (int)DecompressedSize); + else + return compressed; + } + + internal void ReadSegmentHeader(BinaryReader reader) + { + FileOffset = reader.ReadUInt32(); + MemoryOffset = reader.ReadUInt32(); + DecompressedSize = reader.ReadUInt32(); + } + } + + public class RodataRelativeExtent + { + public uint RegionRodataOffset { get; } + public uint RegionSize { get; } + + public RodataRelativeExtent(BinaryReader reader) + { + RegionRodataOffset = reader.ReadUInt32(); + RegionSize = reader.ReadUInt32(); + } + } + } +}