mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
187 lines
7.9 KiB
C#
187 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace libhac.Savefile
|
|
{
|
|
public class Savefile
|
|
{
|
|
public Header Header { get; }
|
|
public RemapStream FileRemap { get; }
|
|
public RemapStream MetaRemap { get; }
|
|
private Stream FileStream { get; }
|
|
public JournalStream JournalStream { get; }
|
|
|
|
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 FileEntry[] Files { get; private set; }
|
|
private Dictionary<string, FileEntry> FileDict { get; }
|
|
|
|
public Savefile(Stream file, IProgressReport logger = null)
|
|
{
|
|
FileStream = file;
|
|
using (var reader = new BinaryReader(file, Encoding.Default, true))
|
|
{
|
|
Header = new Header(reader, logger);
|
|
var layout = Header.Layout;
|
|
FileRemap = new RemapStream(
|
|
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);
|
|
ReadFileInfo();
|
|
FileDict = Files.ToDictionary(x => x.FullPath, x => x);
|
|
}
|
|
}
|
|
|
|
public Stream OpenFile(string filename)
|
|
{
|
|
if (!FileDict.TryGetValue(filename, out FileEntry file))
|
|
{
|
|
throw new FileNotFoundException();
|
|
}
|
|
|
|
return OpenFile(file);
|
|
}
|
|
|
|
public Stream OpenFile(FileEntry file)
|
|
{
|
|
return new SubStream(JournalStream, file.Offset * Header.Save.BlockSize, file.Size);
|
|
}
|
|
|
|
public bool FileExists(string filename) => FileDict.ContainsKey(filename);
|
|
|
|
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(JournalStream, Encoding.Default, true))
|
|
{
|
|
JournalStream.Position = dirOffset;
|
|
dirEntries = ReadFileEntries(reader);
|
|
|
|
JournalStream.Position = fileOffset;
|
|
fileEntries = ReadFileEntries(reader);
|
|
}
|
|
|
|
foreach (var dir in dirEntries)
|
|
{
|
|
if (dir.NextIndex != 0) dir.Next = dirEntries[dir.NextIndex];
|
|
if (dir.ParentDirIndex != 0 && dir.ParentDirIndex < dirEntries.Length)
|
|
dir.ParentDir = dirEntries[dir.ParentDirIndex];
|
|
}
|
|
|
|
foreach (var file in fileEntries)
|
|
{
|
|
if (file.NextIndex != 0) file.Next = fileEntries[file.NextIndex];
|
|
if (file.ParentDirIndex != 0 && file.ParentDirIndex < dirEntries.Length)
|
|
file.ParentDir = dirEntries[file.ParentDirIndex];
|
|
}
|
|
|
|
Files = new FileEntry[fileEntries.Length - 2];
|
|
Array.Copy(fileEntries, 2, Files, 0, Files.Length);
|
|
|
|
FileEntry.ResolveFilenames(Files);
|
|
}
|
|
|
|
private FileEntry[] ReadFileEntries(BinaryReader reader)
|
|
{
|
|
var count = reader.ReadInt32();
|
|
JournalStream.Position -= 4;
|
|
|
|
var entries = new FileEntry[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
entries[i] = new FileEntry(reader);
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
}
|
|
|
|
public static class SavefileExtensions
|
|
{
|
|
public static void Extract(this Savefile save, string outDir, IProgressReport logger = null)
|
|
{
|
|
foreach (var file in save.Files)
|
|
{
|
|
var stream = save.OpenFile(file);
|
|
var outName = outDir + file.FullPath;
|
|
var dir = Path.GetDirectoryName(outName);
|
|
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
|
|
|
|
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
|
|
{
|
|
logger?.LogMessage(file.FullPath);
|
|
stream.CopyStream(outFile, stream.Length, logger);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|