LibHac/libhac/Romfs.cs

262 lines
9.3 KiB
C#
Raw Normal View History

2018-06-30 02:44:12 +02:00
using System.Collections.Generic;
2018-06-29 21:53:51 +02:00
using System.Diagnostics;
using System.IO;
using System.Linq;
2018-06-28 03:25:25 +02:00
using System.Text;
namespace libhac
{
public class Romfs
{
2018-07-03 04:21:35 +02:00
internal const int IvfcMaxLevel = 6;
2018-06-29 21:53:51 +02:00
public RomfsHeader Header { get; }
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
public RomfsDir RootDir { get; }
private Dictionary<string, RomfsFile> FileDict { get; }
private Stream Stream { get; set; }
public Romfs(Stream stream)
{
byte[] dirMetaTable;
byte[] fileMetaTable;
using (var reader = new BinaryReader(stream, Encoding.Default, true))
{
Header = new RomfsHeader(reader);
stream.Position = Header.DirMetaTableOffset;
dirMetaTable = reader.ReadBytes((int)Header.DirMetaTableSize);
stream.Position = Header.FileMetaTableOffset;
fileMetaTable = reader.ReadBytes((int)Header.FileMetaTableSize);
}
using (var reader = new BinaryReader(new MemoryStream(dirMetaTable)))
{
int position = 0;
while (position + 20 < Header.DirMetaTableSize)
{
var dir = new RomfsDir(reader) { Offset = position };
Directories.Add(dir);
if (dir.ParentOffset == position) RootDir = dir;
position = (int)reader.BaseStream.Position;
}
}
using (var reader = new BinaryReader(new MemoryStream(fileMetaTable)))
{
int position = 0;
while (position + 20 < Header.FileMetaTableSize)
{
var file = new RomfsFile(reader) { Offset = position };
Files.Add(file);
position = (int)reader.BaseStream.Position;
}
}
SetReferences();
ResolveFilenames();
FileDict = Files.ToDictionary(x => x.FullPath, x => x);
Stream = stream;
}
public Stream OpenFile(string filename)
{
2018-07-03 04:21:35 +02:00
if (!FileDict.TryGetValue(filename, out RomfsFile file))
2018-06-29 21:53:51 +02:00
{
throw new FileNotFoundException();
}
2018-07-03 04:21:35 +02:00
return OpenFile(file);
}
public Stream OpenFile(RomfsFile file)
{
return new SubStream(Stream, Header.DataOffset + file.DataOffset, file.DataLength);
2018-06-29 21:53:51 +02:00
}
2018-06-30 02:44:12 +02:00
public byte[] GetFile(string filename)
{
var stream = OpenFile(filename);
var file = new byte[stream.Length];
using (var ms = new MemoryStream(file))
{
stream.CopyTo(ms);
}
return file;
}
2018-06-29 21:53:51 +02:00
public bool FileExists(string filename) => FileDict.ContainsKey(filename);
private void SetReferences()
{
var dirDict = Directories.ToDictionary(x => x.Offset, x => x);
var fileDict = Files.ToDictionary(x => x.Offset, x => x);
foreach (var dir in Directories)
{
if (dir.ParentOffset >= 0 && dir.ParentOffset != dir.Offset) dir.Parent = dirDict[dir.ParentOffset];
if (dir.NextSiblingOffset >= 0) dir.NextSibling = dirDict[dir.NextSiblingOffset];
if (dir.FirstChildOffset >= 0) dir.FirstChild = dirDict[dir.FirstChildOffset];
if (dir.FirstFileOffset >= 0) dir.FirstFile = fileDict[dir.FirstFileOffset];
if (dir.NextDirHashOffset >= 0) dir.NextDirHash = dirDict[dir.NextDirHashOffset];
}
foreach (var file in Files)
{
if (file.ParentDirOffset >= 0) file.ParentDir = dirDict[file.ParentDirOffset];
if (file.NextSiblingOffset >= 0) file.NextSibling = fileDict[file.NextSiblingOffset];
if (file.NextFileHashOffset >= 0) file.NextFileHash = fileDict[file.NextFileHashOffset];
}
}
private void ResolveFilenames()
{
2018-06-29 23:07:19 +02:00
var list = new List<string>();
2018-06-29 21:53:51 +02:00
var sb = new StringBuilder();
2018-06-29 23:07:19 +02:00
var delimiter = "/";
2018-06-29 21:53:51 +02:00
foreach (var file in Files)
{
2018-06-29 23:07:19 +02:00
list.Add(file.Name);
2018-06-29 21:53:51 +02:00
var dir = file.ParentDir;
while (dir != null)
{
2018-06-29 23:07:19 +02:00
list.Add(delimiter);
list.Add(dir.Name);
2018-06-29 21:53:51 +02:00
dir = dir.Parent;
}
2018-06-30 02:44:12 +02:00
for (int i = list.Count - 1; i >= 0; i--)
2018-06-29 23:07:19 +02:00
{
sb.Append(list[i]);
}
2018-06-29 21:53:51 +02:00
file.FullPath = sb.ToString();
2018-06-29 23:07:19 +02:00
list.Clear();
2018-06-29 21:53:51 +02:00
sb.Clear();
}
}
2018-06-28 03:25:25 +02:00
}
public class RomfsHeader
{
2018-06-29 21:53:51 +02:00
public long HeaderSize { get; }
public long DirHashTableOffset { get; }
public long DirHashTableSize { get; }
public long DirMetaTableOffset { get; }
public long DirMetaTableSize { get; }
public long FileHashTableOffset { get; }
public long FileHashTableSize { get; }
public long FileMetaTableOffset { get; }
public long FileMetaTableSize { get; }
public long DataOffset { get; }
public RomfsHeader(BinaryReader reader)
{
HeaderSize = reader.ReadInt64();
DirHashTableOffset = reader.ReadInt64();
DirHashTableSize = reader.ReadInt64();
DirMetaTableOffset = reader.ReadInt64();
DirMetaTableSize = reader.ReadInt64();
FileHashTableOffset = reader.ReadInt64();
FileHashTableSize = reader.ReadInt64();
FileMetaTableOffset = reader.ReadInt64();
FileMetaTableSize = reader.ReadInt64();
DataOffset = reader.ReadInt64();
}
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class RomfsDir
{
public int Offset { get; set; }
public int ParentOffset { get; }
public int NextSiblingOffset { get; }
public int FirstChildOffset { get; }
public int FirstFileOffset { get; }
public int NextDirHashOffset { get; }
public int NameLength { get; }
public string Name { get; }
public RomfsDir Parent { get; internal set; }
public RomfsDir NextSibling { get; internal set; }
public RomfsDir FirstChild { get; internal set; }
public RomfsFile FirstFile { get; internal set; }
public RomfsDir NextDirHash { get; internal set; }
public RomfsDir(BinaryReader reader)
{
ParentOffset = reader.ReadInt32();
NextSiblingOffset = reader.ReadInt32();
FirstChildOffset = reader.ReadInt32();
FirstFileOffset = reader.ReadInt32();
NextDirHashOffset = reader.ReadInt32();
NameLength = reader.ReadInt32();
Name = reader.ReadUtf8(NameLength);
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
}
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class RomfsFile
{
public int Offset { get; set; }
public int ParentDirOffset { get; }
public int NextSiblingOffset { get; }
public long DataOffset { get; }
public long DataLength { get; }
public int NextFileHashOffset { get; }
public int NameLength { get; }
public string Name { get; }
public RomfsDir ParentDir { get; internal set; }
public RomfsFile NextSibling { get; internal set; }
public RomfsFile NextFileHash { get; internal set; }
public string FullPath { get; set; }
public RomfsFile(BinaryReader reader)
{
ParentDirOffset = reader.ReadInt32();
NextSiblingOffset = reader.ReadInt32();
DataOffset = reader.ReadInt64();
DataLength = reader.ReadInt64();
NextFileHashOffset = reader.ReadInt32();
NameLength = reader.ReadInt32();
Name = reader.ReadUtf8(NameLength);
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
}
2018-06-28 03:25:25 +02:00
}
public class IvfcLevel
{
2018-07-05 23:37:30 +02:00
public long DataOffset { get; set; }
public long DataSize { get; set; }
public long HashOffset { get; set; }
public long HashSize { get; set; }
public long HashBlockSize { get; set; }
public long HashBlockCount { get; set; }
public Validity HashValidity { get; set; }
2018-06-28 03:25:25 +02:00
}
2018-07-03 04:21:35 +02:00
public static class RomfsExtensions
{
public static void Extract(this Romfs romfs, string outDir, IProgressReport logger = null)
{
foreach (var file in romfs.Files)
{
var stream = romfs.OpenFile(file);
var outName = outDir + file.FullPath;
2018-07-05 23:37:30 +02:00
var dir = Path.GetDirectoryName(outName);
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
2018-07-03 04:21:35 +02:00
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
{
logger?.LogMessage(file.FullPath);
stream.CopyStream(outFile, stream.Length, logger);
}
}
}
}
2018-06-28 03:25:25 +02:00
}