Add Duplex FS handler for save files

This commit is contained in:
Alex Barney 2018-09-01 14:23:45 -05:00
parent 12fbc1484c
commit 1a7e452a89
7 changed files with 300 additions and 25 deletions

View file

@ -0,0 +1,41 @@
using System;
using System.Collections;
using System.IO;
namespace LibHac.Savefile
{
public class DuplexBitmap
{
private Stream Data { get; }
public BitArray Bitmap { get; }
public DuplexBitmap(Stream bitmapStream, int lengthBits)
{
Data = bitmapStream;
Bitmap = new BitArray(lengthBits);
ReadBitmap(lengthBits);
}
private void ReadBitmap(int lengthBits)
{
uint mask = unchecked((uint)(1 << 31));
var reader = new BinaryReader(Data);
int bitsRemaining = lengthBits;
int bitmapPos = 0;
while (bitsRemaining > 0)
{
int bitsToRead = Math.Min(bitsRemaining, 32);
uint val = reader.ReadUInt32();
for (int i = 0; i < bitsToRead; i++)
{
Bitmap[bitmapPos] = (val & mask) != 0;
bitmapPos++;
bitsRemaining--;
val <<= 1;
}
}
}
}
}

View file

@ -0,0 +1,94 @@
using System;
using System.IO;
namespace LibHac.Savefile
{
public class DuplexFs : Stream
{
private int BlockSize{ get; }
private Stream BitmapStream { get; }
private Stream DataA { get; }
private Stream DataB { get; }
private DuplexBitmap Bitmap { get; }
public DuplexFs(Stream bitmap, Stream dataA, Stream dataB, int blockSize)
{
if (dataA.Length != dataB.Length)
{
throw new InvalidDataException("Both data streams must be the same length");
}
BlockSize = blockSize;
BitmapStream = bitmap;
DataA = dataA;
DataB = dataB;
Bitmap = new DuplexBitmap(BitmapStream, (int)(bitmap.Length * 8));
Length = dataA.Length;
}
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
long remaining = Math.Min(count, Length - Position);
if (remaining <= 0) return 0;
int outOffset = offset;
int totalBytesRead = 0;
while (remaining > 0)
{
int blockNum = (int)(Position / BlockSize);
int blockPos = (int)(Position % BlockSize);
int bytesToRead = (int)Math.Min(remaining, BlockSize - blockPos);
var data = Bitmap.Bitmap[blockNum] ? DataB : DataA;
data.Position = blockNum * BlockSize + blockPos;
data.Read(buffer, outOffset, bytesToRead);
outOffset += bytesToRead;
totalBytesRead += bytesToRead;
remaining -= bytesToRead;
Position += bytesToRead;
}
return totalBytesRead;
}
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 NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override long Length { get; }
public override long Position { get; set; }
}
}

View file

