Add NintendoSubmissionPackageRootFileSystem

This commit is contained in:
Alex Barney 2024-07-15 20:57:40 -07:00
parent 436f71e6a7
commit 3f845e2964
5 changed files with 313 additions and 16 deletions

View file

@ -69,3 +69,46 @@ public struct Sha256PartitionFileSystemFormat : IPartitionFileSystemFormat
readonly int IPartitionFileSystemEntry.NameOffset => NameOffset;
}
}
public struct NintendoSubmissionPackageRootFileSystemFormat : IPartitionFileSystemFormat
{
public static ReadOnlySpan<byte> VersionSignature => "NSP1"u8;
public static uint EntryNameLengthMax => PathTool.EntryNameLengthMax;
public static uint FileDataAlignmentSize => 0x20;
public static Result ResultSignatureVerificationFailed => ResultFs.PartitionSignatureVerificationFailed.Value;
[StructLayout(LayoutKind.Sequential)]
public struct PartitionEntry : IPartitionFileSystemEntry
{
public long Offset;
public long Size;
public int NameOffset;
public uint Reserved;
readonly long IPartitionFileSystemEntry.Offset => Offset;
readonly long IPartitionFileSystemEntry.Size => Size;
readonly int IPartitionFileSystemEntry.NameOffset => NameOffset;
}
[StructLayout(LayoutKind.Sequential)]
public struct PartitionFileSystemHeaderImpl : IPartitionFileSystemHeader
{
private Array4<byte> _signature;
public int EntryCount;
public int NameTableSize;
public byte TargetPlatform;
public long TotalSize;
public readonly ReadOnlySpan<byte> Signature
{
get
{
ReadOnlySpan<byte> span = _signature;
return MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(span), span.Length);
}
}
readonly int IPartitionFileSystemHeader.EntryCount => EntryCount;
readonly int IPartitionFileSystemHeader.NameTableSize => NameTableSize;
}
}

View file

@ -0,0 +1,194 @@
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem.Impl;
using LibHac.Util;
namespace LibHac.FsSystem;
using NspRootFileSystemCore =
PartitionFileSystemCore<NintendoSubmissionPackageRootFileSystemMeta, NintendoSubmissionPackageRootFileSystemFormat,
NintendoSubmissionPackageRootFileSystemFormat.PartitionFileSystemHeaderImpl,
NintendoSubmissionPackageRootFileSystemFormat.PartitionEntry>;
/// <summary>
/// Reads a standard partition file system of version 0 or version 1. These files start with "PFS0" or "PFS1" respectively.
/// </summary>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public class NintendoSubmissionPackageRootFileSystem : IFileSystem
{
private Optional<NspRootFileSystemCore> _nspRootFileSystem;
private Optional<PartitionFileSystem> _partitionFileSystem;
public NintendoSubmissionPackageRootFileSystem()
{
_nspRootFileSystem = new Optional<NspRootFileSystemCore>();
_partitionFileSystem = new Optional<PartitionFileSystem>();
}
public override void Dispose()
{
if (_nspRootFileSystem.HasValue)
{
_nspRootFileSystem.Value.Dispose();
_nspRootFileSystem.Clear();
}
if (_partitionFileSystem.HasValue)
{
_partitionFileSystem.Value.Dispose();
_partitionFileSystem.Clear();
}
base.Dispose();
}
public Result Initialize(ref readonly SharedRef<IStorage> baseStorage)
{
return InitializeImpl(in baseStorage).Ret();
}
protected override Result DoOpenFile(ref UniqueRef<IFile> outFile, ref readonly Path path, OpenMode mode)
{
return GetImpl().OpenFile(ref outFile, in path, mode).Ret();
}
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> outDirectory, ref readonly Path path, OpenDirectoryMode mode)
{
return GetImpl().OpenDirectory(ref outDirectory, in path, mode).Ret();
}
protected override Result DoGetEntryType(out DirectoryEntryType entryType, ref readonly Path path)
{
return GetImpl().GetEntryType(out entryType, in path).Ret();
}
protected override Result DoCreateFile(ref readonly Path path, long size, CreateFileOptions option)
{
return GetImpl().CreateFile(in path, size, option).Ret();
}
protected override Result DoDeleteFile(ref readonly Path path)
{
return GetImpl().DeleteFile(in path).Ret();
}
protected override Result DoCreateDirectory(ref readonly Path path)
{
return GetImpl().CreateDirectory(in path).Ret();
}
protected override Result DoDeleteDirectory(ref readonly Path path)
{
return GetImpl().DeleteDirectory(in path).Ret();
}
protected override Result DoDeleteDirectoryRecursively(ref readonly Path path)
{
return GetImpl().DeleteDirectoryRecursively(in path).Ret();
}
protected override Result DoCleanDirectoryRecursively(ref readonly Path path)
{
return GetImpl().CleanDirectoryRecursively(in path).Ret();
}
protected override Result DoRenameFile(ref readonly Path currentPath, ref readonly Path newPath)
{
return GetImpl().RenameFile(in currentPath, in newPath).Ret();
}
protected override Result DoRenameDirectory(ref readonly Path currentPath, ref readonly Path newPath)
{
return GetImpl().RenameDirectory(in currentPath, in newPath).Ret();
}
protected override Result DoCommit()
{
return GetImpl().Commit().Ret();
}
protected override Result DoCommitProvisionally(long counter)
{
return GetImpl().CommitProvisionally(counter).Ret();
}
private Result InitializeImpl(ref readonly SharedRef<IStorage> baseStorage)
{
bool successNsp = false;
try
{
if (_nspRootFileSystem.HasValue)
_nspRootFileSystem.Value.Dispose();
// First try to open the file system as an NspRootFileSystem
_nspRootFileSystem.Set(new NspRootFileSystemCore());
Result res = _nspRootFileSystem.Value.Initialize(in baseStorage);
if (!res.IsSuccess())
{
if (ResultFs.PartitionSignatureVerificationFailed.Includes(res))
{
// If that fails, try to open the file system as a PartitionFileSystem
bool successPfs = false;
try
{
if (_partitionFileSystem.HasValue)
_partitionFileSystem.Value.Dispose();
_partitionFileSystem.Set(new PartitionFileSystem());
res = _partitionFileSystem.Value.Initialize(in baseStorage);
if (res.IsFailure()) return res.Miss();
successPfs = true;
return Result.Success;
}
finally
{
if (!successPfs)
{
if (_partitionFileSystem.HasValue)
{
_partitionFileSystem.Value.Dispose();
_partitionFileSystem.Clear();
}
}
}
}
else
{
return res.Miss();
}
}
else
{
successNsp = true;
return Result.Success;
}
}
finally
{
if (!successNsp)
{
if (_nspRootFileSystem.HasValue)
{
_nspRootFileSystem.Value.Dispose();
_nspRootFileSystem.Clear();
}
}
}
}
private IFileSystem GetImpl()
{
if (_nspRootFileSystem.HasValue)
return _nspRootFileSystem.Value;
Assert.SdkAssert(_partitionFileSystem.HasValue);
return _partitionFileSystem.Value;
}
}

