Remove old Pfs and Romfs classes

This commit is contained in:
Alex Barney 2019-01-02 18:16:19 -07:00
parent 9a234575ac
commit 585c351917
14 changed files with 117 additions and 403 deletions

View File

@ -0,0 +1,31 @@
using System;
namespace LibHac.IO
{
public class FileStorage : Storage
{
private IFile BaseFile { get; }
public FileStorage(IFile baseFile)
{
BaseFile = baseFile;
}
protected override void ReadImpl(Span<byte> destination, long offset)
{
BaseFile.Read(destination, offset);
}
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
{
BaseFile.Write(source, offset);
}
public override void Flush()
{
BaseFile.Flush();
}
public override long Length => BaseFile.GetSize();
}
}

View File

@ -101,8 +101,8 @@ namespace LibHac.IO
public enum PartitionFileSystemType
{
Pfs0,
Hfs0
Standard,
Hashed
}
public class PartitionFileSystemHeader
@ -125,10 +125,10 @@ namespace LibHac.IO
switch (Magic)
{
case "PFS0":
Type = PartitionFileSystemType.Pfs0;
Type = PartitionFileSystemType.Standard;
break;
case "HFS0":
Type = PartitionFileSystemType.Hfs0;
Type = PartitionFileSystemType.Hashed;
break;
default:
throw new InvalidDataException($"Invalid Partition FS type \"{Magic}\"");
@ -151,7 +151,7 @@ namespace LibHac.IO
}
if (Type == PartitionFileSystemType.Hfs0)
if (Type == PartitionFileSystemType.Hashed)
{
for (int i = 0; i < NumFiles; i++)
{
@ -166,9 +166,9 @@ namespace LibHac.IO
{
switch (type)
{
case PartitionFileSystemType.Pfs0:
case PartitionFileSystemType.Standard:
return 24;
case PartitionFileSystemType.Hfs0:
case PartitionFileSystemType.Hashed:
return 0x40;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
@ -193,7 +193,7 @@ namespace LibHac.IO
Offset = reader.ReadInt64();
Size = reader.ReadInt64();
StringTableOffset = reader.ReadUInt32();
if (type == PartitionFileSystemType.Hfs0)
if (type == PartitionFileSystemType.Hashed)
{
HashedRegionSize = reader.ReadInt32();
Reserved = reader.ReadInt64();

View File

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace LibHac.IO
@ -13,15 +12,14 @@ namespace LibHac.IO
// See the LICENSE file in the project root for more information.
public static string Normalize(string inPath)
{
// Relative paths aren't a thing for IFileSystem, so assume all paths are absolute
// and add a '/' to the beginning of the path if it doesn't already begin with one
if (inPath.Length == 0 || !IsDirectorySeparator(inPath[0])) inPath = DirectorySeparator + inPath;
ReadOnlySpan<char> path = inPath.AsSpan();
if (path.Length == 0) return DirectorySeparator.ToString();
if (path[0] != DirectorySeparator)
{
throw new InvalidDataException($"{nameof(path)} must begin with '{DirectorySeparator}'");
}
Span<char> initialBuffer = stackalloc char[0x200];
var sb = new ValueStringBuilder(initialBuffer);

View File

@ -161,5 +161,38 @@ namespace LibHac.IO
return FileDict.ContainsKey(path);
}
public IStorage GetBaseStorage()
{
return BaseStorage;
}
}
public class RomfsHeader
{
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();
}
}
}

View File

@ -297,9 +297,11 @@ namespace LibHac
{
if (Header.ContentType != ContentType.Program) return;
var pfs = new Pfs(OpenSection(ProgramPartitionType.Code, false, IntegrityCheckLevel.ErrorOnInvalid, true));
var pfs = new PartitionFileSystem(OpenSection(ProgramPartitionType.Code, false, IntegrityCheckLevel.ErrorOnInvalid, true));
if (!pfs.TryOpenFile("main.npdm", out IStorage npdmStorage)) return;
if (!pfs.FileExists("main.npdm")) return;
var npdmStorage = new FileStorage(pfs.OpenFile("main.npdm", OpenMode.Read));
Npdm = new Npdm.NpdmBinary(npdmStorage.AsStream(), Keyset);
@ -523,11 +525,11 @@ namespace LibHac
case SectionType.Invalid:
break;
case SectionType.Pfs0:
var pfs0 = new Pfs(storage);
var pfs0 = new PartitionFileSystem(storage);
pfs0.Extract(outputDir, logger);
break;
case SectionType.Romfs:
var romfs = new Romfs(storage);
var romfs = new RomFsFileSystem(storage);
romfs.Extract(outputDir, logger);
break;
case SectionType.Bktr:

View File

@ -1,191 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using LibHac.IO;
namespace LibHac
{
public class Pfs
{
public PfsHeader Header { get; }
public int HeaderSize { get; }
public PfsFileEntry[] Files { get; }
private Dictionary<string, PfsFileEntry> FileDict { get; }
private IStorage BaseStorage { get; }
public Pfs(IStorage storage)
{
using (var reader = new BinaryReader(storage.AsStream(), Encoding.Default, true))
{
Header = new PfsHeader(reader);
}
HeaderSize = Header.HeaderSize;
Files = Header.Files;
FileDict = Header.Files.ToDictionary(x => x.Name, x => x);
BaseStorage = storage;
}
public IStorage OpenFile(string filename)
{
if (!FileDict.TryGetValue(filename, out PfsFileEntry file))
{
throw new FileNotFoundException();
}
return OpenFile(file);
}
public bool TryOpenFile(string filename, out IStorage storage)
{
if (!FileDict.TryGetValue(filename, out PfsFileEntry file))
{
storage = null;
return false;
}
storage = OpenFile(file);
return true;
}
public IStorage OpenFile(PfsFileEntry file)
{
return BaseStorage.Slice(HeaderSize + file.Offset, file.Size);
}
public bool FileExists(string filename)
{
return FileDict.ContainsKey(filename);
}
}
public enum PfsType
{
Pfs0,
Hfs0
}
public class PfsHeader
{
public string Magic;
public int NumFiles;
public int StringTableSize;
public long Reserved;
public PfsType Type;
public int HeaderSize;
public PfsFileEntry[] Files;
public PfsHeader(BinaryReader reader)
{
Magic = reader.ReadAscii(4);
NumFiles = reader.ReadInt32();
StringTableSize = reader.ReadInt32();
Reserved = reader.ReadInt32();
switch (Magic)
{
case "PFS0":
Type = PfsType.Pfs0;
break;
case "HFS0":
Type = PfsType.Hfs0;
break;
default:
throw new InvalidDataException($"Invalid Partition FS type \"{Magic}\"");
}
int entrysize = GetFileEntrySize(Type);
int stringTableOffset = 16 + entrysize * NumFiles;
HeaderSize = stringTableOffset + StringTableSize;
Files = new PfsFileEntry[NumFiles];
for (int i = 0; i < NumFiles; i++)
{
Files[i] = new PfsFileEntry(reader, Type) { Index = i };
}
for (int i = 0; i < NumFiles; i++)
{
reader.BaseStream.Position = stringTableOffset + Files[i].StringTableOffset;
Files[i].Name = reader.ReadAsciiZ();
}
if (Type == PfsType.Hfs0)
{
for (int i = 0; i < NumFiles; i++)
{
reader.BaseStream.Position = HeaderSize + Files[i].Offset;
Files[i].HashValidity = Crypto.CheckMemoryHashTable(reader.ReadBytes(Files[i].HashedRegionSize), Files[i].Hash, 0, Files[i].HashedRegionSize);
}
}
}
private static int GetFileEntrySize(PfsType type)
{
switch (type)
{
case PfsType.Pfs0:
return 24;
case PfsType.Hfs0:
return 0x40;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
}
public class PfsFileEntry
{
public int Index;
public long Offset;
public long Size;
public uint StringTableOffset;
public long Reserved;
public int HashedRegionSize;
public byte[] Hash;
public string Name;
public Validity HashValidity = Validity.Unchecked;
public PfsFileEntry(BinaryReader reader, PfsType type)
{
Offset = reader.ReadInt64();
Size = reader.ReadInt64();
StringTableOffset = reader.ReadUInt32();
if (type == PfsType.Hfs0)
{
HashedRegionSize = reader.ReadInt32();
Reserved = reader.ReadInt64();
Hash = reader.ReadBytes(Crypto.Sha256DigestSize);
}
else
{
Reserved = reader.ReadUInt32();
}
}
}
public static class PfsExtensions
{
public static void Extract(this Pfs pfs, string outDir, IProgressReport logger = null)
{
foreach (PfsFileEntry file in pfs.Header.Files)
{
IStorage storage = pfs.OpenFile(file);
string outName = Path.Combine(outDir, file.Name);
string dir = Path.GetDirectoryName(outName);
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
{
logger?.LogMessage(file.Name);
storage.CopyToStream(outFile, storage.Length, logger);
}
}
}
}
}

View File

@ -1,163 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using LibHac.IO;
namespace LibHac
{
public class Romfs
{
public const int IvfcMaxLevel = 6;
public RomfsHeader Header { get; }
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
public RomfsDir RootDir { get; }
public Dictionary<string, RomfsFile> FileDict { get; }
private IStorage BaseStorage { get; }
public Romfs(IStorage storage)
{
BaseStorage = storage;
byte[] dirMetaTable;
byte[] fileMetaTable;
using (var reader = new BinaryReader(BaseStorage.AsStream(), Encoding.Default, true))
{
Header = new RomfsHeader(reader);
reader.BaseStream.Position = Header.DirMetaTableOffset;
dirMetaTable = reader.ReadBytes((int)Header.DirMetaTableSize);
reader.BaseStream.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.ParentDirOffset == 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();
RomfsEntry.ResolveFilenames(Files);
RomfsEntry.ResolveFilenames(Directories);
FileDict = Files.ToDictionary(x => x.FullPath, x => x);
}
public IStorage OpenFile(string filename)
{
if (!FileDict.TryGetValue(filename, out RomfsFile file))
{
throw new FileNotFoundException();
}
return OpenFile(file);
}
public IStorage OpenFile(RomfsFile file)
{
return BaseStorage.Slice(Header.DataOffset + file.DataOffset, file.DataLength);
}
public byte[] GetFile(string filename)
{
IStorage storage = OpenFile(filename);
var file = new byte[storage.Length];
storage.Read(file, 0);
return file;
}
public bool FileExists(string filename) => FileDict.ContainsKey(filename);
public IStorage OpenRawStream() => BaseStorage.Slice(0);
private void SetReferences()
{
Dictionary<int, RomfsDir> dirDict = Directories.ToDictionary(x => x.Offset, x => x);
Dictionary<int, RomfsFile> fileDict = Files.ToDictionary(x => x.Offset, x => x);
foreach (RomfsDir dir in Directories)
{
if (dir.ParentDirOffset >= 0 && dir.ParentDirOffset != dir.Offset) dir.ParentDir = dirDict[dir.ParentDirOffset];
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 (RomfsFile 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];
}
}
}
public class RomfsHeader
{
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();
}
}
public static class RomfsExtensions
{
public static void Extract(this Romfs romfs, string outDir, IProgressReport logger = null)
{
foreach (RomfsFile file in romfs.Files)
{
IStorage storage = romfs.OpenFile(file);
string outName = outDir + file.FullPath;
string 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);
storage.CopyToStream(outFile, storage.Length, logger);
}
}
}
}
}

View File

@ -149,10 +149,10 @@ namespace LibHac
// Meta contents always have 1 Partition FS section with 1 file in it
IStorage sect = nca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true);
var pfs0 = new Pfs(sect);
IStorage file = pfs0.OpenFile(pfs0.Files[0]);
var pfs0 = new PartitionFileSystem(sect);
IFile file = pfs0.OpenFile(pfs0.Files[0], OpenMode.Read);
var metadata = new Cnmt(file.AsStream());
var metadata = new Cnmt(new FileStorage(file).AsStream());
title.Id = metadata.TitleId;
title.Version = metadata.TitleVersion;
title.Metadata = metadata;
@ -188,8 +188,8 @@ namespace LibHac
{
foreach (Title title in Titles.Values.Where(x => x.ControlNca != null))
{
var romfs = new Romfs(title.ControlNca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true));
IStorage control = romfs.OpenFile("/control.nacp");
var romfs = new RomFsFileSystem(title.ControlNca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid, true));
IStorage control = new FileStorage(romfs.OpenFile("control.nacp", OpenMode.Read));
title.Control = new Nacp(control.AsStream());

View File

@ -25,9 +25,9 @@ namespace LibHac
public Xci(Keyset keyset, IStorage storage)
{
Header = new XciHeader(keyset, storage.AsStream());
IStorage hfs0Stream = storage.Slice(Header.PartitionFsHeaderAddress);
IStorage hfs0Storage = storage.Slice(Header.PartitionFsHeaderAddress);
RootPartition = new XciPartition(hfs0Stream)
RootPartition = new XciPartition(hfs0Storage)
{
Name = RootPartitionName,
Offset = Header.PartitionFsHeaderAddress,
@ -36,11 +36,11 @@ namespace LibHac
Partitions.Add(RootPartition);
foreach (PfsFileEntry file in RootPartition.Files)
foreach (PartitionFileEntry file in RootPartition.Files)
{
IStorage partitionStorage = RootPartition.OpenFile(file);
IFile partitionFile = RootPartition.OpenFile(file, OpenMode.Read);
var partition = new XciPartition(partitionStorage)
var partition = new XciPartition(new FileStorage(partitionFile))
{
Name = file.Name,
Offset = Header.PartitionFsHeaderAddress + RootPartition.HeaderSize + file.Offset,
@ -57,7 +57,7 @@ namespace LibHac
}
}
public class XciPartition : Pfs
public class XciPartition : PartitionFileSystem
{
public string Name { get; internal set; }
public long Offset { get; internal set; }

View File

@ -44,7 +44,7 @@ namespace hactoolnet
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
{
var romfs = new Romfs(nca.OpenSection(1, false, ctx.Options.IntegrityLevel, true));
var romfs = new RomFsFileSystem(nca.OpenSection(1, false, ctx.Options.IntegrityLevel, true));
foreach (RomfsFile romfsFile in romfs.Files)
{

View File

@ -10,12 +10,12 @@ namespace hactoolnet
{
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
{
var romfs = new Romfs(file.AsStorage());
var romfs = new RomFsFileSystem(file.AsStorage());
Process(ctx, romfs);
}
}
public static void Process(Context ctx, Romfs romfs)
public static void Process(Context ctx, RomFsFileSystem romfs)
{
if (ctx.Options.ListRomFs)
{
@ -29,7 +29,7 @@ namespace hactoolnet
{
using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite))
{
IStorage romfsStorage = romfs.OpenRawStream();
IStorage romfsStorage = romfs.GetBaseStorage();
romfsStorage.CopyToStream(outFile, romfsStorage.Length, ctx.Logger);
}
}

View File

@ -101,7 +101,7 @@ namespace hactoolnet
if (ctx.Options.RomfsOutDir != null)
{
var romfs = new Romfs(title.MainNca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel, true));
var romfs = new RomFsFileSystem(title.MainNca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel, true));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}

View File

@ -51,9 +51,9 @@ namespace hactoolnet
return;
}
foreach (PfsFileEntry sub in root.Files)
foreach (PartitionFileEntry sub in root.Files)
{
var subPfs = new Pfs(root.OpenFile(sub));
var subPfs = new PartitionFileSystem(new FileStorage(root.OpenFile(sub, OpenMode.Read)));
string subDir = Path.Combine(ctx.Options.OutDir, sub.Name);
subPfs.Extract(subDir, ctx.Logger);
@ -109,7 +109,7 @@ namespace hactoolnet
if (ctx.Options.RomfsOutDir != null)
{
var romfs = new Romfs(mainNca.OpenSection(romfsSection.SectionNum, false, ctx.Options.IntegrityLevel, true));
var romfs = new RomFsFileSystem(mainNca.OpenSection(romfsSection.SectionNum, false, ctx.Options.IntegrityLevel, true));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}
@ -131,9 +131,9 @@ namespace hactoolnet
Nca mainNca = null;
foreach (PfsFileEntry fileEntry in xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca")))
foreach (PartitionFileEntry fileEntry in xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca")))
{
IStorage ncaStorage = xci.SecurePartition.OpenFile(fileEntry);
IStorage ncaStorage = new FileStorage(xci.SecurePartition.OpenFile(fileEntry, OpenMode.Read));
var nca = new Nca(ctx.Keyset, ncaStorage, true);
if (nca.Header.ContentType == ContentType.Program)
@ -182,7 +182,7 @@ namespace hactoolnet
{
for (int i = 0; i < partition.Files.Length; i++)
{
PfsFileEntry file = partition.Files[i];
PartitionFileEntry file = partition.Files[i];
string label = i == 0 ? " Files:" : "";
string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}";

View File

@ -1,5 +1,4 @@
using System.IO;
using LibHac.IO;
using LibHac.IO;
using Xunit;
namespace LibHac.Tests
@ -18,13 +17,24 @@ namespace LibHac.Tests
new object[] {"/a/../../..", "/"},
new object[] {"/a/../../../a/b/c", "/a/b/c"},
new object[] {"//a/b//.//c", "/a/b/c"},
new object[] {"/../a/b/c/.", "/a/b/c"},
new object[] {"/./a/b/c/.", "/a/b/c"},
new object[] {"/a/b/c/", "/a/b/c/"},
new object[] {"/a/./b/../c/", "/a/c/"},
new object[] {"/./b/../c/", "/c/"},
new object[] {"/a/../../../", "/"},
new object[] {"//a/b//.//c/", "/a/b/c/"},
new object[] {@"/tmp/../", @"/"},
new object[] {"/tmp/../", "/"},
new object[] {"a", "/a"},
new object[] {"a/../../../a/b/c", "/a/b/c"},
new object[] {"./b/../c/", "/c/"},
new object[] {".", "/"},
new object[] {"..", "/"},
new object[] {"../a/b/c/.", "/a/b/c"},
new object[] {"./a/b/c/.", "/a/b/c"},
};
[Theory]
@ -35,11 +45,5 @@ namespace LibHac.Tests
Assert.Equal(expected, actual);
}
[Fact]
public static void NormalizeThrowsOnInvalidStartChar()
{
Assert.Throws<InvalidDataException>(() => PathTools.Normalize(@"c:\a\b\c"));
}
}
}