mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add NDV0 support
This commit is contained in:
parent
fba89fbb95
commit
0ed67d87df
7 changed files with 296 additions and 1 deletions
11
src/LibHac/BitTools.cs
Normal file
11
src/LibHac/BitTools.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace LibHac
|
||||
{
|
||||
public static class BitTools
|
||||
{
|
||||
public static int SignExtend32(int value, int bits)
|
||||
{
|
||||
int shift = 8 * sizeof(int) - bits;
|
||||
return (value << shift) >> shift;
|
||||
}
|
||||
}
|
||||
}
|
160
src/LibHac/IO/DeltaFragment.cs
Normal file
160
src/LibHac/IO/DeltaFragment.cs
Normal file
|
@ -0,0 +1,160 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class DeltaFragment
|
||||
{
|
||||
private const string Ndv0Magic = "NDV0";
|
||||
private IStorage Original { get; set; }
|
||||
private IStorage Delta { get; }
|
||||
public DeltaFragmentHeader Header { get; }
|
||||
private List<DeltaFragmentSegment> Segments { get; } = new List<DeltaFragmentSegment>();
|
||||
|
||||
public DeltaFragment(IStorage delta, IStorage originalData) : this(delta)
|
||||
{
|
||||
SetBaseStorage(originalData);
|
||||
}
|
||||
|
||||
public DeltaFragment(IStorage delta)
|
||||
{
|
||||
Delta = delta;
|
||||
|
||||
if (Delta.Length < 0x40) throw new InvalidDataException("Delta file is too small.");
|
||||
|
||||
Header = new DeltaFragmentHeader(new StorageFile(delta, OpenMode.Read));
|
||||
|
||||
if (Header.Magic != Ndv0Magic) throw new InvalidDataException("NDV0 magic value is missing.");
|
||||
|
||||
long fragmentSize = Header.FragmentHeaderSize + Header.FragmentBodySize;
|
||||
if (Delta.Length < fragmentSize)
|
||||
{
|
||||
throw new InvalidDataException($"Delta file is smaller than the header indicates. (0x{fragmentSize} bytes)");
|
||||
}
|
||||
|
||||
ParseDeltaStructure();
|
||||
}
|
||||
|
||||
public void SetBaseStorage(IStorage baseStorage)
|
||||
{
|
||||
Original = baseStorage;
|
||||
|
||||
if (Original.Length != Header.OriginalSize)
|
||||
{
|
||||
throw new InvalidDataException($"Original file size does not match the size in the delta header. (0x{Header.OriginalSize} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
public IStorage GetPatchedStorage()
|
||||
{
|
||||
if (Original == null) throw new InvalidOperationException("Cannot apply a delta patch without a base file.");
|
||||
|
||||
var storages = new List<IStorage>();
|
||||
|
||||
foreach (DeltaFragmentSegment segment in Segments)
|
||||
{
|
||||
IStorage source = segment.IsInOriginal ? Original : Delta;
|
||||
|
||||
// todo Do this without tons of SubStorages
|
||||
Storage sub = source.Slice(segment.SourceOffset, segment.Size);
|
||||
|
||||
storages.Add(sub);
|
||||
}
|
||||
|
||||
return new ConcatenationStorage(storages, true);
|
||||
}
|
||||
|
||||
private void ParseDeltaStructure()
|
||||
{
|
||||
var reader = new FileReader(new StorageFile(Delta, OpenMode.Read));
|
||||
|
||||
reader.Position = Header.FragmentHeaderSize;
|
||||
|
||||
long offset = 0;
|
||||
|
||||
while (offset < Header.NewSize)
|
||||
{
|
||||
ReadSegmentHeader(reader, out int size, out int seek);
|
||||
|
||||
if (seek > 0)
|
||||
{
|
||||
var segment = new DeltaFragmentSegment()
|
||||
{
|
||||
SourceOffset = offset,
|
||||
Size = seek,
|
||||
IsInOriginal = true
|
||||
};
|
||||
|
||||
Segments.Add(segment);
|
||||
offset += seek;
|
||||
}
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
var segment = new DeltaFragmentSegment()
|
||||
{
|
||||
SourceOffset = reader.Position,
|
||||
Size = size,
|
||||
IsInOriginal = false
|
||||
};
|
||||
|
||||
Segments.Add(segment);
|
||||
offset += size;
|
||||
}
|
||||
|
||||
reader.Position += size;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadSegmentHeader(FileReader reader, out int size, out int seek)
|
||||
{
|
||||
byte type = reader.ReadUInt8();
|
||||
|
||||
int seekBytes = (type & 3) + 1;
|
||||
int sizeBytes = ((type >> 3) & 3) + 1;
|
||||
|
||||
size = ReadInt(reader, sizeBytes);
|
||||
seek = ReadInt(reader, seekBytes);
|
||||
}
|
||||
|
||||
private static int ReadInt(FileReader reader, int bytes)
|
||||
{
|
||||
switch (bytes)
|
||||
{
|
||||
case 1: return reader.ReadUInt8();
|
||||
case 2: return reader.ReadUInt16();
|
||||
case 3: return reader.ReadUInt24();
|
||||
case 4: return reader.ReadInt32();
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class DeltaFragmentSegment
|
||||
{
|
||||
public long SourceOffset { get; set; }
|
||||
public int Size { get; set; }
|
||||
public bool IsInOriginal { get; set; }
|
||||
}
|
||||
|
||||
public class DeltaFragmentHeader
|
||||
{
|
||||
public string Magic { get; }
|
||||
public long OriginalSize { get; }
|
||||
public long NewSize { get; }
|
||||
public long FragmentHeaderSize { get; }
|
||||
public long FragmentBodySize { get; }
|
||||
|
||||
public DeltaFragmentHeader(IFile header)
|
||||
{
|
||||
var reader = new FileReader(header);
|
||||
|
||||
Magic = reader.ReadAscii(4);
|
||||
OriginalSize = reader.ReadInt64(8);
|
||||
NewSize = reader.ReadInt64();
|
||||
FragmentHeaderSize = reader.ReadInt64();
|
||||
FragmentBodySize = reader.ReadInt64();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
|
@ -62,6 +63,20 @@ namespace LibHac.IO
|
|||
return MemoryMarshal.Read<short>(_buffer);
|
||||
}
|
||||
|
||||
public int ReadUInt24(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, 3, updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<int>(_buffer) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
public int ReadInt24(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, 3, updatePosition);
|
||||
|
||||
return BitTools.SignExtend32(MemoryMarshal.Read<int>(_buffer), 24);
|
||||
}
|
||||
|
||||
public uint ReadUInt32(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(uint), updatePosition);
|
||||
|
@ -120,10 +135,21 @@ namespace LibHac.IO
|
|||
if (updatePosition) Position = offset + destination.Length;
|
||||
}
|
||||
|
||||
public string ReadAscii(long offset, int length, bool updatePosition)
|
||||
{
|
||||
var bytes = new byte[length];
|
||||
_file.Read(bytes, offset);
|
||||
|
||||
if (updatePosition) Position = offset + length;
|
||||
return Encoding.ASCII.GetString(bytes);
|
||||
}
|
||||
|
||||
public byte ReadUInt8(long offset) => ReadUInt8(offset, true);
|
||||
public sbyte ReadInt8(long offset) => ReadInt8(offset, true);
|
||||
public ushort ReadUInt16(long offset) => ReadUInt16(offset, true);
|
||||
public short ReadInt16(long offset) => ReadInt16(offset, true);
|
||||
public int ReadUInt24(long offset) => ReadUInt24(offset, true);
|
||||
public int ReadInt24(long offset) => ReadInt24(offset, true);
|
||||
public uint ReadUInt32(long offset) => ReadUInt32(offset, true);
|
||||
public int ReadInt32(long offset) => ReadInt32(offset, true);
|
||||
public ulong ReadUInt64(long offset) => ReadUInt64(offset, true);
|
||||
|
@ -132,11 +158,14 @@ namespace LibHac.IO
|
|||
public double ReadDouble(long offset) => ReadDouble(offset, true);
|
||||
public byte[] ReadBytes(long offset, int length) => ReadBytes(offset, length, true);
|
||||
public void ReadBytes(Span<byte> destination, long offset) => ReadBytes(destination, offset, true);
|
||||
public string ReadAscii(long offset, int length) => ReadAscii(offset, length, true);
|
||||
|
||||
public byte ReadUInt8() => ReadUInt8(Position, true);
|
||||
public sbyte ReadInt8() => ReadInt8(Position, true);
|
||||
public ushort ReadUInt16() => ReadUInt16(Position, true);
|
||||
public short ReadInt16() => ReadInt16(Position, true);
|
||||
public int ReadUInt24() => ReadUInt24(Position, true);
|
||||
public int ReadInt24() => ReadInt24(Position, true);
|
||||
public uint ReadUInt32() => ReadUInt32(Position, true);
|
||||
public int ReadInt32() => ReadInt32(Position, true);
|
||||
public ulong ReadUInt64() => ReadUInt64(Position, true);
|
||||
|
@ -145,5 +174,6 @@ namespace LibHac.IO
|
|||
public double ReadDouble() => ReadDouble(Position, true);
|
||||
public byte[] ReadBytes(int length) => ReadBytes(Position, length, true);
|
||||
public void ReadBytes(Span<byte> destination) => ReadBytes(destination, Position, true);
|
||||
public string ReadAscii(int length) => ReadAscii(Position, length, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,11 +32,13 @@ namespace hactoolnet
|
|||
new CliOption("debugoutdir", 1, (o, a) => o.DebugOutDir = a[0]),
|
||||
new CliOption("savedir", 1, (o, a) => o.SaveOutDir = a[0]),
|
||||
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("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]),
|
||||
new CliOption("basenca", 1, (o, a) => o.BaseNca = a[0]),
|
||||
new CliOption("basefile", 1, (o, a) => o.BaseFile = a[0]),
|
||||
new CliOption("rootdir", 1, (o, a) => o.RootDir = a[0]),
|
||||
new CliOption("updatedir", 1, (o, a) => o.UpdateDir = a[0]),
|
||||
new CliOption("normaldir", 1, (o, a) => o.NormalDir = a[0]),
|
||||
|
@ -159,9 +161,10 @@ namespace hactoolnet
|
|||
sb.AppendLine(" -y, --verify Verify all hashes in the input file.");
|
||||
sb.AppendLine(" -h, --enablehash Enable hash checks when reading the input file.");
|
||||
sb.AppendLine(" -k, --keyset Load keys from an external file.");
|
||||
sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, keygen]");
|
||||
sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, ndv0 keygen]");
|
||||
sb.AppendLine(" --titlekeys <file> Load title keys from an external file.");
|
||||
sb.AppendLine("NCA options:");
|
||||
sb.AppendLine(" --plaintext <file> Specify file path for saving a decrypted copy of the NCA.");
|
||||
sb.AppendLine(" --section0 <file> Specify Section 0 file path.");
|
||||
sb.AppendLine(" --section1 <file> Specify Section 1 file path.");
|
||||
sb.AppendLine(" --section2 <file> Specify Section 2 file path.");
|
||||
|
@ -216,6 +219,10 @@ namespace hactoolnet
|
|||
sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)");
|
||||
sb.AppendLine(" --listfiles List files in save file.");
|
||||
sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data");
|
||||
sb.AppendLine("NDV0 (Delta) options:");
|
||||
sb.AppendLine(" Input delta patch can be a delta NCA file or a delta fragment file.");
|
||||
sb.AppendLine(" --basefile <file> Specify base file path.");
|
||||
sb.AppendLine(" --outfile Specify patched file path.");
|
||||
sb.AppendLine("Keygen options:");
|
||||
sb.AppendLine(" --outdir <dir> Specify directory path to save key files to.");
|
||||
|
||||
|
|
|
@ -23,11 +23,13 @@ namespace hactoolnet
|
|||
public string DebugOutDir;
|
||||
public string SaveOutDir;
|
||||
public string OutDir;
|
||||
public string OutFile;
|
||||
public string PlaintextOut;
|
||||
public string SdSeed;
|
||||
public string NspOut;
|
||||
public string SdPath;
|
||||
public string BaseNca;
|
||||
public string BaseFile;
|
||||
public string RootDir;
|
||||
public string UpdateDir;
|
||||
public string NormalDir;
|
||||
|
@ -70,6 +72,7 @@ namespace hactoolnet
|
|||
Pk21,
|
||||
Kip1,
|
||||
Ini1,
|
||||
Ndv0,
|
||||
Bench
|
||||
}
|
||||
|
||||
|
|
81
src/hactoolnet/ProcessDelta.cs
Normal file
81
src/hactoolnet/ProcessDelta.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using LibHac;
|
||||
using LibHac.IO;
|
||||
using static hactoolnet.Print;
|
||||
|
||||
namespace hactoolnet
|
||||
{
|
||||
internal static class ProcessDelta
|
||||
{
|
||||
private const uint Ndv0Magic = 0x3056444E;
|
||||
private const string FragmentFileName = "fragment";
|
||||
|
||||
public static void Process(Context ctx)
|
||||
{
|
||||
using (var deltaFile = new StreamStorage(new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read), false))
|
||||
{
|
||||
|
||||
IStorage deltaStorage = deltaFile;
|
||||
Span<byte> magic = stackalloc byte[4];
|
||||
deltaFile.Read(magic, 0);
|
||||
|
||||
if (MemoryMarshal.Read<uint>(magic) != Ndv0Magic)
|
||||
{
|
||||
try
|
||||
{
|
||||
var nca = new Nca(ctx.Keyset, deltaStorage, true);
|
||||
IFileSystem fs = nca.OpenSectionFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
|
||||
if (!fs.FileExists(FragmentFileName))
|
||||
{
|
||||
throw new FileNotFoundException("Specified NCA does not contain a delta fragment");
|
||||
}
|
||||
|
||||
deltaStorage = new FileStorage(fs.OpenFile(FragmentFileName, OpenMode.Read));
|
||||
}
|
||||
catch (InvalidDataException) { } // Ignore non-NCA3 files
|
||||
}
|
||||
|
||||
var delta = new DeltaFragment(deltaStorage);
|
||||
|
||||
if (ctx.Options.BaseFile != null)
|
||||
{
|
||||
using (var baseFile = new StreamStorage(new FileStream(ctx.Options.BaseFile, FileMode.Open, FileAccess.Read), false))
|
||||
{
|
||||
delta.SetBaseStorage(baseFile);
|
||||
|
||||
if (ctx.Options.OutFile != null)
|
||||
{
|
||||
using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
IStorage patchedStorage = delta.GetPatchedStorage();
|
||||
patchedStorage.CopyToStream(outFile, patchedStorage.Length, ctx.Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Logger.LogMessage(delta.Print());
|
||||
}
|
||||
}
|
||||
|
||||
private static string Print(this DeltaFragment delta)
|
||||
{
|
||||
int colLen = 36;
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("Delta Fragment:");
|
||||
PrintItem(sb, colLen, "Magic:", delta.Header.Magic);
|
||||
PrintItem(sb, colLen, "Base file size:", $"0x{delta.Header.OriginalSize:x12}");
|
||||
PrintItem(sb, colLen, "New file size:", $"0x{delta.Header.NewSize:x12}");
|
||||
PrintItem(sb, colLen, "Fragment header size:", $"0x{delta.Header.FragmentHeaderSize:x12}");
|
||||
PrintItem(sb, colLen, "Fragment body size:", $"0x{delta.Header.FragmentBodySize:x12}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -90,6 +90,9 @@ namespace hactoolnet
|
|||
case FileType.Ini1:
|
||||
ProcessKip.ProcessIni1(ctx);
|
||||
break;
|
||||
case FileType.Ndv0:
|
||||
ProcessDelta.Process(ctx);
|
||||
break;
|
||||
case FileType.Bench:
|
||||
ProcessBench.Process(ctx);
|
||||
break;
|
||||
|
|
Loading…
Reference in a new issue