mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
First attempt at implementing BKTR
This commit is contained in:
parent
e919bcab1b
commit
18bb3d8531
11 changed files with 501 additions and 15 deletions
|
@ -30,6 +30,7 @@ namespace hactoolnet
|
||||||
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
||||||
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
|
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
|
||||||
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
|
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
|
||||||
|
new CliOption("basenca", 1, (o, a) => o.BaseNca = a[0]),
|
||||||
new CliOption("listapps", 0, (o, a) => o.ListApps = true),
|
new CliOption("listapps", 0, (o, a) => o.ListApps = true),
|
||||||
new CliOption("listtitles", 0, (o, a) => o.ListTitles = true),
|
new CliOption("listtitles", 0, (o, a) => o.ListTitles = true),
|
||||||
new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true),
|
new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true),
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace hactoolnet
|
||||||
public string OutDir;
|
public string OutDir;
|
||||||
public string SdSeed;
|
public string SdSeed;
|
||||||
public string SdPath;
|
public string SdPath;
|
||||||
|
public string BaseNca;
|
||||||
public bool ListApps;
|
public bool ListApps;
|
||||||
public bool ListTitles;
|
public bool ListTitles;
|
||||||
public bool ListRomFs;
|
public bool ListRomFs;
|
||||||
|
|
|
@ -52,6 +52,13 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
var nca = new Nca(ctx.Keyset, file, false);
|
var nca = new Nca(ctx.Keyset, file, false);
|
||||||
|
|
||||||
|
if (ctx.Options.BaseNca != null)
|
||||||
|
{
|
||||||
|
var baseFile = new FileStream(ctx.Options.BaseNca, FileMode.Open, FileAccess.Read);
|
||||||
|
var baseNca = new Nca(ctx.Keyset, baseFile, false);
|
||||||
|
nca.SetBaseNca(baseNca);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
if (ctx.Options.SectionOut[i] != null)
|
if (ctx.Options.SectionOut[i] != null)
|
||||||
|
@ -80,6 +87,36 @@ namespace hactoolnet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.Options.RomfsOutDir != null)
|
||||||
|
{
|
||||||
|
NcaSection section = nca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs || x.Type == SectionType.Bktr);
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
{
|
||||||
|
ctx.Logger.LogMessage("NCA has no RomFS section");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.Type == SectionType.Bktr)
|
||||||
|
{
|
||||||
|
if (ctx.Options.BaseNca == null)
|
||||||
|
{
|
||||||
|
ctx.Logger.LogMessage("Cannot save BKTR section without base RomFS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bktr = nca.OpenSection(1, false);
|
||||||
|
var romfs = new Romfs(bktr);
|
||||||
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var romfs = new Romfs(nca.OpenSection(section.SectionNum, false));
|
||||||
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Logger.LogMessage(nca.Dump());
|
ctx.Logger.LogMessage(nca.Dump());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,14 @@ namespace libhac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateCounterSubsection(uint value)
|
||||||
|
{
|
||||||
|
_counter[7] = (byte) value;
|
||||||
|
_counter[6] = (byte) (value >> 8);
|
||||||
|
_counter[5] = (byte) (value >> 16);
|
||||||
|
_counter[4] = (byte) (value >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
private void EncryptCounterThenIncrement()
|
private void EncryptCounterThenIncrement()
|
||||||
{
|
{
|
||||||
_counterEncryptor.TransformBlock(_counter, 0, _counter.Length, _counterEnc, 0);
|
_counterEncryptor.TransformBlock(_counter, 0, _counter.Length, _counterEnc, 0);
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace libhac
|
||||||
private readonly long _counterOffset;
|
private readonly long _counterOffset;
|
||||||
private readonly byte[] _tempBuffer;
|
private readonly byte[] _tempBuffer;
|
||||||
private readonly Aes _aes;
|
private readonly Aes _aes;
|
||||||
private CounterModeCryptoTransform _decryptor;
|
protected CounterModeCryptoTransform Decryptor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new stream
|
/// Creates a new stream
|
||||||
|
@ -83,7 +83,7 @@ namespace libhac
|
||||||
_aes.Key = key;
|
_aes.Key = key;
|
||||||
_aes.Mode = CipherMode.ECB;
|
_aes.Mode = CipherMode.ECB;
|
||||||
_aes.Padding = PaddingMode.None;
|
_aes.Padding = PaddingMode.None;
|
||||||
_decryptor = CreateDecryptor();
|
Decryptor = CreateDecryptor();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ namespace libhac
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
_decryptor?.Dispose();
|
Decryptor?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
|
@ -141,7 +141,7 @@ namespace libhac
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.Position = value;
|
base.Position = value;
|
||||||
_decryptor.UpdateCounter(_counterOffset + base.Position);
|
Decryptor.UpdateCounter(_counterOffset + base.Position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,11 +162,11 @@ namespace libhac
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (_decryptor == null)
|
if (Decryptor == null)
|
||||||
_decryptor = CreateDecryptor();
|
Decryptor = CreateDecryptor();
|
||||||
|
|
||||||
//decrypt the sector
|
//decrypt the sector
|
||||||
var retV = _decryptor.TransformBlock(_tempBuffer, 0, buffer, offset);
|
var retV = Decryptor.TransformBlock(_tempBuffer, 0, buffer, offset);
|
||||||
|
|
||||||
//Console.WriteLine("Decrypting sector {0} == {1} bytes", currentSector, retV);
|
//Console.WriteLine("Decrypting sector {0} == {1} bytes", currentSector, retV);
|
||||||
|
|
||||||
|
|
172
libhac/Bktr.cs
Normal file
172
libhac/Bktr.cs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace libhac
|
||||||
|
{
|
||||||
|
public class Bktr : Stream
|
||||||
|
{
|
||||||
|
private long _position;
|
||||||
|
public RelocationBlock RelocationBlock { get; }
|
||||||
|
private List<RelocationEntry> RelocationEntries { get; } = new List<RelocationEntry>();
|
||||||
|
private List<long> RelocationOffsets { get; }
|
||||||
|
|
||||||
|
private Stream Patch { get; }
|
||||||
|
private Stream Base { get; }
|
||||||
|
private RelocationEntry CurrentEntry { get; set; }
|
||||||
|
|
||||||
|
public Bktr(Stream patchRomfs, Stream baseRomfs, NcaSection section)
|
||||||
|
{
|
||||||
|
if (section.Type != SectionType.Bktr) throw new ArgumentException("Section is not of type BKTR");
|
||||||
|
|
||||||
|
Patch = patchRomfs;
|
||||||
|
Base = baseRomfs;
|
||||||
|
IvfcLevelHeader level5 = section.Header.Bktr.IvfcHeader.LevelHeaders[5];
|
||||||
|
Length = level5.LogicalOffset + level5.HashDataSize;
|
||||||
|
|
||||||
|
using (var reader = new BinaryReader(patchRomfs, Encoding.Default, true))
|
||||||
|
{
|
||||||
|
patchRomfs.Position = section.Header.Bktr.RelocationHeader.Offset;
|
||||||
|
RelocationBlock = new RelocationBlock(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (RelocationBucket bucket in RelocationBlock.Buckets)
|
||||||
|
{
|
||||||
|
RelocationEntries.AddRange(bucket.Entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < RelocationEntries.Count - 1; i++)
|
||||||
|
{
|
||||||
|
RelocationEntries[i].Next = RelocationEntries[i + 1];
|
||||||
|
RelocationEntries[i].VirtOffsetEnd = RelocationEntries[i + 1].VirtOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
RelocationEntries[RelocationEntries.Count - 1].VirtOffsetEnd = level5.LogicalOffset + level5.HashDataSize;
|
||||||
|
RelocationOffsets = RelocationEntries.Select(x => x.VirtOffset).ToList();
|
||||||
|
|
||||||
|
CurrentEntry = GetRelocationEntry(0);
|
||||||
|
UpdateSourceStreamPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RelocationEntry GetRelocationEntry(long offset)
|
||||||
|
{
|
||||||
|
var index = RelocationOffsets.BinarySearch(offset);
|
||||||
|
if (index < 0) index = ~index - 1;
|
||||||
|
return RelocationEntries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
long remaining = Length - Position;
|
||||||
|
if (remaining <= 0) return 0;
|
||||||
|
if (remaining < count) count = (int)remaining;
|
||||||
|
|
||||||
|
var toOutput = count;
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
while (toOutput > 0)
|
||||||
|
{
|
||||||
|
var remainInEntry = CurrentEntry.VirtOffsetEnd - Position;
|
||||||
|
int toRead = (int)Math.Min(toOutput, remainInEntry);
|
||||||
|
ReadCurrent(buffer, pos, toRead);
|
||||||
|
pos += toRead;
|
||||||
|
toOutput -= toRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadCurrent(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (CurrentEntry.IsPatch)
|
||||||
|
{
|
||||||
|
Patch.Read(buffer, offset, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Base.Read(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
Position += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSourceStreamPositions()
|
||||||
|
{
|
||||||
|
// At end of virtual stream
|
||||||
|
if (CurrentEntry == null) return;
|
||||||
|
|
||||||
|
var entryOffset = Position - CurrentEntry.VirtOffset;
|
||||||
|
|
||||||
|
if (CurrentEntry.IsPatch)
|
||||||
|
{
|
||||||
|
Patch.Position = CurrentEntry.PhysOffset + entryOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Base.Position = CurrentEntry.PhysOffset + entryOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => _position;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value > Length) throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
// Avoid doing a search when reading sequentially
|
||||||
|
if (CurrentEntry != null && value == CurrentEntry.VirtOffsetEnd)
|
||||||
|
{
|
||||||
|
CurrentEntry = CurrentEntry.Next;
|
||||||
|
}
|
||||||
|
else if (CurrentEntry == null || value < CurrentEntry.VirtOffset || value > CurrentEntry.VirtOffsetEnd)
|
||||||
|
{
|
||||||
|
CurrentEntry = GetRelocationEntry(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_position = value;
|
||||||
|
UpdateSourceStreamPositions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
switch (origin)
|
||||||
|
{
|
||||||
|
case SeekOrigin.Begin:
|
||||||
|
Position = offset;
|
||||||
|
break;
|
||||||
|
case SeekOrigin.Current:
|
||||||
|
Position += offset;
|
||||||
|
break;
|
||||||
|
case SeekOrigin.End:
|
||||||
|
Position = Length - offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => true;
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
public override long Length { get; }
|
||||||
|
public override bool CanSeek => true;
|
||||||
|
}
|
||||||
|
}
|
93
libhac/BktrCryptoStream.cs
Normal file
93
libhac/BktrCryptoStream.cs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using libhac.XTSSharp;
|
||||||
|
|
||||||
|
namespace libhac
|
||||||
|
{
|
||||||
|
public class BktrCryptoStream : AesCtrStream
|
||||||
|
{
|
||||||
|
public SubsectionBlock SubsectionBlock { get; }
|
||||||
|
private List<SubsectionEntry> SubsectionEntries { get; } = new List<SubsectionEntry>();
|
||||||
|
private List<long> SubsectionOffsets { get; }
|
||||||
|
private SubsectionEntry CurrentEntry { get; set; }
|
||||||
|
|
||||||
|
public BktrCryptoStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset, byte[] ctrHi, NcaSection section)
|
||||||
|
: base(baseStream, key, offset, length, counterOffset, ctrHi)
|
||||||
|
{
|
||||||
|
if (section.Type != SectionType.Bktr) throw new ArgumentException("Section is not of type BKTR");
|
||||||
|
|
||||||
|
var bktr = section.Header.Bktr;
|
||||||
|
var header = bktr.SubsectionHeader;
|
||||||
|
byte[] subsectionBytes;
|
||||||
|
using (var streamDec = new RandomAccessSectorStream(new AesCtrStream(baseStream, key, offset, length, counterOffset, ctrHi)))
|
||||||
|
{
|
||||||
|
streamDec.Position = header.Offset;
|
||||||
|
subsectionBytes = new byte[header.Size];
|
||||||
|
streamDec.Read(subsectionBytes, 0, subsectionBytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var reader = new BinaryReader(new MemoryStream(subsectionBytes)))
|
||||||
|
{
|
||||||
|
SubsectionBlock = new SubsectionBlock(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var bucket in SubsectionBlock.Buckets)
|
||||||
|
{
|
||||||
|
SubsectionEntries.AddRange(bucket.Entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a subsection for the BKTR headers to make things easier
|
||||||
|
var headerSubsection = new SubsectionEntry
|
||||||
|
{
|
||||||
|
Offset = bktr.RelocationHeader.Offset,
|
||||||
|
Counter = (uint)(ctrHi[4] << 24 | ctrHi[5] << 16 | ctrHi[6] << 8 | ctrHi[7]),
|
||||||
|
OffsetEnd = long.MaxValue
|
||||||
|
};
|
||||||
|
SubsectionEntries.Add(headerSubsection);
|
||||||
|
|
||||||
|
for (int i = 0; i < SubsectionEntries.Count - 1; i++)
|
||||||
|
{
|
||||||
|
SubsectionEntries[i].Next = SubsectionEntries[i + 1];
|
||||||
|
SubsectionEntries[i].OffsetEnd = SubsectionEntries[i + 1].Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubsectionOffsets = SubsectionEntries.Select(x => x.Offset).ToList();
|
||||||
|
|
||||||
|
CurrentEntry = GetSubsectionEntry(0);
|
||||||
|
Decryptor.UpdateCounterSubsection(CurrentEntry.Counter);
|
||||||
|
baseStream.Position = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => base.Position;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Position = value;
|
||||||
|
CurrentEntry = GetSubsectionEntry(value);
|
||||||
|
Decryptor.UpdateCounterSubsection(CurrentEntry.Counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
var ret = base.Read(buffer, offset, count);
|
||||||
|
if (Position >= CurrentEntry.OffsetEnd)
|
||||||
|
{
|
||||||
|
CurrentEntry = CurrentEntry.Next;
|
||||||
|
Decryptor.UpdateCounterSubsection(CurrentEntry.Counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubsectionEntry GetSubsectionEntry(long offset)
|
||||||
|
{
|
||||||
|
var index = SubsectionOffsets.BinarySearch(offset);
|
||||||
|
if (index < 0) index = ~index - 1;
|
||||||
|
return SubsectionEntries[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
152
libhac/BktrStructs.cs
Normal file
152
libhac/BktrStructs.cs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace libhac
|
||||||
|
{
|
||||||
|
public class RelocationBlock
|
||||||
|
{
|
||||||
|
public uint Field0;
|
||||||
|
public int BucketCount;
|
||||||
|
public long Size;
|
||||||
|
public long[] BaseOffsets;
|
||||||
|
public RelocationBucket[] Buckets;
|
||||||
|
|
||||||
|
public RelocationBlock(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var start = reader.BaseStream.Position;
|
||||||
|
|
||||||
|
Field0 = reader.ReadUInt32();
|
||||||
|
BucketCount = reader.ReadInt32();
|
||||||
|
Size = reader.ReadInt64();
|
||||||
|
BaseOffsets = new long[BucketCount];
|
||||||
|
Buckets = new RelocationBucket[BucketCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < BucketCount; i++)
|
||||||
|
{
|
||||||
|
BaseOffsets[i] = reader.ReadInt64();
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.BaseStream.Position = start + 0x4000;
|
||||||
|
|
||||||
|
for (int i = 0; i < BucketCount; i++)
|
||||||
|
{
|
||||||
|
Buckets[i] = new RelocationBucket(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RelocationBucket
|
||||||
|
{
|
||||||
|
public int BucketNum;
|
||||||
|
public int EntryCount;
|
||||||
|
public long VirtualOffsetEnd;
|
||||||
|
public RelocationEntry[] Entries;
|
||||||
|
|
||||||
|
public RelocationBucket(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var start = reader.BaseStream.Position;
|
||||||
|
|
||||||
|
BucketNum = reader.ReadInt32();
|
||||||
|
EntryCount = reader.ReadInt32();
|
||||||
|
VirtualOffsetEnd = reader.ReadInt64();
|
||||||
|
Entries = new RelocationEntry[EntryCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < EntryCount; i++)
|
||||||
|
{
|
||||||
|
Entries[i] = new RelocationEntry(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.BaseStream.Position = start + 0x4000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RelocationEntry
|
||||||
|
{
|
||||||
|
public long VirtOffset;
|
||||||
|
public long VirtOffsetEnd;
|
||||||
|
public long PhysOffset;
|
||||||
|
public bool IsPatch;
|
||||||
|
public RelocationEntry Next;
|
||||||
|
|
||||||
|
public RelocationEntry(BinaryReader reader)
|
||||||
|
{
|
||||||
|
VirtOffset = reader.ReadInt64();
|
||||||
|
PhysOffset = reader.ReadInt64();
|
||||||
|
IsPatch = reader.ReadInt32() != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubsectionBlock
|
||||||
|
{
|
||||||
|
public uint Field0;
|
||||||
|
public int BucketCount;
|
||||||
|
public long Size;
|
||||||
|
public long[] BaseOffsets;
|
||||||
|
public SubsectionBucket[] Buckets;
|
||||||
|
|
||||||
|
public SubsectionBlock(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var start = reader.BaseStream.Position;
|
||||||
|
|
||||||
|
Field0 = reader.ReadUInt32();
|
||||||
|
BucketCount = reader.ReadInt32();
|
||||||
|
Size = reader.ReadInt64();
|
||||||
|
BaseOffsets = new long[BucketCount];
|
||||||
|
Buckets = new SubsectionBucket[BucketCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < BucketCount; i++)
|
||||||
|
{
|
||||||
|
BaseOffsets[i] = reader.ReadInt64();
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.BaseStream.Position = start + 0x4000;
|
||||||
|
|
||||||
|
for (int i = 0; i < BucketCount; i++)
|
||||||
|
{
|
||||||
|
Buckets[i] = new SubsectionBucket(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubsectionBucket
|
||||||
|
{
|
||||||
|
public int BucketNum;
|
||||||
|
public int EntryCount;
|
||||||
|
public long VirtualOffsetEnd;
|
||||||
|
public SubsectionEntry[] Entries;
|
||||||
|
public SubsectionBucket(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var start = reader.BaseStream.Position;
|
||||||
|
|
||||||
|
BucketNum = reader.ReadInt32();
|
||||||
|
EntryCount = reader.ReadInt32();
|
||||||
|
VirtualOffsetEnd = reader.ReadInt64();
|
||||||
|
Entries = new SubsectionEntry[EntryCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < EntryCount; i++)
|
||||||
|
{
|
||||||
|
Entries[i] = new SubsectionEntry(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.BaseStream.Position = start + 0x4000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubsectionEntry
|
||||||
|
{
|
||||||
|
public long Offset;
|
||||||
|
public uint Field8;
|
||||||
|
public uint Counter;
|
||||||
|
|
||||||
|
public SubsectionEntry Next;
|
||||||
|
public long OffsetEnd;
|
||||||
|
|
||||||
|
public SubsectionEntry() { }
|
||||||
|
|
||||||
|
public SubsectionEntry(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Offset = reader.ReadInt64();
|
||||||
|
Field8 = reader.ReadUInt32();
|
||||||
|
Counter = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using libhac.XTSSharp;
|
using libhac.XTSSharp;
|
||||||
|
@ -18,6 +19,7 @@ namespace libhac
|
||||||
public byte[] TitleKeyDec { get; } = new byte[0x10];
|
public byte[] TitleKeyDec { get; } = new byte[0x10];
|
||||||
public Stream Stream { get; private set; }
|
public Stream Stream { get; private set; }
|
||||||
private bool KeepOpen { get; }
|
private bool KeepOpen { get; }
|
||||||
|
private Nca BaseNca { get; set; }
|
||||||
|
|
||||||
public NcaSection[] Sections { get; } = new NcaSection[4];
|
public NcaSection[] Sections { get; } = new NcaSection[4];
|
||||||
|
|
||||||
|
@ -77,9 +79,6 @@ namespace libhac
|
||||||
size = sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
|
size = sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
|
||||||
break;
|
break;
|
||||||
case SectionType.Bktr:
|
case SectionType.Bktr:
|
||||||
offset = sect.Offset + sect.Header.Bktr.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1]
|
|
||||||
.LogicalOffset;
|
|
||||||
size = sect.Header.Bktr.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
|
@ -97,7 +96,27 @@ namespace libhac
|
||||||
case SectionCryptType.CTR:
|
case SectionCryptType.CTR:
|
||||||
return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
|
return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
|
||||||
case SectionCryptType.BKTR:
|
case SectionCryptType.BKTR:
|
||||||
return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
|
var patchStream = new RandomAccessSectorStream(
|
||||||
|
new BktrCryptoStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr, sect),
|
||||||
|
false);
|
||||||
|
if (BaseNca == null)
|
||||||
|
{
|
||||||
|
return patchStream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dataLevel = sect.Header.Bktr.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1];
|
||||||
|
|
||||||
|
var baseSect = BaseNca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs);
|
||||||
|
if (baseSect == null) throw new InvalidDataException("Base NCA has no RomFS section");
|
||||||
|
|
||||||
|
var baseStream = BaseNca.OpenSection(baseSect.SectionNum, true);
|
||||||
|
var virtStreamRaw = new Bktr(patchStream, baseStream, sect);
|
||||||
|
|
||||||
|
if (raw) return virtStreamRaw;
|
||||||
|
var virtStream = new SubStream(virtStreamRaw, dataLevel.LogicalOffset, dataLevel.HashDataSize);
|
||||||
|
return virtStream;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
@ -105,6 +124,8 @@ namespace libhac
|
||||||
return new SubStream(Stream, offset, size);
|
return new SubStream(Stream, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetBaseNca(Nca baseNca) => BaseNca = baseNca;
|
||||||
|
|
||||||
private void DecryptHeader(Keyset keyset, Stream stream)
|
private void DecryptHeader(Keyset keyset, Stream stream)
|
||||||
{
|
{
|
||||||
byte[] headerBytes = new byte[0xC00];
|
byte[] headerBytes = new byte[0xC00];
|
||||||
|
|
|
@ -204,8 +204,8 @@ namespace libhac
|
||||||
|
|
||||||
public class BktrHeader
|
public class BktrHeader
|
||||||
{
|
{
|
||||||
public ulong Offset;
|
public long Offset;
|
||||||
public ulong Size;
|
public long Size;
|
||||||
public uint Magic;
|
public uint Magic;
|
||||||
public uint Field14;
|
public uint Field14;
|
||||||
public uint NumEntries;
|
public uint NumEntries;
|
||||||
|
@ -213,8 +213,8 @@ namespace libhac
|
||||||
|
|
||||||
public BktrHeader(BinaryReader reader)
|
public BktrHeader(BinaryReader reader)
|
||||||
{
|
{
|
||||||
Offset = reader.ReadUInt64();
|
Offset = reader.ReadInt64();
|
||||||
Size = reader.ReadUInt64();
|
Size = reader.ReadInt64();
|
||||||
Magic = reader.ReadUInt32();
|
Magic = reader.ReadUInt32();
|
||||||
Field14 = reader.ReadUInt32();
|
Field14 = reader.ReadUInt32();
|
||||||
NumEntries = reader.ReadUInt32();
|
NumEntries = reader.ReadUInt32();
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace libhac
|
||||||
|
|
||||||
baseStream.Seek(offset, SeekOrigin.Begin);
|
baseStream.Seek(offset, SeekOrigin.Begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
long remaining = Length - Position;
|
long remaining = Length - Position;
|
||||||
|
|
Loading…
Reference in a new issue