diff --git a/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs b/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs index e7e6d710..f26063df 100644 --- a/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs +++ b/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs @@ -68,4 +68,47 @@ public struct Sha256PartitionFileSystemFormat : IPartitionFileSystemFormat readonly long IPartitionFileSystemEntry.Size => Size; readonly int IPartitionFileSystemEntry.NameOffset => NameOffset; } +} + +public struct NintendoSubmissionPackageRootFileSystemFormat : IPartitionFileSystemFormat +{ + public static ReadOnlySpan 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 _signature; + public int EntryCount; + public int NameTableSize; + public byte TargetPlatform; + public long TotalSize; + + public readonly ReadOnlySpan Signature + { + get + { + ReadOnlySpan span = _signature; + return MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(span), span.Length); + } + } + + readonly int IPartitionFileSystemHeader.EntryCount => EntryCount; + readonly int IPartitionFileSystemHeader.NameTableSize => NameTableSize; + } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/NintendoSubmissionPackageRootFileSystem.cs b/src/LibHac/FsSystem/NintendoSubmissionPackageRootFileSystem.cs new file mode 100644 index 00000000..063da003 --- /dev/null +++ b/src/LibHac/FsSystem/NintendoSubmissionPackageRootFileSystem.cs @@ -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; + +/// +/// Reads a standard partition file system of version 0 or version 1. These files start with "PFS0" or "PFS1" respectively. +/// +/// Based on nnSdk 18.3.0 (FS 18.0.0) +public class NintendoSubmissionPackageRootFileSystem : IFileSystem +{ + private Optional _nspRootFileSystem; + private Optional _partitionFileSystem; + + public NintendoSubmissionPackageRootFileSystem() + { + _nspRootFileSystem = new Optional(); + _partitionFileSystem = new Optional(); + } + + 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 baseStorage) + { + return InitializeImpl(in baseStorage).Ret(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, ref readonly Path path, OpenMode mode) + { + return GetImpl().OpenFile(ref outFile, in path, mode).Ret(); + } + + protected override Result DoOpenDirectory(ref UniqueRef 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 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; + } +} \ No newline at end of file diff --git a/src/LibHac/FsSystem/PartitionFileSystem.cs b/src/LibHac/FsSystem/PartitionFileSystem.cs index 1ea3f034..38e93853 100644 --- a/src/LibHac/FsSystem/PartitionFileSystem.cs +++ b/src/LibHac/FsSystem/PartitionFileSystem.cs @@ -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; + /// /// The allocator used by a when none is provided. /// @@ -52,18 +58,18 @@ file sealed class DefaultAllocatorForPartitionFileSystem : MemoryResource /// /// Based on nnSdk 16.2.0 (FS 16.0.0) public class PartitionFileSystem : PartitionFileSystemCore { } + PartitionFileSystemFormat, + PartitionFileSystemFormat.PartitionFileSystemHeaderImpl, + PartitionFileSystemFormat.PartitionEntry>; /// /// Reads a hashed partition file system. These files start with "HFS0" and are typically found inside XCIs. /// /// Based on nnSdk 16.2.0 (FS 16.0.0) public class Sha256PartitionFileSystem : PartitionFileSystemCore { } + Sha256PartitionFileSystemFormat, + PartitionFileSystemFormat.PartitionFileSystemHeaderImpl, + Sha256PartitionFileSystemFormat.PartitionEntry>; /// /// Provides the base for an that can read from different partition file system files. @@ -197,6 +203,11 @@ public class PartitionFileSystemCore : 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 : IFil bytesRead = readSize; return Result.Success; } + + private static Result DoRead(NspRootFileSystemCore.PartitionFile fs, out long bytesRead, long offset, + Span 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; + } } /// diff --git a/src/LibHac/FsSystem/PartitionFileSystemMeta.cs b/src/LibHac/FsSystem/PartitionFileSystemMeta.cs index 5e706e97..aa41df07 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemMeta.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemMeta.cs @@ -73,7 +73,7 @@ namespace LibHac.FsSystem protected MemoryResource Allocator; protected Buffer MetaDataBuffer; - private ref readonly THeader Header => ref MemoryMarshal.GetReference(HeaderBuffer.GetSpan()); + protected ref readonly THeader Header => ref MemoryMarshal.GetReference(HeaderBuffer.GetSpan()); private ReadOnlySpan Entries => EntryBuffer.GetSpan(); private ReadOnlySpan NameTable => NameTableBuffer.Span; @@ -356,11 +356,23 @@ namespace LibHac.FsSystem return Result.Success; } } +} +namespace LibHac.FsSystem +{ /// /// Reads the metadata for a . /// /// Based on nnSdk 16.2.0 (FS 16.0.0) public class PartitionFileSystemMeta : PartitionFileSystemMetaCore { } + + public class NintendoSubmissionPackageRootFileSystemMeta : PartitionFileSystemMetaCore< + NintendoSubmissionPackageRootFileSystemFormat, + NintendoSubmissionPackageRootFileSystemFormat.PartitionFileSystemHeaderImpl, + NintendoSubmissionPackageRootFileSystemFormat.PartitionEntry> + { + public byte GetTargetPlatform() => Header.TargetPlatform; + public long GetTotalSize() => Header.TotalSize; + } } \ No newline at end of file diff --git a/src/hactoolnet/ProcessPfs.cs b/src/hactoolnet/ProcessPfs.cs index ebd29dab..b7a51a39 100644 --- a/src/hactoolnet/ProcessPfs.cs +++ b/src/hactoolnet/ProcessPfs.cs @@ -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; + internal static class ProcessPfs { public static void Process(Context ctx) @@ -26,9 +32,12 @@ internal static class ProcessPfs IFileSystem fs = null; using UniqueRef pfs = new UniqueRef(); using UniqueRef hfs = new UniqueRef(); + using UniqueRef nsp = new UniqueRef(); + + using var sharedFile = new SharedRef(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)