View file

@ -6,11 +6,17 @@ using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem.Impl;
using LibHac.Util;
using Buffer = LibHac.Mem.Buffer;
namespace LibHac.FsSystem;
using NspRootFileSystemCore =
PartitionFileSystemCore<NintendoSubmissionPackageRootFileSystemMeta, NintendoSubmissionPackageRootFileSystemFormat,
NintendoSubmissionPackageRootFileSystemFormat.PartitionFileSystemHeaderImpl,
NintendoSubmissionPackageRootFileSystemFormat.PartitionEntry>;
/// <summary>
/// The allocator used by a <see cref="PartitionFileSystemCore{TMetaData,TFormat,THeader,TEntry}"/> when none is provided.
/// </summary>
@ -52,18 +58,18 @@ file sealed class DefaultAllocatorForPartitionFileSystem : MemoryResource
/// </summary>
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
public class PartitionFileSystem : PartitionFileSystemCore<PartitionFileSystemMeta,
Impl.PartitionFileSystemFormat,
Impl.PartitionFileSystemFormat.PartitionFileSystemHeaderImpl,
Impl.PartitionFileSystemFormat.PartitionEntry> { }
PartitionFileSystemFormat,
PartitionFileSystemFormat.PartitionFileSystemHeaderImpl,
PartitionFileSystemFormat.PartitionEntry>;
/// <summary>
/// Reads a hashed partition file system. These files start with "HFS0" and are typically found inside XCIs.
/// </summary>
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
public class Sha256PartitionFileSystem : PartitionFileSystemCore<Sha256PartitionFileSystemMeta,
Impl.Sha256PartitionFileSystemFormat,
Impl.PartitionFileSystemFormat.PartitionFileSystemHeaderImpl,
Impl.Sha256PartitionFileSystemFormat.PartitionEntry> { }
Sha256PartitionFileSystemFormat,
PartitionFileSystemFormat.PartitionFileSystemHeaderImpl,
Sha256PartitionFileSystemFormat.PartitionEntry>;
/// <summary>
/// Provides the base for an <see cref="IFileSystem"/> that can read from different partition file system files.
@ -197,6 +203,11 @@ public class PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> : IFil
return DoRead(fileSha, out bytesRead, offset, destination, in option).Ret();
}
if (this is NspRootFileSystemCore.PartitionFile fileNsp)
{
return DoRead(fileNsp, out bytesRead, offset, destination, in option).Ret();
}
UnsafeHelpers.SkipParamInit(out bytesRead);
Abort.DoAbort("PartitionFileSystemCore.PartitionFile type is not supported.");
return ResultFs.NotImplemented.Log();
@ -330,6 +341,22 @@ public class PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> : IFil
bytesRead = readSize;
return Result.Success;
}
private static Result DoRead(NspRootFileSystemCore.PartitionFile fs, out long bytesRead, long offset,
Span<byte> destination, in ReadOption option)
{
UnsafeHelpers.SkipParamInit(out bytesRead);
Result res = fs.DryRead(out long readSize, offset, destination.Length, in option, fs._mode);
if (res.IsFailure()) return res.Miss();
res = fs._parent._baseStorage.Read(fs._parent._metaDataSize + fs._partitionEntry.Offset + offset,
destination.Slice(0, (int)readSize));
if (res.IsFailure()) return res.Miss();
bytesRead = readSize;
return Result.Success;
}
}
/// <summary>

