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; }
|
||||
private JournalStream JournalStream { get; }
|
||||
public SharedStreamSource JournalStreamSource { get; }
|
||||
private AllocationTable AllocationTable { get; }
|
||||
|
||||
public Stream DuplexL1A { get; }
|
||||
public Stream DuplexL1B { get; }
|
||||
@ -88,6 +89,7 @@ namespace LibHac.Savefile
|
||||
JournalLayer2Hash = MetaRemapSource.CreateStream(layout.Layer2HashOffset, layout.Layer2HashSize);
|
||||
JournalLayer3Hash = MetaRemapSource.CreateStream(layout.Layer3HashOffset, layout.Layer3HashSize);
|
||||
JournalFat = MetaRemapSource.CreateStream(layout.Field148, layout.Field150);
|
||||
AllocationTable = new AllocationTable(JournalFat);
|
||||
|
||||
var journalMap = JournalStream.ReadMappingEntries(JournalTable, Header.Journal.MappingEntryCount);
|
||||
|
||||
@ -118,7 +120,18 @@ namespace LibHac.Savefile
|
||||
|
||||
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);
|
||||
@ -126,19 +139,13 @@ namespace LibHac.Savefile
|
||||
private void ReadFileInfo()
|
||||
{
|
||||
var blockSize = Header.Save.BlockSize;
|
||||
var dirOffset = Header.Save.DirectoryTableBlock * blockSize;
|
||||
var fileOffset = Header.Save.FileTableBlock * blockSize;
|
||||
|
||||
FileEntry[] dirEntries;
|
||||
FileEntry[] fileEntries;
|
||||
using (var reader = new BinaryReader(JournalStreamSource.CreateStream(), Encoding.Default, true))
|
||||
{
|
||||
reader.BaseStream.Position = dirOffset;
|
||||
dirEntries = ReadFileEntries(reader);
|
||||
// todo: Query the FAT for the file size when none is given
|
||||
var dirTableStream = OpenFatBlock(Header.Save.DirectoryTableBlock, 1000000);
|
||||
var fileTableStream = OpenFatBlock(Header.Save.FileTableBlock, 1000000);
|
||||
|
||||
reader.BaseStream.Position = fileOffset;
|
||||
fileEntries = ReadFileEntries(reader);
|
||||
}
|
||||
FileEntry[] dirEntries = ReadFileEntries(dirTableStream);
|
||||
FileEntry[] fileEntries = ReadFileEntries(fileTableStream);
|
||||
|
||||
foreach (var dir in dirEntries)
|
||||
{
|
||||
@ -161,9 +168,11 @@ namespace LibHac.Savefile
|
||||
FileEntry.ResolveFilenames(Files);
|
||||
}
|
||||
|
||||
private FileEntry[] ReadFileEntries(BinaryReader reader)
|
||||
private FileEntry[] ReadFileEntries(Stream stream)
|
||||
{
|
||||
var reader = new BinaryReader(stream);
|
||||
var count = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Position -= 4;
|
||||
|
||||
var entries = new FileEntry[count];
|
||||
|
Loading…
x
Reference in New Issue
Block a user