@ -8,6 +8,7 @@ namespace LibHac.Savefile
public byte[] Cmac { get; set; } public byte[] Cmac { get; set; }
public FsLayout Layout { get; set; } public FsLayout Layout { get; set; }
public JournalHeader Journal { get; set; } public JournalHeader Journal { get; set; }
public DuplexHeader Duplex { get; set; }
public SaveHeader Save { get; set; } public SaveHeader Save { get; set; }
public RemapHeader FileRemap { get; set; } public RemapHeader FileRemap { get; set; }
@ -34,6 +35,9 @@ namespace LibHac.Savefile
reader.BaseStream.Position = 0x100; reader.BaseStream.Position = 0x100;
Layout = new FsLayout(reader); Layout = new FsLayout(reader);
reader.BaseStream.Position = 0x300;
Duplex = new DuplexHeader(reader);
reader.BaseStream.Position = 0x408; reader.BaseStream.Position = 0x408;
Journal = new JournalHeader(reader); Journal = new JournalHeader(reader);
@ -193,6 +197,42 @@ namespace LibHac.Savefile
} }
} }
public class DuplexHeader
{
public string Magic { get; }
public uint MagicNum { get; }
public DuplexInfo[] Layers { get; } = new DuplexInfo[3];
public DuplexHeader(BinaryReader reader)
{
Magic = reader.ReadAscii(4);
MagicNum = reader.ReadUInt32();
for (int i = 0; i < Layers.Length; i++)
{
Layers[i] = new DuplexInfo(reader);
}
}
}
public class DuplexInfo
{
public long Offset { get; }
public long Length { get; set; }
public int BlockSizePower { get; set; }
public int BlockSize { get; set; }
public DuplexInfo() { }
public DuplexInfo(BinaryReader reader)
{
Offset = reader.ReadInt64();
Length = reader.ReadInt64();
BlockSizePower = reader.ReadInt32();
BlockSize = 1 << BlockSizePower;
}
}
public class JournalHeader public class JournalHeader
{ {
public string Magic { get; } public string Magic { get; }

View file

@ -0,0 +1,75 @@
using System.IO;
namespace LibHac.Savefile
{
public class LayeredDuplexFs : Stream
{
private DuplexFs[] Layers { get; }
private DuplexFs DataLayer { get; }
public LayeredDuplexFs(DuplexFsLayerInfo[] layers, bool masterBit)
{
Layers = new DuplexFs[layers.Length - 1];
for (int i = 0; i < Layers.Length; i++)
{
Stream bitmap;
if (i == 0)
{
bitmap = masterBit ? layers[0].DataB : layers[0].DataA;
}
else
{
bitmap = Layers[i - 1];
}
Layers[i] = new DuplexFs(bitmap, layers[i + 1].DataA, layers[i + 1].DataB, layers[i + 1].Info.BlockSize);
}
DataLayer = Layers[Layers.Length - 1];
}
public override void Flush()
{
throw new System.NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
return DataLayer.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return DataLayer.Seek(offset, origin);
}
public override void SetLength(long value)
{
throw new System.NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new System.NotImplementedException();
}
public override bool CanRead => DataLayer.CanRead;
public override bool CanSeek => DataLayer.CanSeek;
public override bool CanWrite => DataLayer.CanWrite;
public override long Length => DataLayer.Length;
public override long Position
{
get => DataLayer.Position;
set => DataLayer.Position = value;
}
}
public class DuplexFsLayerInfo
{
public Stream DataA { get; set; }
public Stream DataB { get; set; }
public DuplexInfo Info { get; set; }
}
}

View file

@ -20,6 +20,7 @@ namespace LibHac.Savefile
public Stream DuplexL1B { get; } public Stream DuplexL1B { get; }
public Stream DuplexDataA { get; } public Stream DuplexDataA { get; }
public Stream DuplexDataB { get; } public Stream DuplexDataB { get; }
public LayeredDuplexFs DuplexData { get; }
public Stream JournalData { get; } public Stream JournalData { get; }
public Stream JournalTable { get; } public Stream JournalTable { get; }
@ -46,14 +47,37 @@ namespace LibHac.Savefile
FileRemapSource = new SharedStreamSource(FileRemap); FileRemapSource = new SharedStreamSource(FileRemap);
var duplexLayers = new DuplexFsLayerInfo[3];
duplexLayers[0] = new DuplexFsLayerInfo
{
DataA = new MemoryStream(Header.DuplexMasterA),
DataB = new MemoryStream(Header.DuplexMasterB),
Info = Header.Duplex.Layers[0]
};
duplexLayers[1] = new DuplexFsLayerInfo
{
DataA = FileRemapSource.CreateStream(layout.DuplexL1OffsetA, layout.DuplexL1Size),
DataB = FileRemapSource.CreateStream(layout.DuplexL1OffsetB, layout.DuplexL1Size),
Info = Header.Duplex.Layers[1]
};
duplexLayers[2] = new DuplexFsLayerInfo
{
DataA = FileRemapSource.CreateStream(layout.DuplexDataOffsetA, layout.DuplexDataSize),
DataB = FileRemapSource.CreateStream(layout.DuplexDataOffsetB, layout.DuplexDataSize),
Info = Header.Duplex.Layers[2]
};
DuplexL1A = FileRemapSource.CreateStream(layout.DuplexL1OffsetA, layout.DuplexL1Size); DuplexL1A = FileRemapSource.CreateStream(layout.DuplexL1OffsetA, layout.DuplexL1Size);
DuplexL1B = FileRemapSource.CreateStream(layout.DuplexL1OffsetB, layout.DuplexL1Size); DuplexL1B = FileRemapSource.CreateStream(layout.DuplexL1OffsetB, layout.DuplexL1Size);
DuplexDataA = FileRemapSource.CreateStream(layout.DuplexDataOffsetA, layout.DuplexDataSize); DuplexDataA = FileRemapSource.CreateStream(layout.DuplexDataOffsetA, layout.DuplexDataSize);
DuplexDataB = FileRemapSource.CreateStream(layout.DuplexDataOffsetB, layout.DuplexDataSize); DuplexDataB = FileRemapSource.CreateStream(layout.DuplexDataOffsetB, layout.DuplexDataSize);
JournalData = FileRemapSource.CreateStream(layout.JournalDataOffset, layout.JournalDataSizeB + layout.SizeReservedArea); JournalData = FileRemapSource.CreateStream(layout.JournalDataOffset, layout.JournalDataSizeB + layout.SizeReservedArea);
var duplexData = layout.DuplexIndex == 0 ? DuplexDataA : DuplexDataB; DuplexData = new LayeredDuplexFs(duplexLayers, Header.Layout.DuplexIndex == 1);
MetaRemap = new RemapStream(duplexData, Header.MetaMapEntries, Header.MetaRemap.MapSegmentCount); MetaRemap = new RemapStream(DuplexData, Header.MetaMapEntries, Header.MetaRemap.MapSegmentCount);
MetaRemapSource = new SharedStreamSource(MetaRemap); MetaRemapSource = new SharedStreamSource(MetaRemap);
JournalTable = MetaRemapSource.CreateStream(layout.JournalTableOffset, layout.JournalTableSize); JournalTable = MetaRemapSource.CreateStream(layout.JournalTableOffset, layout.JournalTableSize);

View file

@ -95,7 +95,7 @@ namespace hactoolnet
i += option.ArgsNeeded; i += option.ArgsNeeded;
} }
if (!inputSpecified && options.InFileType != FileType.Keygen) if (!inputSpecified && options.InFileType != FileType.Keygen && !options.RunCustom)
{ {
PrintWithUsage("Input file must be specified"); PrintWithUsage("Input file must be specified");
return null; return null;

View file

@ -474,26 +474,27 @@ namespace hactoolnet
var dir = ctx.Options.DebugOutDir; var dir = ctx.Options.DebugOutDir;
Directory.CreateDirectory(dir); Directory.CreateDirectory(dir);
File.WriteAllBytes(Path.Combine(dir, "L1_0_MasterHashA"), save.Header.MasterHashA); File.WriteAllBytes(Path.Combine(dir, "L0_0_MasterHashA"), save.Header.MasterHashA);
File.WriteAllBytes(Path.Combine(dir, "L1_1_MasterHashB"), save.Header.MasterHashB); File.WriteAllBytes(Path.Combine(dir, "L0_1_MasterHashB"), save.Header.MasterHashB);
File.WriteAllBytes(Path.Combine(dir, "L1_2_DuplexMasterA"), save.Header.DuplexMasterA); File.WriteAllBytes(Path.Combine(dir, "L0_2_DuplexMasterA"), save.Header.DuplexMasterA);
File.WriteAllBytes(Path.Combine(dir, "L1_3_DuplexMasterB"), save.Header.DuplexMasterB); File.WriteAllBytes(Path.Combine(dir, "L0_3_DuplexMasterB"), save.Header.DuplexMasterB);
save.DuplexL1A.WriteAllBytes(Path.Combine(dir, "L0_0_DuplexL1A"), ctx.Logger); save.DuplexL1A.WriteAllBytes(Path.Combine(dir, "L0_4_DuplexL1A"), ctx.Logger);
save.DuplexL1B.WriteAllBytes(Path.Combine(dir, "L0_1_DuplexL1B"), ctx.Logger); save.DuplexL1B.WriteAllBytes(Path.Combine(dir, "L0_5_DuplexL1B"), ctx.Logger);
save.DuplexDataA.WriteAllBytes(Path.Combine(dir, "L0_2_DuplexDataA"), ctx.Logger); save.DuplexDataA.WriteAllBytes(Path.Combine(dir, "L0_6_DuplexDataA"), ctx.Logger);
save.DuplexDataB.WriteAllBytes(Path.Combine(dir, "L0_3_DuplexDataB"), ctx.Logger); save.DuplexDataB.WriteAllBytes(Path.Combine(dir, "L0_7_DuplexDataB"), ctx.Logger);
save.JournalData.WriteAllBytes(Path.Combine(dir, "L0_4_JournalData"), ctx.Logger); save.JournalData.WriteAllBytes(Path.Combine(dir, "L0_9_JournalData"), ctx.Logger);
save.JournalTable.WriteAllBytes(Path.Combine(dir, "L1_0_JournalTable"), ctx.Logger); save.DuplexData.WriteAllBytes(Path.Combine(dir, "L1_0_DuplexData"), ctx.Logger);
save.JournalBitmapUpdatedPhysical.WriteAllBytes(Path.Combine(dir, "L1_1_JournalBitmapUpdatedPhysical"), ctx.Logger); save.JournalTable.WriteAllBytes(Path.Combine(dir, "L2_0_JournalTable"), ctx.Logger);
save.JournalBitmapUpdatedVirtual.WriteAllBytes(Path.Combine(dir, "L1_2_JournalBitmapUpdatedVirtual"), ctx.Logger); save.JournalBitmapUpdatedPhysical.WriteAllBytes(Path.Combine(dir, "L2_1_JournalBitmapUpdatedPhysical"), ctx.Logger);
save.JournalBitmapUnassigned.WriteAllBytes(Path.Combine(dir, "L1_3_JournalBitmapUnassigned"), ctx.Logger); save.JournalBitmapUpdatedVirtual.WriteAllBytes(Path.Combine(dir, "L2_2_JournalBitmapUpdatedVirtual"), ctx.Logger);
save.JournalLayer1Hash.WriteAllBytes(Path.Combine(dir, "L1_4_Layer1Hash"), ctx.Logger); save.JournalBitmapUnassigned.WriteAllBytes(Path.Combine(dir, "L2_3_JournalBitmapUnassigned"), ctx.Logger);
save.JournalLayer2Hash.WriteAllBytes(Path.Combine(dir, "L1_5_Layer2Hash"), ctx.Logger); save.JournalLayer1Hash.WriteAllBytes(Path.Combine(dir, "L2_4_Layer1Hash"), ctx.Logger);
save.JournalLayer3Hash.WriteAllBytes(Path.Combine(dir, "L1_6_Layer3Hash"), ctx.Logger); save.JournalLayer2Hash.WriteAllBytes(Path.Combine(dir, "L2_5_Layer2Hash"), ctx.Logger);
save.JournalFat.WriteAllBytes(Path.Combine(dir, "L1_7_FAT"), ctx.Logger); save.JournalLayer3Hash.WriteAllBytes(Path.Combine(dir, "L2_6_Layer3Hash"), ctx.Logger);
save.JournalFat.WriteAllBytes(Path.Combine(dir, "L2_7_FAT"), ctx.Logger);
save.JournalStreamSource.CreateStream().WriteAllBytes(Path.Combine(dir, "L2_0_SaveData"), ctx.Logger); save.JournalStreamSource.CreateStream().WriteAllBytes(Path.Combine(dir, "L3_0_SaveData"), ctx.Logger);
} }
} }
} }