View file

@ -73,7 +73,7 @@ namespace LibHac.FsSystem
protected MemoryResource Allocator;
protected Buffer MetaDataBuffer;
private ref readonly THeader Header => ref MemoryMarshal.GetReference(HeaderBuffer.GetSpan<THeader>());
protected ref readonly THeader Header => ref MemoryMarshal.GetReference(HeaderBuffer.GetSpan<THeader>());
private ReadOnlySpan<TEntry> Entries => EntryBuffer.GetSpan<TEntry>();
private ReadOnlySpan<byte> NameTable => NameTableBuffer.Span;
@ -356,11 +356,23 @@ namespace LibHac.FsSystem
return Result.Success;
}
}
}
namespace LibHac.FsSystem
{
/// <summary>
/// Reads the metadata for a <see cref="PartitionFileSystem"/>.
/// </summary>
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
public class PartitionFileSystemMeta : PartitionFileSystemMetaCore<PartitionFileSystemFormat,
PartitionFileSystemFormat.PartitionFileSystemHeaderImpl, PartitionFileSystemFormat.PartitionEntry> { }
public class NintendoSubmissionPackageRootFileSystemMeta : PartitionFileSystemMetaCore<
NintendoSubmissionPackageRootFileSystemFormat,
NintendoSubmissionPackageRootFileSystemFormat.PartitionFileSystemHeaderImpl,
NintendoSubmissionPackageRootFileSystemFormat.PartitionEntry>
{
public byte GetTargetPlatform() => Header.TargetPlatform;
public long GetTotalSize() => Header.TotalSize;
}
}

View file

@ -8,6 +8,7 @@ using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.FsSystem.Impl;
using LibHac.Tools.Es;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
@ -17,6 +18,11 @@ using Path = LibHac.Fs.Path;
namespace hactoolnet;
using NspRootFileSystemCore =
PartitionFileSystemCore<NintendoSubmissionPackageRootFileSystemMeta, NintendoSubmissionPackageRootFileSystemFormat,
NintendoSubmissionPackageRootFileSystemFormat.PartitionFileSystemHeaderImpl,
NintendoSubmissionPackageRootFileSystemFormat.PartitionEntry>;
internal static class ProcessPfs
{
public static void Process(Context ctx)
@ -26,9 +32,12 @@ internal static class ProcessPfs
IFileSystem fs = null;
using UniqueRef<PartitionFileSystem> pfs = new UniqueRef<PartitionFileSystem>();
using UniqueRef<Sha256PartitionFileSystem> hfs = new UniqueRef<Sha256PartitionFileSystem>();
using UniqueRef<NspRootFileSystemCore> nsp = new UniqueRef<NspRootFileSystemCore>();
using var sharedFile = new SharedRef<IStorage>(file);
pfs.Reset(new PartitionFileSystem());
Result res = pfs.Get.Initialize(file);
Result res = pfs.Get.Initialize(in sharedFile);
if (res.IsSuccess())
{
fs = pfs.Get;
@ -43,18 +52,30 @@ internal static class ProcessPfs
// Reading the input as a PartitionFileSystem didn't work. Try reading it as an Sha256PartitionFileSystem
hfs.Reset(new Sha256PartitionFileSystem());
res = hfs.Get.Initialize(file);
if (res.IsFailure())
if (res.IsSuccess())
{
fs = hfs.Get;
ctx.Logger.LogMessage(hfs.Get.Print());
}
else if (!ResultFs.Sha256PartitionSignatureVerificationFailed.Includes(res))
{
if (ResultFs.Sha256PartitionSignatureVerificationFailed.Includes(res))
{
ResultFs.PartitionSignatureVerificationFailed.Value.ThrowIfFailure();
}
res.ThrowIfFailure();
}
else
{
nsp.Reset(new NspRootFileSystemCore());
res = nsp.Get.Initialize(file);
fs = hfs.Get;
ctx.Logger.LogMessage(hfs.Get.Print());
if (res.IsSuccess())
{
fs = nsp.Get;
ctx.Logger.LogMessage(nsp.Get.Print());
}
else
{
res.ThrowIfFailure();
}
}
}
if (ctx.Options.OutDir != null)