mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Read all remapping and journaling savefile layers
This commit is contained in:
parent
2f00192cc7
commit
b0cac269f3
5 changed files with 379 additions and 30 deletions
|
@ -214,38 +214,34 @@ namespace hactoolnet
|
|||
{
|
||||
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
var save = new Savefile(file);
|
||||
var save = new Savefile(file, ctx.Logger);
|
||||
var layout = save.Header.Layout;
|
||||
|
||||
save.FileRemap.Position = layout.DuplexL1OffsetB;
|
||||
using (var outFile = new FileStream("0_DuplexL1A", FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
save.FileRemap.CopyStream(outFile, layout.DuplexDataSize);
|
||||
}
|
||||
File.WriteAllBytes("d0_JournalTable", save.JournalTable);
|
||||
File.WriteAllBytes("d1_JournalBitmapUpdatedPhysical", save.JournalBitmapUpdatedPhysical);
|
||||
File.WriteAllBytes("d2_JournalBitmapUpdatedVirtual", save.JournalBitmapUpdatedVirtual);
|
||||
File.WriteAllBytes("d3_JournalBitmapUnassigned", save.JournalBitmapUnassigned);
|
||||
File.WriteAllBytes("d4_Layer1Hash", save.JournalLayer1Hash);
|
||||
File.WriteAllBytes("d5_Layer2Hash", save.JournalLayer2Hash);
|
||||
File.WriteAllBytes("d6_Layer3Hash", save.JournalLayer3Hash);
|
||||
File.WriteAllBytes("d7_Stuff", save.JournalStuff);
|
||||
|
||||
save.FileRemap.Position = layout.DuplexL1OffsetB;
|
||||
using (var outFile = new FileStream("1_DuplexL1B", FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
save.FileRemap.CopyStream(outFile, layout.DuplexDataSize);
|
||||
}
|
||||
|
||||
save.FileRemap.Position = layout.DuplexDataOffsetA;
|
||||
using (var outFile = new FileStream("2_DuplexDataA", FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
save.FileRemap.CopyStream(outFile, layout.DuplexDataSize);
|
||||
}
|
||||
|
||||
save.FileRemap.Position = layout.DuplexDataOffsetB;
|
||||
using (var outFile = new FileStream("3_DuplexDataB", FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
save.FileRemap.CopyStream(outFile, layout.DuplexDataSize);
|
||||
}
|
||||
File.WriteAllBytes("0_DuplexL1A", save.DuplexL1A);
|
||||
File.WriteAllBytes("1_DuplexL1B", save.DuplexL1B);
|
||||
File.WriteAllBytes("2_DuplexDataA", save.DuplexDataA);
|
||||
File.WriteAllBytes("3_DuplexDataB", save.DuplexDataB);
|
||||
|
||||
save.FileRemap.Position = layout.JournalDataOffset;
|
||||
using (var outFile = new FileStream("4_JournalData", FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
save.FileRemap.CopyStream(outFile, layout.JournalDataSizeB + layout.SizeReservedArea);
|
||||
}
|
||||
|
||||
save.JournalStream.Position = 0;
|
||||
using (var outFile = new FileStream("j0_Data", FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
save.JournalStream.CopyStream(outFile, save.JournalStream.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
133
libhac/BitReader.cs
Normal file
133
libhac/BitReader.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace libhac
|
||||
{
|
||||
public class BitReader
|
||||
{
|
||||
public byte[] Buffer { get; private set; }
|
||||
public int LengthBits { get; private set; }
|
||||
public int Position { get; set; }
|
||||
public int Remaining => LengthBits - Position;
|
||||
|
||||
public BitReader(byte[] buffer) => SetBuffer(buffer);
|
||||
|
||||
public void SetBuffer(byte[] buffer)
|
||||
{
|
||||
Buffer = buffer;
|
||||
LengthBits = Buffer?.Length * 8 ?? 0;
|
||||
Position = 0;
|
||||
}
|
||||
|
||||
public int ReadInt(int bitCount)
|
||||
{
|
||||
int value = PeekInt(bitCount);
|
||||
Position += bitCount;
|
||||
return value;
|
||||
}
|
||||
|
||||
//public int ReadSignedInt(int bitCount)
|
||||
//{
|
||||
// int value = PeekInt(bitCount);
|
||||
// Position += bitCount;
|
||||
// return Bit.SignExtend32(value, bitCount);
|
||||
//}
|
||||
|
||||
public bool ReadBool() => ReadInt(1) == 1;
|
||||
|
||||
public int ReadOffsetBinary(int bitCount, OffsetBias bias)
|
||||
{
|
||||
int offset = (1 << (bitCount - 1)) - (int)bias;
|
||||
int value = PeekInt(bitCount) - offset;
|
||||
Position += bitCount;
|
||||
return value;
|
||||
}
|
||||
|
||||
//public void AlignPosition(int multiple)
|
||||
//{
|
||||
// Position = Helpers.GetNextMultiple(Position, multiple);
|
||||
//}
|
||||
|
||||
public int PeekInt(int bitCount)
|
||||
{
|
||||
Debug.Assert(bitCount >= 0 && bitCount <= 32);
|
||||
|
||||
if (bitCount > Remaining)
|
||||
{
|
||||
if (Position >= LengthBits) return 0;
|
||||
|
||||
int extraBits = bitCount - Remaining;
|
||||
return PeekIntFallback(Remaining) << extraBits;
|
||||
}
|
||||
|
||||
int byteIndex = Position / 8;
|
||||
int bitIndex = Position % 8;
|
||||
|
||||
if (bitCount <= 9 && Remaining >= 16)
|
||||
{
|
||||
int value = Buffer[byteIndex] << 8 | Buffer[byteIndex + 1];
|
||||
value &= 0xFFFF >> bitIndex;
|
||||
value >>= 16 - bitCount - bitIndex;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (bitCount <= 17 && Remaining >= 24)
|
||||
{
|
||||
int value = Buffer[byteIndex] << 16 | Buffer[byteIndex + 1] << 8 | Buffer[byteIndex + 2];
|
||||
value &= 0xFFFFFF >> bitIndex;
|
||||
value >>= 24 - bitCount - bitIndex;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (bitCount <= 25 && Remaining >= 32)
|
||||
{
|
||||
int value = Buffer[byteIndex] << 24 | Buffer[byteIndex + 1] << 16 | Buffer[byteIndex + 2] << 8 | Buffer[byteIndex + 3];
|
||||
value &= (int)(0xFFFFFFFF >> bitIndex);
|
||||
value >>= 32 - bitCount - bitIndex;
|
||||
return value;
|
||||
}
|
||||
return PeekIntFallback(bitCount);
|
||||
}
|
||||
|
||||
private int PeekIntFallback(int bitCount)
|
||||
{
|
||||
int value = 0;
|
||||
int byteIndex = Position / 8;
|
||||
int bitIndex = Position % 8;
|
||||
|
||||
while (bitCount > 0)
|
||||
{
|
||||
if (bitIndex >= 8)
|
||||
{
|
||||
bitIndex = 0;
|
||||
byteIndex++;
|
||||
}
|
||||
|
||||
int bitsToRead = Math.Min(bitCount, 8 - bitIndex);
|
||||
int mask = 0xFF >> bitIndex;
|
||||
int currentByte = (mask & Buffer[byteIndex]) >> (8 - bitIndex - bitsToRead);
|
||||
|
||||
value = (value << bitsToRead) | currentByte;
|
||||
bitIndex += bitsToRead;
|
||||
bitCount -= bitsToRead;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the bias of an offset binary value. A positive bias can represent one more
|
||||
/// positive value than negative value, and a negative bias can represent one more
|
||||
/// negative value than positive value.
|
||||
/// </summary>
|
||||
/// <remarks>Example:
|
||||
/// A 4-bit offset binary value with a positive bias can store
|
||||
/// the values 8 through -7 inclusive.
|
||||
/// A 4-bit offset binary value with a positive bias can store
|
||||
/// the values 7 through -8 inclusive.</remarks>
|
||||
public enum OffsetBias
|
||||
{
|
||||
Positive = 1,
|
||||
Negative = 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ namespace libhac.Savefile
|
|||
{
|
||||
public byte[] Cmac { get; set; }
|
||||
public FsLayout Layout { get; set; }
|
||||
public JournalHeader Journal{ get; set; }
|
||||
|
||||
public RemapHeader FileRemap { get; set; }
|
||||
public RemapHeader MetaRemap { get; set; }
|
||||
|
@ -27,6 +28,9 @@ namespace libhac.Savefile
|
|||
reader.BaseStream.Position = 0x100;
|
||||
Layout = new FsLayout(reader);
|
||||
|
||||
reader.BaseStream.Position = 0x408;
|
||||
Journal = new JournalHeader(reader);
|
||||
|
||||
reader.BaseStream.Position = 0x650;
|
||||
FileRemap = new RemapHeader(reader);
|
||||
reader.BaseStream.Position = 0x690;
|
||||
|
@ -87,8 +91,8 @@ namespace libhac.Savefile
|
|||
public long MasterHashOffset0 { get; set; }
|
||||
public long MasterHashOffset1 { get; set; }
|
||||
public long MasterHashSize { get; set; }
|
||||
public long OffsetJournalTable { get; set; }
|
||||
public long SizeJournalTable { get; set; }
|
||||
public long JournalTableOffset { get; set; }
|
||||
public long JournalTableSize { get; set; }
|
||||
public long JournalBitmapUpdatedPhysicalOffset { get; set; }
|
||||
public long JournalBitmapUpdatedPhysicalSize { get; set; }
|
||||
public long JournalBitmapUpdatedVirtualOffset { get; set; }
|
||||
|
@ -132,8 +136,8 @@ namespace libhac.Savefile
|
|||
MasterHashOffset0 = reader.ReadInt64();
|
||||
MasterHashOffset1 = reader.ReadInt64();
|
||||
MasterHashSize = reader.ReadInt64();
|
||||
OffsetJournalTable = reader.ReadInt64();
|
||||
SizeJournalTable = reader.ReadInt64();
|
||||
JournalTableOffset = reader.ReadInt64();
|
||||
JournalTableSize = reader.ReadInt64();
|
||||
JournalBitmapUpdatedPhysicalOffset = reader.ReadInt64();
|
||||
JournalBitmapUpdatedPhysicalSize = reader.ReadInt64();
|
||||
JournalBitmapUpdatedVirtualOffset = reader.ReadInt64();
|
||||
|
@ -170,6 +174,32 @@ namespace libhac.Savefile
|
|||
}
|
||||
}
|
||||
|
||||
public class JournalHeader
|
||||
{
|
||||
public string Magic { get; }
|
||||
public uint MagicNum { get; }
|
||||
public long Field8 { get; }
|
||||
public long Field10 { get; }
|
||||
public long BlockSize { get; }
|
||||
public int Field20 { get; }
|
||||
public int MappingEntryCount { get; }
|
||||
public int Field28 { get; }
|
||||
public int Field2C { get; }
|
||||
|
||||
public JournalHeader(BinaryReader reader)
|
||||
{
|
||||
Magic = reader.ReadAscii(4);
|
||||
MagicNum = reader.ReadUInt32();
|
||||
Field8 = reader.ReadInt64();
|
||||
Field10 = reader.ReadInt64();
|
||||
BlockSize = reader.ReadInt64();
|
||||
Field20 = reader.ReadInt32();
|
||||
MappingEntryCount = reader.ReadInt32();
|
||||
Field28 = reader.ReadInt32();
|
||||
Field2C = reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
public class MapEntry
|
||||
{
|
||||
public long VirtualOffset { get; }
|
||||
|
|
120
libhac/Savefile/Journal.cs
Normal file
120
libhac/Savefile/Journal.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace libhac.Savefile
|
||||
{
|
||||
public class JournalStream : Stream
|
||||
{
|
||||
private long _position;
|
||||
private Stream BaseStream { get; }
|
||||
public MappingEntry[] Map { get; }
|
||||
public int BlockSize { get; }
|
||||
private MappingEntry CurrentMapEntry { get; set; }
|
||||
|
||||
public JournalStream(Stream baseStream, MappingEntry[] map, int blockSize)
|
||||
{
|
||||
BaseStream = baseStream;
|
||||
Map = map;
|
||||
BlockSize = blockSize;
|
||||
Length = map.Length * BlockSize;
|
||||
}
|
||||
|
||||
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 outPos = offset;
|
||||
|
||||
while (toOutput > 0)
|
||||
{
|
||||
var remainInEntry = BlockSize - Position % BlockSize;
|
||||
int toRead = (int)Math.Min(toOutput, remainInEntry);
|
||||
BaseStream.Read(buffer, outPos, toRead);
|
||||
|
||||
outPos += toRead;
|
||||
toOutput -= toRead;
|
||||
Position += toRead;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
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 CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length { get; }
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
if (value >= Length) return;
|
||||
var currentBlock = value / BlockSize;
|
||||
var blockPos = value % BlockSize;
|
||||
CurrentMapEntry = Map[currentBlock];
|
||||
BaseStream.Position = CurrentMapEntry.PhysicalIndex * BlockSize + blockPos;
|
||||
}
|
||||
}
|
||||
|
||||
public static MappingEntry[] ReadMappingEntries(byte[] mapTable, byte[] bitmapUpdatedPhysical,
|
||||
byte[] bitmapUpdatedVirtual, byte[] bitmapUnassigned, int count)
|
||||
{
|
||||
var physicalBits = new BitReader(bitmapUpdatedPhysical);
|
||||
var virtualBits = new BitReader(bitmapUpdatedVirtual);
|
||||
var unassignedBits = new BitReader(bitmapUnassigned);
|
||||
var tableReader = new BinaryReader(new MemoryStream(mapTable));
|
||||
var map = new MappingEntry[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var entry = new MappingEntry
|
||||
{
|
||||
VirtualIndex = i,
|
||||
PhysicalIndex = tableReader.ReadInt32() & 0x7FFFFFFF,
|
||||
//UpdatedPhysical = physicalBits.ReadBool(),
|
||||
//UpdatedVirtual = virtualBits.ReadBool(),
|
||||
//Unassigned = unassignedBits.ReadBool()
|
||||
};
|
||||
|
||||
map[i] = entry;
|
||||
tableReader.BaseStream.Position += 4;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
public class MappingEntry
|
||||
{
|
||||
public int PhysicalIndex { get; set; }
|
||||
public int VirtualIndex { get; set; }
|
||||
public bool UpdatedPhysical { get; set; }
|
||||
public bool UpdatedVirtual { get; set; }
|
||||
public bool Unassigned { get; set; }
|
||||
}
|
||||
}
|
|
@ -7,15 +7,85 @@ namespace libhac.Savefile
|
|||
{
|
||||
public Header Header { get; }
|
||||
public RemapStream FileRemap { get; }
|
||||
public RemapStream MetaRemap { get; }
|
||||
private Stream FileStream { get; }
|
||||
public JournalStream JournalStream { get; }
|
||||
|
||||
public Savefile(Stream file)
|
||||
public byte[] DuplexL1A { get; }
|
||||
public byte[] DuplexL1B { get; }
|
||||
public byte[] DuplexDataA { get; }
|
||||
public byte[] DuplexDataB { get; }
|
||||
|
||||
public byte[] JournalTable { get; }
|
||||
public byte[] JournalBitmapUpdatedPhysical { get; }
|
||||
public byte[] JournalBitmapUpdatedVirtual { get; }
|
||||
public byte[] JournalBitmapUnassigned { get; }
|
||||
public byte[] JournalLayer1Hash { get; }
|
||||
public byte[] JournalLayer2Hash { get; }
|
||||
public byte[] JournalLayer3Hash { get; }
|
||||
public byte[] JournalStuff { get; }
|
||||
|
||||
public Savefile(Stream file, IProgressReport logger = null)
|
||||
{
|
||||
FileStream = file;
|
||||
using (var reader = new BinaryReader(file, Encoding.Default, true))
|
||||
{
|
||||
Header = new Header(reader);
|
||||
Header = new Header(reader, logger);
|
||||
var layout = Header.Layout;
|
||||
FileRemap = new RemapStream(
|
||||
new SubStream(file, Header.Layout.FileMapDataOffset, Header.Layout.FileMapDataSize),
|
||||
new SubStream(file, layout.FileMapDataOffset, layout.FileMapDataSize),
|
||||
Header.FileMapEntries, Header.FileRemap.MapSegmentCount);
|
||||
|
||||
DuplexL1A = new byte[layout.DuplexL1Size];
|
||||
DuplexL1B = new byte[layout.DuplexL1Size];
|
||||
DuplexDataA = new byte[layout.DuplexDataSize];
|
||||
DuplexDataB = new byte[layout.DuplexDataSize];
|
||||
|
||||
FileRemap.Position = layout.DuplexL1OffsetA;
|
||||
FileRemap.Read(DuplexL1A, 0, DuplexL1A.Length);
|
||||
FileRemap.Position = layout.DuplexL1OffsetB;
|
||||
FileRemap.Read(DuplexL1B, 0, DuplexL1B.Length);
|
||||
FileRemap.Position = layout.DuplexDataOffsetA;
|
||||
FileRemap.Read(DuplexDataA, 0, DuplexDataA.Length);
|
||||
FileRemap.Position = layout.DuplexDataOffsetB;
|
||||
FileRemap.Read(DuplexDataB, 0, DuplexDataB.Length);
|
||||
|
||||
var duplexData = new SubStream(FileRemap, layout.DuplexDataOffsetB, layout.DuplexDataSize);
|
||||
MetaRemap = new RemapStream(duplexData, Header.MetaMapEntries, Header.MetaRemap.MapSegmentCount);
|
||||
|
||||
JournalTable = new byte[layout.JournalTableSize];
|
||||
JournalBitmapUpdatedPhysical = new byte[layout.JournalBitmapUpdatedPhysicalSize];
|
||||
JournalBitmapUpdatedVirtual = new byte[layout.JournalBitmapUpdatedVirtualSize];
|
||||
JournalBitmapUnassigned = new byte[layout.JournalBitmapUnassignedSize];
|
||||
JournalLayer1Hash = new byte[layout.Layer1HashSize];
|
||||
JournalLayer2Hash = new byte[layout.Layer2HashSize];
|
||||
JournalLayer3Hash = new byte[layout.Layer3HashSize];
|
||||
JournalStuff = new byte[layout.Field150];
|
||||
|
||||
MetaRemap.Position = layout.JournalTableOffset;
|
||||
MetaRemap.Read(JournalTable, 0, JournalTable.Length);
|
||||
MetaRemap.Position = layout.JournalBitmapUpdatedPhysicalOffset;
|
||||
MetaRemap.Read(JournalBitmapUpdatedPhysical, 0, JournalBitmapUpdatedPhysical.Length);
|
||||
MetaRemap.Position = layout.JournalBitmapUpdatedVirtualOffset;
|
||||
MetaRemap.Read(JournalBitmapUpdatedVirtual, 0, JournalBitmapUpdatedVirtual.Length);
|
||||
MetaRemap.Position = layout.JournalBitmapUnassignedOffset;
|
||||
MetaRemap.Read(JournalBitmapUnassigned, 0, JournalBitmapUnassigned.Length);
|
||||
MetaRemap.Position = layout.Layer1HashOffset;
|
||||
MetaRemap.Read(JournalLayer1Hash, 0, JournalLayer1Hash.Length);
|
||||
MetaRemap.Position = layout.Layer2HashOffset;
|
||||
MetaRemap.Read(JournalLayer2Hash, 0, JournalLayer2Hash.Length);
|
||||
MetaRemap.Position = layout.Layer3HashOffset;
|
||||
MetaRemap.Read(JournalLayer3Hash, 0, JournalLayer3Hash.Length);
|
||||
MetaRemap.Position = layout.Field148;
|
||||
MetaRemap.Read(JournalStuff, 0, JournalStuff.Length);
|
||||
|
||||
var journalMap = JournalStream.ReadMappingEntries(JournalTable, JournalBitmapUpdatedPhysical,
|
||||
JournalBitmapUpdatedVirtual, JournalBitmapUnassigned, Header.Journal.MappingEntryCount);
|
||||
|
||||
var journalData = new SubStream(FileRemap, layout.JournalDataOffset,
|
||||
layout.JournalDataSizeB + layout.SizeReservedArea);
|
||||
JournalStream = new JournalStream(journalData, journalMap, (int) Header.Journal.BlockSize)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue