mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Process the file allocation table in savefiles
This commit is contained in:
parent
1a7e452a89
commit
7bc718c975
52
LibHac/Savefile/AllocationTable.cs
Normal file
52
LibHac/Savefile/AllocationTable.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace LibHac.Savefile
|
||||||
|
{
|
||||||
|
public class AllocationTable
|
||||||
|
{
|
||||||
|
public AllocationTableEntry[] Entries { get; }
|
||||||
|
|
||||||
|
public AllocationTable(Stream tableStream)
|
||||||
|
{
|
||||||
|
int blockCount = (int)(tableStream.Length / 8);
|
||||||
|
|
||||||
|
Entries = new AllocationTableEntry[blockCount];
|
||||||
|
tableStream.Position = 0;
|
||||||
|
var reader = new BinaryReader(tableStream);
|
||||||
|
|
||||||
|
for (int i = 0; i < blockCount; i++)
|
||||||
|
{
|
||||||
|
int parent = reader.ReadInt32();
|
||||||
|
int child = reader.ReadInt32();
|
||||||
|
|
||||||
|
Entries[i] = new AllocationTableEntry { Next = child, Prev = parent };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AllocationTableEntry
|
||||||
|
{
|
||||||
|
public int Prev { get; set; }
|
||||||
|
public int Next { get; set; }
|
||||||
|
|
||||||
|
public bool IsListStart()
|
||||||
|
{
|
||||||
|
return Prev == int.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsListEnd()
|
||||||
|
{
|
||||||
|
return (Next & 0x7FFFFFFF) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMultiBlockSegment()
|
||||||
|
{
|
||||||
|
return Next < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSingleBlockSegment()
|
||||||
|
{
|
||||||
|
return Next >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
LibHac/Savefile/AllocationTableIterator.cs
Normal file
88
LibHac/Savefile/AllocationTableIterator.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
namespace LibHac.Savefile
|
||||||
|
{
|
||||||
|
public class AllocationTableIterator
|
||||||
|
{
|
||||||
|
private AllocationTable Fat { get; }
|
||||||
|
|
||||||
|
public bool IsValid { get; }
|
||||||
|
public int VirtualBlock { get; private set; }
|
||||||
|
public int PhysicalBlock { get; private set; }
|
||||||
|
public int CurrentSegmentSize { get; private set; }
|
||||||
|
|
||||||
|
public AllocationTableIterator(AllocationTable table, int initialBlock)
|
||||||
|
{
|
||||||
|
Fat = table;
|
||||||
|
IsValid = BeginIteration(initialBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BeginIteration(int initialBlock)
|
||||||
|
{
|
||||||
|
var tableEntry = Fat.Entries[initialBlock + 1];
|
||||||
|
|
||||||
|
if (!tableEntry.IsListStart())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableEntry.IsSingleBlockSegment())
|
||||||
|
{
|
||||||
|
CurrentSegmentSize = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var lengthEntry = Fat.Entries[initialBlock + 2];
|
||||||
|
CurrentSegmentSize = lengthEntry.Next - initialBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalBlock = initialBlock;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
var currentEntry = Fat.Entries[PhysicalBlock + 1];
|
||||||
|
if (currentEntry.IsListEnd()) return false;
|
||||||
|
int newBlock = currentEntry.Next & 0x7FFFFFFF;
|
||||||
|
|
||||||
|
var newEntry = Fat.Entries[newBlock];
|
||||||
|
VirtualBlock += CurrentSegmentSize;
|
||||||
|
|
||||||
|
if (newEntry.IsSingleBlockSegment())
|
||||||
|
{
|
||||||
|
CurrentSegmentSize = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var lengthEntry = Fat.Entries[newBlock + 1];
|
||||||
|
CurrentSegmentSize = lengthEntry.Next - (newBlock - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalBlock = newBlock - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MovePrevious()
|
||||||
|
{
|
||||||
|
var currentEntry = Fat.Entries[PhysicalBlock + 1];
|
||||||
|
if (currentEntry.IsListStart()) return false;
|
||||||
|
int newBlock = currentEntry.Prev & 0x7FFFFFFF;
|
||||||
|
|
||||||
|
var newEntry = Fat.Entries[newBlock];
|
||||||
|
|
||||||
|
if (newEntry.IsSingleBlockSegment())
|
||||||
|
{
|
||||||
|
CurrentSegmentSize = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var lengthEntry = Fat.Entries[newBlock + 1];
|
||||||
|
CurrentSegmentSize = lengthEntry.Next - (newBlock - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualBlock -= CurrentSegmentSize;
|
||||||
|
PhysicalBlock = newBlock - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
LibHac/Savefile/AllocationTableStream.cs
Normal file
100
LibHac/Savefile/AllocationTableStream.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace LibHac.Savefile
|
||||||
|
{
|
||||||
|
public class AllocationTableStream : Stream
|
||||||
|
{
|
||||||
|
private int SegmentPos => (int)(Data.Position - (Iterator.PhysicalBlock * BlockSize));
|
||||||
|
|
||||||
|
private Stream Data { get; }
|
||||||
|
private int BlockSize { get; }
|
||||||
|
private AllocationTableIterator Iterator { get; }
|
||||||
|
|
||||||
|
public AllocationTableStream(Stream data, AllocationTable table, int blockSize, int initialBlock, long length)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
BlockSize = blockSize;
|
||||||
|
Length = length;
|
||||||
|
Iterator = new AllocationTableIterator(table, initialBlock);
|
||||||
|
if (!Iterator.IsValid) Length = 0;
|
||||||
|
Data.Position = Iterator.PhysicalBlock * BlockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
int remaining = count;
|
||||||
|
int outOffset = offset;
|
||||||
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
var remainingInSegment = Iterator.CurrentSegmentSize * BlockSize - SegmentPos;
|
||||||
|
int bytesToRead = Math.Min(remaining, remainingInSegment);
|
||||||
|
int bytesRead = Data.Read(buffer, outOffset, bytesToRead);
|
||||||
|
|
||||||
|
outOffset += bytesRead;
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
remaining -= bytesRead;
|
||||||
|
|
||||||
|
if (SegmentPos >= Iterator.CurrentSegmentSize * BlockSize)
|
||||||
|
{
|
||||||
|
if (!Iterator.MoveNext()) return totalBytesRead;
|
||||||
|
Data.Position = Iterator.PhysicalBlock * BlockSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalBytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => Iterator.VirtualBlock * BlockSize + (Data.Position - (Iterator.PhysicalBlock * BlockSize));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
long blockIndex = value / BlockSize;
|
||||||
|
|
||||||
|
while (Iterator.VirtualBlock > blockIndex ||
|
||||||
|
Iterator.VirtualBlock + Iterator.CurrentSegmentSize <= blockIndex)
|
||||||
|
{
|
||||||
|
if (Iterator.VirtualBlock > blockIndex)
|
||||||
|
{
|
||||||
|
Iterator.MovePrevious();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Iterator.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var segmentPos = value - (Iterator.VirtualBlock * BlockSize);
|
||||||
|
Data.Position = Iterator.PhysicalBlock * BlockSize + segmentPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ namespace LibHac.Savefile
|
|||||||
public SharedStreamSource MetaRemapSource { get; }
|
public SharedStreamSource MetaRemapSource { get; }
|
||||||
private JournalStream JournalStream { get; }
|
private JournalStream JournalStream { get; }
|
||||||
public SharedStreamSource JournalStreamSource { get; }
|
public SharedStreamSource JournalStreamSource { get; }
|
||||||
|
private AllocationTable AllocationTable { get; }
|
||||||
|
|
||||||
public Stream DuplexL1A { get; }
|
public Stream DuplexL1A { get; }
|
||||||
public Stream DuplexL1B { get; }
|
public Stream DuplexL1B { get; }
|
||||||
@ -88,6 +89,7 @@ namespace LibHac.Savefile
|
|||||||
JournalLayer2Hash = MetaRemapSource.CreateStream(layout.Layer2HashOffset, layout.Layer2HashSize);
|
JournalLayer2Hash = MetaRemapSource.CreateStream(layout.Layer2HashOffset, layout.Layer2HashSize);
|
||||||
JournalLayer3Hash = MetaRemapSource.CreateStream(layout.Layer3HashOffset, layout.Layer3HashSize);
|
JournalLayer3Hash = MetaRemapSource.CreateStream(layout.Layer3HashOffset, layout.Layer3HashSize);
|
||||||
JournalFat = MetaRemapSource.CreateStream(layout.Field148, layout.Field150);
|
JournalFat = MetaRemapSource.CreateStream(layout.Field148, layout.Field150);
|
||||||
|
AllocationTable = new AllocationTable(JournalFat);
|
||||||
|
|
||||||
var journalMap = JournalStream.ReadMappingEntries(JournalTable, Header.Journal.MappingEntryCount);
|
var journalMap = JournalStream.ReadMappingEntries(JournalTable, Header.Journal.MappingEntryCount);
|
||||||
|
|
||||||
@ -118,7 +120,18 @@ namespace LibHac.Savefile
|
|||||||
|
|
||||||
public Stream OpenFile(FileEntry file)
|
public Stream OpenFile(FileEntry file)
|
||||||
{
|
{
|
||||||
return JournalStreamSource.CreateStream(file.Offset, file.Size);
|
if (file.BlockIndex < 0)
|
||||||
|
{
|
||||||
|
//todo replace
|
||||||
|
return JournalStreamSource.CreateStream(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OpenFatBlock(file.BlockIndex, file.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllocationTableStream OpenFatBlock(int blockIndex, long size)
|
||||||
|
{
|
||||||
|
return new AllocationTableStream(JournalStreamSource.CreateStream(), AllocationTable, (int)Header.Save.BlockSize, blockIndex, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FileExists(string filename) => FileDict.ContainsKey(filename);
|
public bool FileExists(string filename) => FileDict.ContainsKey(filename);
|
||||||
@ -126,19 +139,13 @@ namespace LibHac.Savefile
|
|||||||
private void ReadFileInfo()
|
private void ReadFileInfo()
|
||||||
{
|
{
|
||||||
var blockSize = Header.Save.BlockSize;
|
var blockSize = Header.Save.BlockSize;
|
||||||
var dirOffset = Header.Save.DirectoryTableBlock * blockSize;
|
|
||||||
var fileOffset = Header.Save.FileTableBlock * blockSize;
|
|
||||||
|
|
||||||
FileEntry[] dirEntries;
|
// todo: Query the FAT for the file size when none is given
|
||||||
FileEntry[] fileEntries;
|
var dirTableStream = OpenFatBlock(Header.Save.DirectoryTableBlock, 1000000);
|
||||||
using (var reader = new BinaryReader(JournalStreamSource.CreateStream(), Encoding.Default, true))
|
var fileTableStream = OpenFatBlock(Header.Save.FileTableBlock, 1000000);
|
||||||
{
|
|
||||||
reader.BaseStream.Position = dirOffset;
|
|
||||||
dirEntries = ReadFileEntries(reader);
|
|
||||||
|
|
||||||
reader.BaseStream.Position = fileOffset;
|
FileEntry[] dirEntries = ReadFileEntries(dirTableStream);
|
||||||
fileEntries = ReadFileEntries(reader);
|
FileEntry[] fileEntries = ReadFileEntries(fileTableStream);
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var dir in dirEntries)
|
foreach (var dir in dirEntries)
|
||||||
{
|
{
|
||||||
@ -161,9 +168,11 @@ namespace LibHac.Savefile
|
|||||||
FileEntry.ResolveFilenames(Files);
|
FileEntry.ResolveFilenames(Files);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileEntry[] ReadFileEntries(BinaryReader reader)
|
private FileEntry[] ReadFileEntries(Stream stream)
|
||||||
{
|
{
|
||||||
|
var reader = new BinaryReader(stream);
|
||||||
var count = reader.ReadInt32();
|
var count = reader.ReadInt32();
|
||||||
|
|
||||||
reader.BaseStream.Position -= 4;
|
reader.BaseStream.Position -= 4;
|
||||||
|
|
||||||
var entries = new FileEntry[count];
|
var entries = new FileEntry[count];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user