LibHac/LibHac/Nso.cs
Alex Barney 3d50085e22
Use Storage throughout the library instead of Stream (#18)
*Create an IStorage interface and Storage abstract class to use instead of Stream

* Improve AES-XTS performance by ~16x

* Double AES-CTR performance: 800 MB/s -> 1600 MB/s on a 6700K

* Add AES-XTS tests

* Add AES benchmark and AES-CTR writing

* Add support for a hashed FAT in save files

* Add option to export decrypted NCA

* Allow opening decrypted package1 and package2

* Make sure romfs disposal can cascade all the way down

* Validate NCA, NPDM and package2 signatures
2018-11-18 23:20:34 -05:00

114 lines
3.9 KiB
C#

using System.Collections;
using System.IO;
using LibHac.IO;
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 IStorage Storage { get; }
public Nso(IStorage storage)
{
Storage = storage;
var reader = new BinaryReader(Storage.AsStream());
if (reader.ReadAscii(4) != "NSO0")
throw new InvalidDataException("NSO magic is incorrect!");
reader.ReadUInt32(); // Version
reader.ReadUInt32(); // Reserved/Unused
var flags = new BitArray(new[] { (int)reader.ReadUInt32() });
var textSection = new NsoSection(Storage);
var rodataSection = new NsoSection(Storage);
var dataSection = new NsoSection(Storage);
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 IStorage Storage { 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(IStorage storage)
{
Storage = storage;
}
public IStorage OpenSection()
{
return Storage.Slice(FileOffset, CompressedSize);
}
public byte[] DecompressSection()
{
var compressed = new byte[CompressedSize];
OpenSection().Read(compressed, 0);
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();
}
}
}
}