diff --git a/src/LibHac/Common/FixedArrays/Array100.cs b/src/LibHac/Common/FixedArrays/Array100.cs new file mode 100644 index 00000000..341845ae --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array100.cs @@ -0,0 +1,34 @@ +#pragma warning disable CS0169, CS0649, IDE0051 // Field is never used, Field is never assigned to, Remove unused private members +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +public struct Array100 +{ + public const int Length = 100; + + private Array64 _0; + private Array36 _64; + + [UnscopedRef] public ref T this[int i] => ref Items[i]; + + [UnscopedRef] + public Span Items + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); + } + + [UnscopedRef] + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array100 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs index 0723d601..80cc1f65 100644 --- a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs +++ b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs @@ -31,6 +31,7 @@ internal class FileSystemAccessor : IDisposable private LinkedList _openFiles; private LinkedList _openDirectories; private SdkMutexType _openListLock; + private SdkMutexType _getFsAttributeLock; private UniqueRef _mountNameGenerator; private UniqueRef _saveDataAttributeGetter; private bool _isAccessLogEnabled; @@ -39,6 +40,7 @@ internal class FileSystemAccessor : IDisposable private bool _isPathCacheAttached; private IMultiCommitTarget _multiCommitTarget; private PathFlags _pathFlags; + private Optional _fsAttribute; private IStorage _storageForPurgeFileDataCache; internal HorizonClient Hos { get; } @@ -53,6 +55,7 @@ internal class FileSystemAccessor : IDisposable _openFiles = new LinkedList(); _openDirectories = new LinkedList(); _openListLock = new SdkMutexType(); + _getFsAttributeLock = new SdkMutexType(); _mountNameGenerator = new UniqueRef(ref mountNameGenerator); _saveDataAttributeGetter = new UniqueRef(ref saveAttributeGetter); _multiCommitTarget = multiCommitTarget; @@ -457,6 +460,25 @@ internal class FileSystemAccessor : IDisposable return Result.Success; } + public Result GetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _getFsAttributeLock); + + if (_fsAttribute.HasValue) + { + outAttribute = _fsAttribute.Value; + } + else + { + Result res = _fileSystem.Get.GetFileSystemAttribute(out outAttribute); + if (res.IsFailure()) return res.Miss(); + + _fsAttribute.Set(in outAttribute); + } + + return Result.Success; + } + public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) { using var pathNormalized = new Path(); diff --git a/src/LibHac/Fs/Fsa/IFileSystem.cs b/src/LibHac/Fs/Fsa/IFileSystem.cs index d00ab461..9310b916 100644 --- a/src/LibHac/Fs/Fsa/IFileSystem.cs +++ b/src/LibHac/Fs/Fsa/IFileSystem.cs @@ -1,5 +1,7 @@ using System; +using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Common.FixedArrays; using LibHac.FsSystem; namespace LibHac.Fs.Fsa; @@ -297,6 +299,16 @@ public abstract class IFileSystem : IDisposable return DoQueryEntry(outBuffer, inBuffer, queryId, path); } + /// + /// Gets attributes of the including info about the maximum path length sizes it supports. + /// + /// If the operation returns successfully, the file system attributes. + /// The of the requested operation. + public Result GetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + return DoGetFileSystemAttribute(out outAttribute); + } + protected abstract Result DoCreateFile(in Path path, long size, CreateFileOptions option); protected abstract Result DoDeleteFile(in Path path); protected abstract Result DoCreateDirectory(in Path path); @@ -320,8 +332,7 @@ public abstract class IFileSystem : IDisposable } protected abstract Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode); - protected abstract Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode); + protected abstract Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, OpenDirectoryMode mode); protected abstract Result DoCommit(); protected virtual Result DoCommitProvisionally(long counter) => ResultFs.NotImplemented.Log(); @@ -336,6 +347,12 @@ public abstract class IFileSystem : IDisposable protected virtual Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) => ResultFs.NotImplemented.Log(); + + protected virtual Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + UnsafeHelpers.SkipParamInit(out outAttribute); + return ResultFs.NotImplemented.Log(); + } } /// @@ -373,4 +390,37 @@ public enum QueryId UpdateMac = 1, IsSignedSystemPartition = 2, QueryUnpreparedFileInformation = 3 +} + +[StructLayout(LayoutKind.Sequential)] +public struct FileSystemAttribute +{ + public bool DirectoryNameLengthMaxHasValue; + public bool FileNameLengthMaxHasValue; + public bool DirectoryPathLengthMaxHasValue; + public bool FilePathLengthMaxHasValue; + public bool Utf16CreateDirectoryPathLengthMaxHasValue; + public bool Utf16DeleteDirectoryPathLengthMaxHasValue; + public bool Utf16RenameSourceDirectoryPathLengthMaxHasValue; + public bool Utf16RenameDestinationDirectoryPathLengthMaxHasValue; + public bool Utf16OpenDirectoryPathLengthMaxHasValue; + public bool Utf16DirectoryNameLengthMaxHasValue; + public bool Utf16FileNameLengthMaxHasValue; + public bool Utf16DirectoryPathLengthMaxHasValue; + public bool Utf16FilePathLengthMaxHasValue; + public Array27 Reserved1; + public int DirectoryNameLengthMax; + public int FileNameLengthMax; + public int DirectoryPathLengthMax; + public int FilePathLengthMax; + public int Utf16CreateDirectoryPathLengthMax; + public int Utf16DeleteDirectoryPathLengthMax; + public int Utf16RenameSourceDirectoryPathLengthMax; + public int Utf16RenameDestinationDirectoryPathLengthMax; + public int Utf16OpenDirectoryPathLengthMax; + public int Utf16DirectoryNameLengthMax; + public int Utf16FileNameLengthMax; + public int Utf16DirectoryPathLengthMax; + public int Utf16FilePathLengthMax; + public Array100 Reserved2; } \ No newline at end of file diff --git a/src/LibHac/Fs/Fsa/Registrar.cs b/src/LibHac/Fs/Fsa/Registrar.cs index 419dbef4..7a21d30d 100644 --- a/src/LibHac/Fs/Fsa/Registrar.cs +++ b/src/LibHac/Fs/Fsa/Registrar.cs @@ -76,6 +76,9 @@ public static class Registrar protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) => _fileSystem.Get.GetFreeSpaceSize(out freeSpace, in path); + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) => + _fileSystem.Get.GetFileSystemAttribute(out outAttribute); + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) => _fileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); @@ -183,7 +186,7 @@ public static class Registrar Assert.SdkAssert(storageForPurgeFileDataCache is not null); using (var unmountHookFileSystem = - new UniqueRef(new UnmountHookFileSystem(ref fileSystem, ref unmountHook))) + new UniqueRef(new UnmountHookFileSystem(ref fileSystem, ref unmountHook))) { fileSystem.Set(ref unmountHookFileSystem.Ref); } diff --git a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs index c857831a..de747c48 100644 --- a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs +++ b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs @@ -1,6 +1,8 @@ using System; using LibHac.Common; +using LibHac.Diag; using LibHac.Fs.Impl; +using LibHac.FsSrv.Sf; using LibHac.Os; using static LibHac.Fs.Impl.AccessLogStrings; @@ -9,7 +11,7 @@ namespace LibHac.Fs.Fsa; /// /// Contains functions meant for internal use for interacting with mounted file systems. /// -/// Based on nnSdk 13.4.0 (FS 13.1.0) +/// Based on nnSdk 16.2.0 public static class UserFileSystemPrivate { public static Result CreateFile(this FileSystemClient fs, U8Span path, long size, CreateFileOptions option) @@ -26,7 +28,7 @@ public static class UserFileSystemPrivate Tick end = fs.Hos.Os.GetSystemTick(); var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); + sb.Append(LogPath).Append(path).Append(LogQuote); logBuffer = sb.Buffer; fs.Impl.OutputAccessLogUnlessResultSuccess(res, start, end, null, new U8Span(logBuffer)); @@ -45,7 +47,7 @@ public static class UserFileSystemPrivate Tick end = fs.Hos.Os.GetSystemTick(); var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize).AppendFormat(size); + sb.Append(LogPath).Append(path).Append(LogQuote).Append(LogSize).AppendFormat(size); logBuffer = sb.Buffer; fs.Impl.OutputAccessLog(res, start, end, null, new U8Span(logBuffer)); @@ -97,4 +99,134 @@ public static class UserFileSystemPrivate fs.Impl.AbortIfNeeded(res); return res; } + + private static void SetOrChangeMin(ref int minValue, ref bool hasMinValue, int value, bool hasValue) + { + if (!hasValue) + return; + + if (!hasMinValue) + { + minValue = value; + hasMinValue = true; + return; + } + + if (minValue > value) + { + minValue = value; + } + } + + public static Result GetPathLengthMax(this FileSystemClient fs, out long outLength, U8Span path) + { + UnsafeHelpers.SkipParamInit(out outLength); + + Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + int retValue = 0; + bool hasRetValue = false; + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.DirectoryPathLengthMax, attribute.DirectoryPathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.FilePathLengthMax, attribute.FileNameLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, FspPath.MaxLength, true); + + Assert.SdkAssert(hasRetValue); + + outLength = retValue; + return Result.Success; + } + + public static Result GetEntryNameLengthMax(this FileSystemClient fs, out long outLength, U8Span path) + { + UnsafeHelpers.SkipParamInit(out outLength); + + Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + int retValue = 0; + bool hasRetValue = false; + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.DirectoryNameLengthMax, attribute.DirectoryNameLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.FileNameLengthMax, attribute.FileNameLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, FspPath.MaxLength, true); + + Assert.SdkAssert(hasRetValue); + + outLength = retValue; + return Result.Success; + } + + public static Result GetUtf16PathLengthMax(this FileSystemClient fs, out bool outHasValue, out long outLength, U8Span path) + { + UnsafeHelpers.SkipParamInit(out outHasValue, out outLength); + + Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + int retValue = 0; + bool hasRetValue = false; + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16CreateDirectoryPathLengthMax, attribute.Utf16CreateDirectoryPathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16DeleteDirectoryPathLengthMax, attribute.Utf16DeleteDirectoryPathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16RenameSourceDirectoryPathLengthMax, attribute.Utf16RenameSourceDirectoryPathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16RenameDestinationDirectoryPathLengthMax, attribute.Utf16RenameDestinationDirectoryPathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16OpenDirectoryPathLengthMax, attribute.Utf16OpenDirectoryPathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16FilePathLengthMax, attribute.Utf16FilePathLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16DirectoryPathLengthMax, attribute.Utf16DirectoryPathLengthMaxHasValue); + + outHasValue = hasRetValue; + outLength = retValue; + return Result.Success; + } + + public static Result GetUtf16EntryNameLengthMax(this FileSystemClient fs, out bool outHasValue, out long outLength, U8Span path) + { + UnsafeHelpers.SkipParamInit(out outHasValue, out outLength); + + Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + res = fileSystem.GetFileSystemAttribute(out FileSystemAttribute attribute); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + int retValue = 0; + bool hasRetValue = false; + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16FileNameLengthMax, attribute.Utf16FileNameLengthMaxHasValue); + SetOrChangeMin(ref retValue, ref hasRetValue, attribute.Utf16DirectoryNameLengthMax, attribute.Utf16DirectoryNameLengthMaxHasValue); + + outHasValue = hasRetValue; + outLength = retValue; + return Result.Success; + } + + public static Result GetFileSystemAttributeForDebug(this FileSystemClient fs, out FileSystemAttribute outAttribute, U8Span path) + { + UnsafeHelpers.SkipParamInit(out outAttribute); + + Result res = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out _, path); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + res = fileSystem.GetFileSystemAttribute(out outAttribute); + fs.Impl.AbortIfNeeded(res); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } } \ No newline at end of file diff --git a/src/LibHac/Fs/ReadOnlyFileSystem.cs b/src/LibHac/Fs/ReadOnlyFileSystem.cs index 5b059814..54458dd7 100644 --- a/src/LibHac/Fs/ReadOnlyFileSystem.cs +++ b/src/LibHac/Fs/ReadOnlyFileSystem.cs @@ -10,7 +10,7 @@ namespace LibHac.Fs; /// Wraps an and only allows read operations on it. /// /// Based on nnSdk 13.4.0 (FS 13.1.0) -internal class ReadOnlyFile : IFile +file class ReadOnlyFile : IFile { private UniqueRef _baseFile; @@ -156,4 +156,9 @@ public class ReadOnlyFileSystem : IFileSystem Unsafe.SkipInit(out totalSpace); return ResultFs.UnsupportedGetTotalSpaceSizeForReadOnlyFileSystem.Log(); } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute); + } } \ No newline at end of file diff --git a/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs b/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs index d15a7467..fa3233c5 100644 --- a/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs +++ b/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs @@ -293,6 +293,11 @@ internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget return _baseFs.Get.GetFileTimeStampRaw(out timeStamp, in sfPath); } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + return _baseFs.Get.GetFileSystemAttribute(out outAttribute); + } + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) { diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index bfac1aa2..36a63b1f 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -592,6 +592,17 @@ public class FileSystemInterfaceAdapter : IFileSystemSf return Result.Success; } + public Result GetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + UnsafeHelpers.SkipParamInit(out outAttribute); + + Result res = _baseFileSystem.Get.GetFileSystemAttribute(out FileSystemAttribute attribute); + if (res.IsFailure()) return res.Miss(); + + outAttribute = attribute; + return Result.Success; + } + public Result GetImpl(ref SharedRef fileSystem) { fileSystem.SetByCopy(in _baseFileSystem); diff --git a/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs index 55bdd7d8..a09c8a59 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs @@ -114,4 +114,9 @@ public class SaveDataFileSystemCacheRegister : IFileSystem { return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute); + } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Sf/IFileSystem.cs b/src/LibHac/FsSrv/Sf/IFileSystem.cs index f081caee..6ac7b877 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystem.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystem.cs @@ -1,6 +1,7 @@ using System; using LibHac.Common; using LibHac.Fs; +using LibHac.Fs.Fsa; using LibHac.Sf; using IFileSf = LibHac.FsSrv.Sf.IFile; using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; @@ -26,4 +27,5 @@ public interface IFileSystem : IDisposable Result CleanDirectoryRecursively(in Path path); Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path); Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path); + Result GetFileSystemAttribute(out FileSystemAttribute outAttribute); } \ No newline at end of file diff --git a/src/LibHac/FsSystem/ConcatenationFileSystem.cs b/src/LibHac/FsSystem/ConcatenationFileSystem.cs index b0d7b072..a0c384bb 100644 --- a/src/LibHac/FsSystem/ConcatenationFileSystem.cs +++ b/src/LibHac/FsSystem/ConcatenationFileSystem.cs @@ -502,8 +502,7 @@ public class ConcatenationFileSystem : IFileSystem /// The base for the /// new . public ConcatenationFileSystem(ref UniqueRef baseFileSystem) : this(ref baseFileSystem, - DefaultInternalFileSize) - { } + DefaultInternalFileSize) { } /// /// Initializes a new . @@ -582,7 +581,7 @@ public class ConcatenationFileSystem : IFileSystem Result res = internalFilePath.Initialize(in path); if (res.IsFailure()) return res.Miss(); - for (int i = 0; ; i++) + for (int i = 0;; i++) { res = AppendInternalFilePath(ref internalFilePath.Ref(), i); if (res.IsFailure()) return res.Miss(); @@ -958,7 +957,7 @@ public class ConcatenationFileSystem : IFileSystem long sizeTotal = 0; - for (int i = 0; ; i++) + for (int i = 0;; i++) { res = AppendInternalFilePath(ref internalFilePath.Ref(), i); if (res.IsFailure()) return res.Miss(); @@ -1011,4 +1010,11 @@ public class ConcatenationFileSystem : IFileSystem return _baseFileSystem.Get.CommitProvisionally(counter).Ret(); } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + using var scopedLock = new ScopedLock(ref _mutex); + + return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute).Ret(); + } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index aa9f6309..37255424 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -682,6 +682,18 @@ public class DirectorySaveDataFileSystem : ISaveDataFileSystem return Result.Success; } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + const int baseDirectoryNameLength = 2; + + Result res = _baseFs.GetFileSystemAttribute(out outAttribute); + if (res.IsFailure()) return res.Miss(); + + Utility.SubtractAllPathLengthMax(ref outAttribute, baseDirectoryNameLength); + Utility.SubtractAllUtf16CountMax(ref outAttribute, baseDirectoryNameLength); + return Result.Success; + } + public override bool IsSaveDataFileSystemCacheEnabled() { return false; diff --git a/src/LibHac/FsSystem/ForwardingFileSystem.cs b/src/LibHac/FsSystem/ForwardingFileSystem.cs index c76e3160..39bda625 100644 --- a/src/LibHac/FsSystem/ForwardingFileSystem.cs +++ b/src/LibHac/FsSystem/ForwardingFileSystem.cs @@ -139,6 +139,9 @@ public class ForwardingFileSystem : IFileSystem protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) => BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path); + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) => + BaseFileSystem.Get.GetFileSystemAttribute(out outAttribute); + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) => BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in path); } \ No newline at end of file diff --git a/src/LibHac/FsSystem/IResultConvertFileSystem.cs b/src/LibHac/FsSystem/IResultConvertFileSystem.cs index 4ac738c1..f27ae852 100644 --- a/src/LibHac/FsSystem/IResultConvertFileSystem.cs +++ b/src/LibHac/FsSystem/IResultConvertFileSystem.cs @@ -207,4 +207,9 @@ public abstract class IResultConvertFileSystem : ISaveDataFileSystem where T { return ConvertResult(_baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path)).Ret(); } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + return ConvertResult(_baseFileSystem.Get.GetFileSystemAttribute(out outAttribute)).Ret(); + } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/LocalFileSystem.cs b/src/LibHac/FsSystem/LocalFileSystem.cs index 8072b12d..bbc2b522 100644 --- a/src/LibHac/FsSystem/LocalFileSystem.cs +++ b/src/LibHac/FsSystem/LocalFileSystem.cs @@ -523,6 +523,44 @@ public class LocalFileSystem : IAttributeFileSystem return Result.Success; } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + const int winMaxPathComponentLength = 255; + const int winMaxPathLength = 259; + const int winMaxDirectoryPathLength = 247; + + outAttribute = default; + + outAttribute.Utf16DirectoryNameLengthMax = winMaxPathComponentLength; + outAttribute.Utf16DirectoryNameLengthMaxHasValue = true; + outAttribute.Utf16FileNameLengthMax = winMaxPathComponentLength; + outAttribute.Utf16FileNameLengthMaxHasValue = true; + outAttribute.Utf16DirectoryPathLengthMax = winMaxPathLength; + outAttribute.Utf16DirectoryPathLengthMaxHasValue = true; + outAttribute.Utf16FilePathLengthMax = winMaxPathLength; + outAttribute.Utf16FilePathLengthMaxHasValue = true; + outAttribute.Utf16CreateDirectoryPathLengthMax = winMaxDirectoryPathLength; + outAttribute.Utf16CreateDirectoryPathLengthMaxHasValue = true; + outAttribute.Utf16DeleteDirectoryPathLengthMax = winMaxPathLength; + outAttribute.Utf16DeleteDirectoryPathLengthMaxHasValue = true; + outAttribute.Utf16RenameSourceDirectoryPathLengthMax = winMaxDirectoryPathLength; + outAttribute.Utf16RenameSourceDirectoryPathLengthMaxHasValue = true; + outAttribute.Utf16RenameDestinationDirectoryPathLengthMax = winMaxDirectoryPathLength; + outAttribute.Utf16RenameDestinationDirectoryPathLengthMaxHasValue = true; + outAttribute.Utf16OpenDirectoryPathLengthMax = winMaxPathLength; + outAttribute.Utf16OpenDirectoryPathLengthMaxHasValue = true; + + int rootPathCount = _rootPath.GetLength(); + + Result res = Utility.CountUtf16CharacterForUtf8String(out ulong rootPathUtf16Count, _rootPath.GetString()); + if (res.IsFailure()) return res.Miss(); + + Utility.SubtractAllPathLengthMax(ref outAttribute, rootPathCount); + Utility.SubtractAllUtf16CountMax(ref outAttribute, (int)rootPathUtf16Count); + + return Result.Success; + } + protected override Result DoCommit() { return Result.Success; diff --git a/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs b/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs index 973983b1..c0c1f69a 100644 --- a/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs +++ b/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs @@ -38,10 +38,7 @@ internal struct ScopedStorageLayoutTypeSetter : IDisposable // Todo: Implement } - public void Dispose() - { - - } + public void Dispose() { } } /// @@ -377,6 +374,12 @@ internal class StorageLayoutTypeSetFileSystem : IFileSystem return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path); } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.GetFileSystemAttribute(out outAttribute); + } + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) { diff --git a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs index db31526c..4681577b 100644 --- a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs +++ b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs @@ -255,4 +255,20 @@ public class SubdirectoryFileSystem : IFileSystem { return _baseFileSystem.Rollback(); } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + int rootPathCount = _rootPath.GetLength(); + + Result res = _baseFileSystem.GetFileSystemAttribute(out outAttribute); + if (res.IsFailure()) return res.Miss(); + + res = Utility.CountUtf16CharacterForUtf8String(out ulong rootPathUtf16Count, _rootPath.GetString()); + if (res.IsFailure()) return res.Miss(); + + Utility.SubtractAllPathLengthMax(ref outAttribute, rootPathCount); + Utility.SubtractAllUtf16CountMax(ref outAttribute, (int)rootPathUtf16Count); + + return Result.Success; + } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/Utility.cs b/src/LibHac/FsSystem/Utility.cs index e977b883..450446f1 100644 --- a/src/LibHac/FsSystem/Utility.cs +++ b/src/LibHac/FsSystem/Utility.cs @@ -1,10 +1,12 @@ using System; using LibHac.Common; +using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv.Sf; using LibHac.Os; using LibHac.Sf; +using LibHac.Util; using IDirectory = LibHac.Fs.Fsa.IDirectory; using IFile = LibHac.Fs.Fsa.IFile; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; @@ -208,7 +210,7 @@ internal static class Utility } public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, - in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) { static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) { @@ -251,7 +253,7 @@ internal static class Utility } public static Result CopyDirectoryRecursively(IFileSystem fileSystem, in Path destinationPath, - in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) { var closure = new FsIterationTaskClosure(); closure.Buffer = workBuffer; @@ -438,4 +440,73 @@ internal static class Utility outUniqueLock.Set(ref uniqueLock.Ref); return Result.Success; } + + private static void SubtractIfHasValue(ref int inOutValue, int count, bool hasValue) + { + if (hasValue) + { + inOutValue -= count; + Assert.SdkAssert(inOutValue >= 0); + inOutValue = Math.Max(inOutValue, 0); + } + } + + private static ulong GetCodePointByteLength(byte firstUtf8CodeUnit) + { + if ((firstUtf8CodeUnit & 0x80) == 0) + return 1; + + if ((firstUtf8CodeUnit & 0xE0) == 0xC0) + return 2; + + if ((firstUtf8CodeUnit & 0xF0) == 0xE0) + return 3; + + if ((firstUtf8CodeUnit & 0xF8) == 0xF0) + return 4; + + return 0; + } + + public static void SubtractAllPathLengthMax(ref FileSystemAttribute attribute, int count) + { + SubtractIfHasValue(ref attribute.DirectoryPathLengthMax, count, attribute.DirectoryPathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.FilePathLengthMax, count, attribute.FilePathLengthMaxHasValue); + } + + public static Result CountUtf16CharacterForUtf8String(out ulong outCount, ReadOnlySpan utf8String) + { + UnsafeHelpers.SkipParamInit(out outCount); + + Span buffer = stackalloc byte[4]; + ReadOnlySpan curString = utf8String; + ulong utf16CodeUnitTotalCount = 0; + + while (curString.Length > 0 && curString[0] != 0) + { + int utf16CodeUnitCount = GetCodePointByteLength(curString[0]) >= 4 ? 2 : 1; + buffer.Clear(); + + if (CharacterEncoding.PickOutCharacterFromUtf8String(buffer, ref curString) != CharacterEncodingResult.Success) + { + return ResultFs.InvalidPathFormat.Log(); + } + + utf16CodeUnitTotalCount += (ulong)utf16CodeUnitCount; + } + + outCount = utf16CodeUnitTotalCount; + return Result.Success; + } + + public static void SubtractAllUtf16CountMax(ref FileSystemAttribute attribute, int count) + { + SubtractIfHasValue(ref attribute.Utf16DirectoryPathLengthMax, count, attribute.Utf16DirectoryPathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.Utf16FilePathLengthMax, count, attribute.Utf16FilePathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.Utf16CreateDirectoryPathLengthMax, count, attribute.Utf16CreateDirectoryPathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.Utf16DeleteDirectoryPathLengthMax, count, attribute.Utf16DeleteDirectoryPathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.Utf16RenameSourceDirectoryPathLengthMax, count, attribute.Utf16RenameSourceDirectoryPathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.Utf16RenameDestinationDirectoryPathLengthMax, count, attribute.Utf16RenameDestinationDirectoryPathLengthMaxHasValue); + SubtractIfHasValue(ref attribute.Utf16OpenDirectoryPathLengthMax, count, attribute.Utf16OpenDirectoryPathLengthMaxHasValue); + } } \ No newline at end of file diff --git a/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs b/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs index 99201d23..269b1b37 100644 --- a/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs +++ b/src/LibHac/Tools/FsSystem/RomFs/RomFsFileSystem.cs @@ -117,6 +117,17 @@ public class RomFsFileSystem : IFileSystem return ResultFs.UnsupportedGetTotalSpaceSizeForRomFsFileSystem.Log(); } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + outAttribute = default; + outAttribute.DirectoryNameLengthMax = PathTool.EntryNameLengthMax; + outAttribute.DirectoryNameLengthMaxHasValue = true; + outAttribute.FileNameLengthMax = PathTool.EntryNameLengthMax; + outAttribute.FileNameLengthMaxHasValue = true; + + return Result.Success; + } + internal static Result ConvertRomFsDriverPrivateResult(Result result) { if (result.IsSuccess()) diff --git a/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystem.cs index 62c1829e..47859b67 100644 --- a/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystem.cs @@ -247,6 +247,13 @@ public class SaveDataFileSystem : IFileSystem return SaveResults.ConvertToExternalResult(result).LogConverted(result); } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + Result result = SaveDataFileSystemCore.GetFileSystemAttribute(out outAttribute); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + protected override Result DoCommit() { Result result = Commit(KeySet); diff --git a/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystemCore.cs b/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystemCore.cs index 272b30b3..f974c83d 100644 --- a/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/Tools/FsSystem/Save/SaveDataFileSystemCore.cs @@ -229,6 +229,19 @@ public class SaveDataFileSystemCore : IFileSystem return Result.Success; } + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + const int maxSaveNameLength = 0x40; + + outAttribute = default; + outAttribute.DirectoryNameLengthMax = maxSaveNameLength; + outAttribute.DirectoryNameLengthMaxHasValue = true; + outAttribute.FileNameLengthMax = maxSaveNameLength; + outAttribute.FileNameLengthMaxHasValue = true; + + return Result.Success; + } + protected override Result DoCommit() { return Result.Success; diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 5747b841..efe876ab 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using LibHac.Fs; +using LibHac.Fs.Fsa; using LibHac.Fs.Impl; using Xunit; using static LibHac.Tests.Common.Layout; @@ -258,6 +259,43 @@ public class TypeLayoutTests Assert.Equal(0xC, GetOffset(in s, in s.NumReadWriteErrorCorrections)); } + [Fact] + public static void FileSystemAttribute_Layout() + { + var s = new FileSystemAttribute(); + + Assert.Equal(0xC0, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.DirectoryNameLengthMaxHasValue)); + Assert.Equal(0x01, GetOffset(in s, in s.FileNameLengthMaxHasValue)); + Assert.Equal(0x02, GetOffset(in s, in s.DirectoryPathLengthMaxHasValue)); + Assert.Equal(0x03, GetOffset(in s, in s.FilePathLengthMaxHasValue)); + Assert.Equal(0x04, GetOffset(in s, in s.Utf16CreateDirectoryPathLengthMaxHasValue)); + Assert.Equal(0x05, GetOffset(in s, in s.Utf16DeleteDirectoryPathLengthMaxHasValue)); + Assert.Equal(0x06, GetOffset(in s, in s.Utf16RenameSourceDirectoryPathLengthMaxHasValue)); + Assert.Equal(0x07, GetOffset(in s, in s.Utf16RenameDestinationDirectoryPathLengthMaxHasValue)); + Assert.Equal(0x08, GetOffset(in s, in s.Utf16OpenDirectoryPathLengthMaxHasValue)); + Assert.Equal(0x09, GetOffset(in s, in s.Utf16DirectoryNameLengthMaxHasValue)); + Assert.Equal(0x0A, GetOffset(in s, in s.Utf16FileNameLengthMaxHasValue)); + Assert.Equal(0x0B, GetOffset(in s, in s.Utf16DirectoryPathLengthMaxHasValue)); + Assert.Equal(0x0C, GetOffset(in s, in s.Utf16FilePathLengthMaxHasValue)); + Assert.Equal(0x0D, GetOffset(in s, in s.Reserved1)); + Assert.Equal(0x28, GetOffset(in s, in s.DirectoryNameLengthMax)); + Assert.Equal(0x2C, GetOffset(in s, in s.FileNameLengthMax)); + Assert.Equal(0x30, GetOffset(in s, in s.DirectoryPathLengthMax)); + Assert.Equal(0x34, GetOffset(in s, in s.FilePathLengthMax)); + Assert.Equal(0x38, GetOffset(in s, in s.Utf16CreateDirectoryPathLengthMax)); + Assert.Equal(0x3C, GetOffset(in s, in s.Utf16DeleteDirectoryPathLengthMax)); + Assert.Equal(0x40, GetOffset(in s, in s.Utf16RenameSourceDirectoryPathLengthMax)); + Assert.Equal(0x44, GetOffset(in s, in s.Utf16RenameDestinationDirectoryPathLengthMax)); + Assert.Equal(0x48, GetOffset(in s, in s.Utf16OpenDirectoryPathLengthMax)); + Assert.Equal(0x4C, GetOffset(in s, in s.Utf16DirectoryNameLengthMax)); + Assert.Equal(0x50, GetOffset(in s, in s.Utf16FileNameLengthMax)); + Assert.Equal(0x54, GetOffset(in s, in s.Utf16DirectoryPathLengthMax)); + Assert.Equal(0x58, GetOffset(in s, in s.Utf16FilePathLengthMax)); + Assert.Equal(0x5C, GetOffset(in s, in s.Reserved2)); + } + [Fact] public static void FileTimeStamp_Layout() {