Read all remapping and journaling savefile layers

This commit is contained in:
Alex Barney 2018-07-22 20:28:31 -05:00
parent 2f00192cc7
commit b0cac269f3
5 changed files with 379 additions and 30 deletions

View file

@ -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
View 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
}
}
}

View file

@ -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
View 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; }
}
}

View file

@ -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)
;
}
}
}