diff --git a/src/LibHac.Nand/FatFileSystemProvider.cs b/src/LibHac.Nand/FatFileSystemProvider.cs index 4b07cf3b..ed6634a9 100644 --- a/src/LibHac.Nand/FatFileSystemProvider.cs +++ b/src/LibHac.Nand/FatFileSystemProvider.cs @@ -75,6 +75,13 @@ namespace LibHac.Nand return Fs.GetAttributes(path); } + public void SetFileAttributes(string path, FileAttributes attributes) + { + path = ToDiscUtilsPath(PathTools.Normalize(path)); + + Fs.SetAttributes(path, attributes); + } + public long GetFileSize(string path) { path = ToDiscUtilsPath(PathTools.Normalize(path)); @@ -83,9 +90,9 @@ namespace LibHac.Nand } public void Commit() { } - + public void CreateDirectory(string path) => throw new NotSupportedException(); - public void CreateFile(string path, long size) => throw new NotSupportedException(); + public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); diff --git a/src/LibHac/IO/AesXtsFileSystem.cs b/src/LibHac/IO/AesXtsFileSystem.cs index 6368ede4..27662686 100644 --- a/src/LibHac/IO/AesXtsFileSystem.cs +++ b/src/LibHac/IO/AesXtsFileSystem.cs @@ -31,7 +31,7 @@ namespace LibHac.IO BaseFileSystem.CreateDirectory(path); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { throw new NotImplementedException(); } diff --git a/src/LibHac/IO/ConcatenationDirectory.cs b/src/LibHac/IO/ConcatenationDirectory.cs index 5a1fa2d0..1c34f069 100644 --- a/src/LibHac/IO/ConcatenationDirectory.cs +++ b/src/LibHac/IO/ConcatenationDirectory.cs @@ -33,7 +33,7 @@ namespace LibHac.IO } else { - long size = ParentFileSystem.GetSplitFileSize(entry.FullPath); + long size = ParentFileSystem.GetConcatenationFileSize(entry.FullPath); yield return new DirectoryEntry(entry.Name, entry.FullPath, DirectoryEntryType.File, size); } } diff --git a/src/LibHac/IO/ConcatenationFile.cs b/src/LibHac/IO/ConcatenationFile.cs index 7bf3f925..f6fb44c9 100644 --- a/src/LibHac/IO/ConcatenationFile.cs +++ b/src/LibHac/IO/ConcatenationFile.cs @@ -54,7 +54,26 @@ namespace LibHac.IO public override void Write(ReadOnlySpan source, long offset) { - throw new NotImplementedException(); + ValidateWriteParams(source, offset); + + long inPos = offset; + int outPos = 0; + int remaining = source.Length; + + while (remaining > 0) + { + int fileIndex = GetFileIndexFromOffset(offset); + IFile file = Sources[fileIndex]; + long fileOffset = offset - fileIndex * SplitFileSize; + + long fileEndOffset = Math.Min((fileIndex + 1) * SplitFileSize, GetSize()); + int bytesToWrite = (int)Math.Min(fileEndOffset - inPos, remaining); + file.Write(source.Slice(outPos, bytesToWrite), fileOffset); + + outPos += bytesToWrite; + inPos += bytesToWrite; + remaining -= bytesToWrite; + } } public override void Flush() diff --git a/src/LibHac/IO/ConcatenationFileSystem.cs b/src/LibHac/IO/ConcatenationFileSystem.cs index a864497d..a4fe26e5 100644 --- a/src/LibHac/IO/ConcatenationFileSystem.cs +++ b/src/LibHac/IO/ConcatenationFileSystem.cs @@ -6,12 +6,16 @@ namespace LibHac.IO { public class ConcatenationFileSystem : IFileSystem { + private const long DefaultSplitFileSize = 0xFFFF0000; // Hard-coded value used by FS private IAttributeFileSystem BaseFileSystem { get; } - private long SplitFileSize { get; } = 0xFFFF0000; // Hard-coded value used by FS + private long SplitFileSize { get; } - public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) + public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultSplitFileSize) { } + + public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long splitFileSize) { BaseFileSystem = baseFileSystem; + SplitFileSize = splitFileSize; } internal bool IsSplitFile(string path) @@ -23,22 +27,78 @@ namespace LibHac.IO public void CreateDirectory(string path) { - throw new NotImplementedException(); + path = PathTools.Normalize(path); + + if (FileExists(path)) + { + throw new IOException("Cannot create directory because a file with this name already exists."); + } + + BaseFileSystem.CreateDirectory(path); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { - throw new NotImplementedException(); + path = PathTools.Normalize(path); + + CreateFileOptions newOptions = options & ~CreateFileOptions.CreateConcatenationFile; + + if (!options.HasFlag(CreateFileOptions.CreateConcatenationFile)) + { + BaseFileSystem.CreateFile(path, size, newOptions); + return; + } + + // A concatenation file directory can't contain normal files + string parentDir = PathTools.GetParentDirectory(path); + if (IsSplitFile(parentDir)) throw new IOException("Cannot create files inside of a concatenation file"); + + BaseFileSystem.CreateDirectory(path); + FileAttributes attributes = BaseFileSystem.GetFileAttributes(path) | FileAttributes.Archive; + BaseFileSystem.SetFileAttributes(path, attributes); + + long remaining = size; + + for (int i = 0; remaining > 0; i++) + { + long fileSize = Math.Min(SplitFileSize, remaining); + string fileName = GetSplitFilePath(path, i); + + BaseFileSystem.CreateFile(fileName, fileSize, CreateFileOptions.None); + + remaining -= fileSize; + } } public void DeleteDirectory(string path) { - throw new NotImplementedException(); + path = PathTools.Normalize(path); + + if (IsSplitFile(path)) + { + throw new DirectoryNotFoundException(path); + } + + BaseFileSystem.DeleteDirectory(path); } public void DeleteFile(string path) { - throw new NotImplementedException(); + path = PathTools.Normalize(path); + + if (!IsSplitFile(path)) + { + BaseFileSystem.DeleteFile(path); + } + + int count = GetSplitFileCount(path); + + for (int i = 0; i < count; i++) + { + BaseFileSystem.DeleteFile(GetSplitFilePath(path, i)); + } + + BaseFileSystem.DeleteDirectory(path); } public IDirectory OpenDirectory(string path, OpenDirectoryMode mode) @@ -151,7 +211,7 @@ namespace LibHac.IO return $"{dirPath}/{index:D2}"; } - internal long GetSplitFileSize(string path) + internal long GetConcatenationFileSize(string path) { int fileCount = GetSplitFileCount(path); long size = 0; diff --git a/src/LibHac/IO/FileSystemExtensions.cs b/src/LibHac/IO/FileSystemExtensions.cs index e44396fa..eb88fa00 100644 --- a/src/LibHac/IO/FileSystemExtensions.cs +++ b/src/LibHac/IO/FileSystemExtensions.cs @@ -11,7 +11,7 @@ namespace LibHac.IO { public static class FileSystemExtensions { - public static void CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null) + public static void CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None) { IFileSystem sourceFs = source.ParentFileSystem; IFileSystem destFs = dest.ParentFileSystem; @@ -27,12 +27,12 @@ namespace LibHac.IO IDirectory subSrcDir = sourceFs.OpenDirectory(subSrcPath, OpenDirectoryMode.All); IDirectory subDstDir = destFs.OpenDirectory(subDstPath, OpenDirectoryMode.All); - subSrcDir.CopyDirectory(subDstDir, logger); + subSrcDir.CopyDirectory(subDstDir, logger, options); } if (entry.Type == DirectoryEntryType.File) { - destFs.CreateFile(subDstPath, entry.Size); + destFs.CreateFile(subDstPath, entry.Size, options); using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read)) using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write)) @@ -44,12 +44,12 @@ namespace LibHac.IO } } - public static void CopyFileSystem(this IFileSystem source, IFileSystem dest, IProgressReport logger = null) + public static void CopyFileSystem(this IFileSystem source, IFileSystem dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None) { IDirectory sourceRoot = source.OpenDirectory("/", OpenDirectoryMode.All); IDirectory destRoot = dest.OpenDirectory("/", OpenDirectoryMode.All); - sourceRoot.CopyDirectory(destRoot, logger); + sourceRoot.CopyDirectory(destRoot, logger, options); } public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null) diff --git a/src/LibHac/IO/IAttributeFileSystem.cs b/src/LibHac/IO/IAttributeFileSystem.cs index 1d23d0f8..8fb79fa3 100644 --- a/src/LibHac/IO/IAttributeFileSystem.cs +++ b/src/LibHac/IO/IAttributeFileSystem.cs @@ -5,6 +5,7 @@ namespace LibHac.IO public interface IAttributeFileSystem : IFileSystem { FileAttributes GetFileAttributes(string path); + void SetFileAttributes(string path, FileAttributes attributes); long GetFileSize(string path); } } diff --git a/src/LibHac/IO/IFileSystem.cs b/src/LibHac/IO/IFileSystem.cs index 73c73b42..bfedc57c 100644 --- a/src/LibHac/IO/IFileSystem.cs +++ b/src/LibHac/IO/IFileSystem.cs @@ -5,7 +5,7 @@ namespace LibHac.IO public interface IFileSystem { void CreateDirectory(string path); - void CreateFile(string path, long size); + void CreateFile(string path, long size, CreateFileOptions options); void DeleteDirectory(string path); void DeleteFile(string path); IDirectory OpenDirectory(string path, OpenDirectoryMode mode); @@ -25,4 +25,11 @@ namespace LibHac.IO Files = 2, All = Directories | Files } + + [Flags] + public enum CreateFileOptions + { + None = 0, + CreateConcatenationFile = 1 << 0 + } } \ No newline at end of file diff --git a/src/LibHac/IO/LayeredFileSystem.cs b/src/LibHac/IO/LayeredFileSystem.cs index f9110359..11475caf 100644 --- a/src/LibHac/IO/LayeredFileSystem.cs +++ b/src/LibHac/IO/LayeredFileSystem.cs @@ -100,7 +100,7 @@ namespace LibHac.IO public void Commit() { } public void CreateDirectory(string path) => throw new NotSupportedException(); - public void CreateFile(string path, long size) => throw new NotSupportedException(); + public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); public void DeleteDirectory(string path) => throw new NotSupportedException(); public void DeleteFile(string path) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); diff --git a/src/LibHac/IO/LocalFileSystem.cs b/src/LibHac/IO/LocalFileSystem.cs index 7a404cd8..f6aac027 100644 --- a/src/LibHac/IO/LocalFileSystem.cs +++ b/src/LibHac/IO/LocalFileSystem.cs @@ -23,6 +23,12 @@ namespace LibHac.IO return File.GetAttributes(ResolveLocalPath(path)); } + public void SetFileAttributes(string path, FileAttributes attributes) + { + path = PathTools.Normalize(path); + File.SetAttributes(ResolveLocalPath(path), attributes); + } + public long GetFileSize(string path) { path = PathTools.Normalize(path); @@ -36,7 +42,7 @@ namespace LibHac.IO Directory.CreateDirectory(ResolveLocalPath(path)); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { path = PathTools.Normalize(path); string localPath = ResolveLocalPath(path); diff --git a/src/LibHac/IO/PartitionFileSystem.cs b/src/LibHac/IO/PartitionFileSystem.cs index 1ffe5f43..75b62faa 100644 --- a/src/LibHac/IO/PartitionFileSystem.cs +++ b/src/LibHac/IO/PartitionFileSystem.cs @@ -33,7 +33,7 @@ namespace LibHac.IO throw new NotSupportedException(); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { throw new NotSupportedException(); } diff --git a/src/LibHac/IO/PathTools.cs b/src/LibHac/IO/PathTools.cs index 1363246b..599af969 100644 --- a/src/LibHac/IO/PathTools.cs +++ b/src/LibHac/IO/PathTools.cs @@ -76,6 +76,21 @@ namespace LibHac.IO return sb.ToString(); } + public static string GetParentDirectory(string path) + { + if (path.Length == 0) return "/"; + + int i = path.Length - 1; + + // A trailing separator should be ignored + if (path[i] == '/') i--; + + while (i >= 0 && path[i] != '/') i--; + + if (i < 1) return "/"; + return path.Substring(0, i); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsDirectorySeparator(char c) { diff --git a/src/LibHac/IO/RomFsFileSystem.cs b/src/LibHac/IO/RomFsFileSystem.cs index 7ae44039..ec5c5f30 100644 --- a/src/LibHac/IO/RomFsFileSystem.cs +++ b/src/LibHac/IO/RomFsFileSystem.cs @@ -148,7 +148,7 @@ namespace LibHac.IO } public void CreateDirectory(string path) => throw new NotSupportedException(); - public void CreateFile(string path, long size) => throw new NotSupportedException(); + public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); public void DeleteDirectory(string path) => throw new NotSupportedException(); public void DeleteFile(string path) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); diff --git a/src/LibHac/IO/Save/SaveDataFileSystem.cs b/src/LibHac/IO/Save/SaveDataFileSystem.cs index 902912ab..86312cb2 100644 --- a/src/LibHac/IO/Save/SaveDataFileSystem.cs +++ b/src/LibHac/IO/Save/SaveDataFileSystem.cs @@ -122,7 +122,7 @@ namespace LibHac.IO.Save throw new System.NotImplementedException(); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { throw new System.NotImplementedException(); } diff --git a/src/LibHac/IO/Save/SaveDataFileSystemCore.cs b/src/LibHac/IO/Save/SaveDataFileSystemCore.cs index c2490265..787692ee 100644 --- a/src/LibHac/IO/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/IO/Save/SaveDataFileSystemCore.cs @@ -66,7 +66,7 @@ namespace LibHac.IO.Save throw new System.NotImplementedException(); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { throw new System.NotImplementedException(); } diff --git a/src/LibHac/IO/SubdirectoryFileSystem.cs b/src/LibHac/IO/SubdirectoryFileSystem.cs index ac415888..64d89f85 100644 --- a/src/LibHac/IO/SubdirectoryFileSystem.cs +++ b/src/LibHac/IO/SubdirectoryFileSystem.cs @@ -24,11 +24,11 @@ ParentFileSystem.CreateDirectory(ResolveFullPath(path)); } - public void CreateFile(string path, long size) + public void CreateFile(string path, long size, CreateFileOptions options) { path = PathTools.Normalize(path); - ParentFileSystem.CreateFile(ResolveFullPath(path), size); + ParentFileSystem.CreateFile(ResolveFullPath(path), size, options); } public void DeleteDirectory(string path)