From c89bc1c706ba5bb2780866e623ba2fcd3d5e58ed Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 23 Sep 2019 18:37:23 -0500 Subject: [PATCH] Make FileSystemProxy implement IFileSystemProxy --- src/LibHac/Common/StringUtils.cs | 12 + src/LibHac/Fs/ContentStorage.cs | 2 +- src/LibHac/Fs/CustomStorage.cs | 2 +- src/LibHac/Fs/FileSystemClient.cs | 4 +- src/LibHac/FsService/FileSystemProxy.cs | 537 ++++++++++++++++++++--- src/LibHac/FsService/FileSystemServer.cs | 2 +- src/LibHac/FsSystem/FsPath.cs | 1 + src/LibHac/FsSystem/PathTools.cs | 15 + 8 files changed, 499 insertions(+), 76 deletions(-) diff --git a/src/LibHac/Common/StringUtils.cs b/src/LibHac/Common/StringUtils.cs index b4501ea7..df4443c9 100644 --- a/src/LibHac/Common/StringUtils.cs +++ b/src/LibHac/Common/StringUtils.cs @@ -33,6 +33,18 @@ namespace LibHac.Common return i; } + public static int GetLength(ReadOnlySpan s, int maxLen) + { + int i = 0; + + while (i < maxLen && i < s.Length && s[i] != 0) + { + i++; + } + + return i; + } + /// /// Concatenates 2 byte strings. /// diff --git a/src/LibHac/Fs/ContentStorage.cs b/src/LibHac/Fs/ContentStorage.cs index c9545d71..c74d2763 100644 --- a/src/LibHac/Fs/ContentStorage.cs +++ b/src/LibHac/Fs/ContentStorage.cs @@ -20,7 +20,7 @@ namespace LibHac.Fs Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName); if (rc.IsFailure()) return rc; - FileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId); if (rc.IsFailure()) return rc; diff --git a/src/LibHac/Fs/CustomStorage.cs b/src/LibHac/Fs/CustomStorage.cs index b1081b37..f97727b1 100644 --- a/src/LibHac/Fs/CustomStorage.cs +++ b/src/LibHac/Fs/CustomStorage.cs @@ -11,7 +11,7 @@ namespace LibHac.Fs Result rc = MountHelpers.CheckMountName(mountName); if (rc.IsFailure()) return rc; - FileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); rc = fsProxy.OpenCustomStorageFileSystem(out IFileSystem customFs, storageId); if (rc.IsFailure()) return rc; diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 999aa7a6..76ce211b 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -6,7 +6,7 @@ namespace LibHac.Fs public partial class FileSystemClient { private FileSystemServer FsSrv { get; } - private FileSystemProxy FsProxy { get; set; } + private IFileSystemProxy FsProxy { get; set; } private FileSystemManager FsManager { get; } private readonly object _fspInitLocker = new object(); @@ -17,7 +17,7 @@ namespace LibHac.Fs FsManager = new FileSystemManager(timer); } - public FileSystemProxy GetFileSystemProxyServiceObject() + public IFileSystemProxy GetFileSystemProxyServiceObject() { if (FsProxy != null) return FsProxy; diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index a579ea05..cfa8c405 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -2,10 +2,12 @@ using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Spl; namespace LibHac.FsService { - public class FileSystemProxy + public class FileSystemProxy : IFileSystemProxy { private FileSystemProxyCore FsProxyCore { get; } @@ -17,7 +19,7 @@ namespace LibHac.FsService public long SaveDataSize { get; private set; } public long SaveDataJournalSize { get; private set; } - public string SaveDataRootPath { get; private set; } + public FsPath SaveDataRootPath { get; } = default; public bool AutoCreateSaveData { get; private set; } private const ulong SaveIndexerId = 0x8000000000000000; @@ -30,10 +32,19 @@ namespace LibHac.FsService CurrentProcess = -1; SaveDataSize = 0x2000000; SaveDataJournalSize = 0x1000000; - SaveDataRootPath = string.Empty; AutoCreateSaveData = true; } + public Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, TitleId titleId, FileSystemType type) + { + throw new NotImplementedException(); + } + + public Result OpenFileSystemWithPatch(out IFileSystem fileSystem, TitleId titleId, FileSystemType type) + { + throw new NotImplementedException(); + } + public Result SetCurrentProcess(long processId) { CurrentProcess = processId; @@ -41,104 +52,100 @@ namespace LibHac.FsService return Result.Success; } - public Result DisableAutoSaveDataCreation() + public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) { - AutoCreateSaveData = false; - - return Result.Success; + throw new NotImplementedException(); } - public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) + public Result OpenDataFileSystemByCurrentProcess(out IFileSystem fileSystem) { - if (saveDataSize < 0 || saveDataJournalSize < 0) - { - return ResultFs.InvalidSize; - } - - SaveDataSize = saveDataSize; - SaveDataJournalSize = saveDataJournalSize; - - return Result.Success; + throw new NotImplementedException(); } - public Result SetSaveDataRootPath(string path) + public Result OpenDataFileSystemByProgramId(out IFileSystem fileSystem, TitleId titleId) { - // Missing permission check - - if (path.Length > PathTools.MaxPathLength) - { - return ResultFs.TooLongPath; - } - - SaveDataRootPath = path; - - return Result.Success; + throw new NotImplementedException(); } - public Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId) + public Result OpenDataStorageByCurrentProcess(out IStorage storage) { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenBisFileSystem(out fileSystem, rootPath, partitionId); + throw new NotImplementedException(); } - public Result OpenSdCardFileSystem(out IFileSystem fileSystem) + public Result OpenDataStorageByProgramId(out IStorage storage, TitleId programId) { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenSdCardFileSystem(out fileSystem); + throw new NotImplementedException(); } - public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId) + public Result OpenDataStorageByDataId(out IStorage storage, TitleId dataId, StorageId storageId) { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId); + throw new NotImplementedException(); } - public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId) + public Result OpenPatchDataStorageByCurrentProcess(out IStorage storage) { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId); + throw new NotImplementedException(); } - public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, - SaveDataAttribute attribute) + public Result OpenDataFileSystemWithProgramIndex(out IFileSystem fileSystem, byte programIndex) { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - fileSystem = default; - - if (!IsSystemSaveDataId(attribute.SaveId)) return ResultFs.InvalidArgument.Log(); - - Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, spaceId, - attribute, false, true); - if (rc.IsFailure()) return rc; - - // Missing check if the current title owns the save data or can open it - - fileSystem = saveFs; - - return Result.Success; + throw new NotImplementedException(); } - public Result SetSdCardEncryptionSeed(ReadOnlySpan seed) + public Result OpenDataStorageWithProgramIndex(out IStorage storage, byte programIndex) { - // todo: use struct instead of byte span - if (seed.Length != 0x10) return ResultFs.InvalidSize; + throw new NotImplementedException(); + } - // Missing permission check + public Result RegisterSaveDataFileSystemAtomicDeletion(ReadOnlySpan saveDataIds) + { + throw new NotImplementedException(); + } - Result rc = FsProxyCore.SetSdCardEncryptionSeed(seed); - if (rc.IsFailure()) return rc; + public Result DeleteSaveDataFileSystem(ulong saveDataId) + { + throw new NotImplementedException(); + } - // todo: Reset save data indexer + public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } - return Result.Success; + public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute2 attribute) + { + throw new NotImplementedException(); + } + + public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result CreateSaveDataFileSystem(ref SaveDataAttribute2 attribute, ref SaveDataCreateInfo createInfo, + ref SaveMetaCreateInfo metaCreateInfo) + { + throw new NotImplementedException(); + } + + public Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute2 attribute, ref SaveDataCreateInfo createInfo, + ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt) + { + throw new NotImplementedException(); + } + + public Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute2 attribute, ref SaveDataCreateInfo createInfo) + { + throw new NotImplementedException(); + } + + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize) + { + throw new NotImplementedException(); } private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId, - SaveDataSpaceId spaceId, SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) + SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) { bool hasFixedId = attribute.SaveId != 0 && attribute.UserId.Id == Id128.InvalidId; @@ -152,7 +159,7 @@ namespace LibHac.FsService } Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId, - SaveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); + SaveDataRootPath.ToString(), openReadOnly, attribute.Type, cacheExtraData); if (saveFsResult.IsSuccess()) return Result.Success; @@ -170,7 +177,395 @@ namespace LibHac.FsService return ResultFs.TargetNotFound; } - private bool IsSystemSaveDataId(ulong id) + public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute) + { + throw new NotImplementedException(); + } + + public Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, + ref SaveDataAttribute attribute) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, + ref SaveDataAttribute attribute) + { + // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + fileSystem = default; + + if (!IsSystemSaveDataId(attribute.SaveId)) return ResultFs.InvalidArgument.Log(); + + Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, spaceId, + ref attribute, false, true); + if (rc.IsFailure()) return rc; + + // Missing check if the current title owns the save data or can open it + + fileSystem = saveFs; + + return Result.Success; + } + + public Result ReadSaveDataFileSystemExtraData(Span extraDataBuffer, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(Span extraDataBuffer, SaveDataSpaceId spaceId, + ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(Span extraDataBuffer, SaveDataSpaceId spaceId, + ref SaveDataAttribute2 attribute) + { + throw new NotImplementedException(); + } + + public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer) + { + throw new NotImplementedException(); + } + + public Result WriteSaveDataFileSystemExtraDataBySaveDataAttribute(ref SaveDataAttribute2 attribute, SaveDataSpaceId spaceId, + ReadOnlySpan extraDataBuffer, ReadOnlySpan maskBuffer) + { + throw new NotImplementedException(); + } + + public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer, + ReadOnlySpan maskBuffer) + { + throw new NotImplementedException(); + } + + public Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId dirId) + { + throw new NotImplementedException(); + } + + public Result SetBisRootForHost(BisPartitionId partitionId, ref FsPath path) + { + throw new NotImplementedException(); + } + + public Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId) + { + fileSystem = default; + + // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + + Result rc = PathTools.Normalize(out U8Span normalizedPath, rootPath); + if (rc.IsFailure()) return rc; + + return FsProxyCore.OpenBisFileSystem(out fileSystem, normalizedPath.ToString(), partitionId); + } + + public Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId) + { + throw new NotImplementedException(); + } + + public Result InvalidateBisCache() + { + throw new NotImplementedException(); + } + + public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath) + { + throw new NotImplementedException(); + } + + public Result OpenSdCardFileSystem(out IFileSystem fileSystem) + { + // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + + return FsProxyCore.OpenSdCardFileSystem(out fileSystem); + } + + public Result FormatSdCardFileSystem() + { + throw new NotImplementedException(); + } + + public Result FormatSdCardDryRun() + { + throw new NotImplementedException(); + } + + public Result IsExFatSupported(out bool isSupported) + { + throw new NotImplementedException(); + } + + public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId) + { + throw new NotImplementedException(); + } + + public Result OpenDeviceOperator(out IDeviceOperator deviceOperator) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId, + ref SaveDataFilter filter) + { + throw new NotImplementedException(); + } + + public Result FindSaveDataWithFilter(out long count, Span saveDataInfoBuffer, SaveDataSpaceId spaceId, + ref SaveDataFilter filter) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInfoReaderOnlyCacheStorage(out ISaveDataInfoReader infoReader) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute2 attribute, + SaveMetaType type) + { + throw new NotImplementedException(); + } + + public Result DeleteCacheStorage(short index) + { + throw new NotImplementedException(); + } + + public Result GetCacheStorageSize(out long dataSize, out long journalSize, short index) + { + throw new NotImplementedException(); + } + + public Result ListAccessibleSaveDataOwnerId(out int readCount, Span idBuffer, TitleId programId, int startIndex, + int bufferIdCount) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) + { + if (saveDataSize < 0 || saveDataJournalSize < 0) + { + return ResultFs.InvalidSize; + } + + SaveDataSize = saveDataSize; + SaveDataJournalSize = saveDataJournalSize; + + return Result.Success; + } + public Result SetSaveDataRootPath(ref FsPath path) + { + // Missing permission check + + if (StringUtils.GetLength(path.Str, FsPath.MaxLength + 1) > FsPath.MaxLength) + { + return ResultFs.TooLongPath; + } + + StringUtils.Copy(SaveDataRootPath.Str, path.Str); + + return Result.Success; + } + + public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId) + { + // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + + return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId); + } + + public Result OpenCloudBackupWorkStorageFileSystem(out IFileSystem fileSystem, CloudBackupWorkStorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId) + { + // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + + return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId); + } + + public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionId) + { + throw new NotImplementedException(); + } + + public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) + { + throw new NotImplementedException(); + } + + public Result SetCurrentPosixTimeWithTimeDifference(long time, int difference) + { + throw new NotImplementedException(); + } + + public Result GetRightsId(out RightsId rightsId, TitleId programId, StorageId storageId) + { + throw new NotImplementedException(); + } + + public Result GetRightsIdByPath(out RightsId rightsId, ref FsPath path) + { + throw new NotImplementedException(); + } + + public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, ref FsPath path) + { + throw new NotImplementedException(); + } + + public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey) + { + throw new NotImplementedException(); + } + + public Result UnregisterExternalKey(ref RightsId rightsId) + { + throw new NotImplementedException(); + } + + public Result UnregisterAllExternalKey() + { + throw new NotImplementedException(); + } + public Result SetSdCardEncryptionSeed(ReadOnlySpan seed) + { + // todo: use struct instead of byte span + if (seed.Length != 0x10) return ResultFs.InvalidSize; + + // Missing permission check + + Result rc = FsProxyCore.SetSdCardEncryptionSeed(seed); + if (rc.IsFailure()) return rc; + + // todo: Reset save data indexer + + return Result.Success; + } + + public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span readBuffer) + { + throw new NotImplementedException(); + } + + public Result VerifySaveDataFileSystem(ulong saveDataId, Span readBuffer) + { + throw new NotImplementedException(); + } + + public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) + { + throw new NotImplementedException(); + } + + public Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result CorruptSaveDataFileSystem(ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result CreatePaddingFile(long size) + { + throw new NotImplementedException(); + } + + public Result DeleteAllPaddingFiles() + { + throw new NotImplementedException(); + } + + public Result DisableAutoSaveDataCreation() + { + AutoCreateSaveData = false; + + return Result.Success; + } + + public Result SetGlobalAccessLogMode(int mode) + { + throw new NotImplementedException(); + } + + public Result GetGlobalAccessLogMode(out int mode) + { + throw new NotImplementedException(); + } + + public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount) + { + throw new NotImplementedException(); + } + + public Result OutputAccessLogToSdCard(U8Span logString) + { + throw new NotImplementedException(); + } + + public Result RegisterUpdatePartition() + { + throw new NotImplementedException(); + } + + public Result OpenRegisteredUpdatePartition(out IFileSystem fileSystem) + { + throw new NotImplementedException(); + } + + public Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan key) + { + throw new NotImplementedException(); + } + + public Result SetSdCardAccessibility(bool isAccessible) + { + throw new NotImplementedException(); + } + + public Result IsSdCardAccessible(out bool isAccessible) + { + throw new NotImplementedException(); + } + + private static bool IsSystemSaveDataId(ulong id) { return (long)id < 0; } diff --git a/src/LibHac/FsService/FileSystemServer.cs b/src/LibHac/FsService/FileSystemServer.cs index 849ba674..09d17f40 100644 --- a/src/LibHac/FsService/FileSystemServer.cs +++ b/src/LibHac/FsService/FileSystemServer.cs @@ -47,7 +47,7 @@ namespace LibHac.FsService return new FileSystemClient(this, timer); } - public FileSystemProxy CreateFileSystemProxyService() + public IFileSystemProxy CreateFileSystemProxyService() { return new FileSystemProxy(FsProxyCore, FsClient); } diff --git a/src/LibHac/FsSystem/FsPath.cs b/src/LibHac/FsSystem/FsPath.cs index b40e5b60..af82bb49 100644 --- a/src/LibHac/FsSystem/FsPath.cs +++ b/src/LibHac/FsSystem/FsPath.cs @@ -15,6 +15,7 @@ namespace LibHac.FsSystem public Span Str => SpanHelpers.CreateSpan(ref _str, MaxLength + 1); + public static implicit operator U8Span(FsPath value) => new U8Span(value.Str); public override string ToString() => StringUtils.Utf8ZToString(Str); } } diff --git a/src/LibHac/FsSystem/PathTools.cs b/src/LibHac/FsSystem/PathTools.cs index 3a0fcd51..742d0b51 100644 --- a/src/LibHac/FsSystem/PathTools.cs +++ b/src/LibHac/FsSystem/PathTools.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using LibHac.Common; using LibHac.Fs; #if HAS_FILE_SYSTEM_NAME @@ -51,6 +52,20 @@ namespace LibHac.FsSystem return normalized; } + public static Result Normalize(out U8Span normalizedPath, U8Span path) + { + if (path.Length == 0) + { + normalizedPath = path; + return Result.Success; + } + + // Todo: optimize + normalizedPath = new U8Span(Normalize(path.ToString())); + + return Result.Success; + } + // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information.