diff --git a/src/LibHac/Fs/AesXtsDirectory.cs b/src/LibHac/Fs/AesXtsDirectory.cs index 99caa17a..e3400a65 100644 --- a/src/LibHac/Fs/AesXtsDirectory.cs +++ b/src/LibHac/Fs/AesXtsDirectory.cs @@ -33,6 +33,7 @@ namespace LibHac.Fs } else { + // todo: FS returns invalid file entries with a size of 0 long size = GetAesXtsFileSize(entry.FullPath); if (size == -1) continue; diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index d5f09439..edb8f0b2 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -42,4 +42,15 @@ Secure = 1, Writable = 2 } + + public enum SaveDataSpaceId + { + System = 0, + User = 1, + SdSystem = 2, + TemporaryStorage = 3, + SdCache = 4, + ProperSystem = 100, + Safe = 101 + } } diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index 549f5d60..4cc73c86 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -11,6 +11,9 @@ public static Result InsufficientFreeSpace => new Result(ModuleFs, 30); public static Result MountNameAlreadyExists => new Result(ModuleFs, 60); + public static Result ResultPartitionNotFound => new Result(ModuleFs, 1002); + public static Result TargetNotFound => new Result(ModuleFs, 1002); + public static Result NotImplemented => new Result(ModuleFs, 3001); public static Result Result3002 => new Result(ModuleFs, 3002); public static Result SaveDataPathAlreadyExists => new Result(ModuleFs, 3003); diff --git a/src/LibHac/FsService/Creators/FileSystemCreators.cs b/src/LibHac/FsService/Creators/FileSystemCreators.cs index 2ae3114f..2afa63df 100644 --- a/src/LibHac/FsService/Creators/FileSystemCreators.cs +++ b/src/LibHac/FsService/Creators/FileSystemCreators.cs @@ -8,6 +8,8 @@ namespace LibHac.FsService.Creators public IPartitionFileSystemCreator PartitionFileSystemCreator { get; set; } public IStorageOnNcaCreator StorageOnNcaCreator { get; set; } public IFatFileSystemCreator FatFileSystemCreator { get; set; } + public IHostFileSystemCreator HostFileSystemCreator { get; set; } + public ITargetManagerFileSystemCreator TargetManagerFileSystemCreator { get; set; } public ISubDirectoryFileSystemCreator SubDirectoryFileSystemCreator { get; set; } public IBuiltInStorageCreator BuiltInStorageCreator { get; set; } public ISdStorageCreator SdStorageCreator { get; set; } diff --git a/src/LibHac/FsService/Creators/IHostFileSystemCreator.cs b/src/LibHac/FsService/Creators/IHostFileSystemCreator.cs new file mode 100644 index 00000000..f00dde73 --- /dev/null +++ b/src/LibHac/FsService/Creators/IHostFileSystemCreator.cs @@ -0,0 +1,10 @@ +using LibHac.Fs; + +namespace LibHac.FsService.Creators +{ + public interface IHostFileSystemCreator + { + Result Create(out IFileSystem fileSystem, bool someBool); + Result Create(out IFileSystem fileSystem, string path, bool someBool); + } +} \ No newline at end of file diff --git a/src/LibHac/FsService/Creators/ITargetManagerFileSystemCreator.cs b/src/LibHac/FsService/Creators/ITargetManagerFileSystemCreator.cs new file mode 100644 index 00000000..7de7c485 --- /dev/null +++ b/src/LibHac/FsService/Creators/ITargetManagerFileSystemCreator.cs @@ -0,0 +1,10 @@ +using LibHac.Fs; + +namespace LibHac.FsService.Creators +{ + public interface ITargetManagerFileSystemCreator + { + Result Create(out IFileSystem fileSystem, bool someBool); + Result GetCaseSensitivePath(out bool isSuccess, ref string path); + } +} \ No newline at end of file diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index 5d61e0be..6e5b675b 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common; using LibHac.Fs; namespace LibHac.FsService @@ -13,6 +14,8 @@ namespace LibHac.FsService public string SaveDataRootPath { get; private set; } public bool AutoCreateSaveData { get; private set; } + private const ulong SaveIndexerId = 0x8000000000000000; + internal FileSystemProxy(FileSystemProxyCore fsProxyCore) { FsProxyCore = fsProxyCore; @@ -86,10 +89,29 @@ namespace LibHac.FsService return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId); } + public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, + SaveDataAttribute attribute) + { + // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter + fileSystem = default; + + if (!IsSystemSaveDataId(attribute.SaveId)) return ResultFs.InvalidArgument.Log(); + + Result saveFsResult = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out ulong saveDataId, spaceId, + attribute, false, true); + if (saveFsResult.IsFailure()) return saveFsResult.Log(); + + // Missing check if the current title owns the save data or can open it + + fileSystem = saveFs; + + return Result.Success; + } + public Result SetSdCardEncryptionSeed(ReadOnlySpan seed) { // todo: use struct instead of byte span - if (seed.Length != 0x16) return ResultFs.InvalidSize; + if (seed.Length != 0x10) return ResultFs.InvalidSize; // Missing permission check @@ -100,5 +122,42 @@ namespace LibHac.FsService return Result.Success; } + + private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId, + SaveDataSpaceId spaceId, SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) + { + bool hasFixedId = attribute.SaveId != 0 && attribute.UserId.Id == Id128.InvalidId; + + if (hasFixedId) + { + saveDataId = attribute.SaveId; + } + else + { + throw new NotImplementedException(); + } + + Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId, + SaveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); + + if (saveFsResult.IsSuccess()) return Result.Success; + + if (saveFsResult == ResultFs.PathNotFound || saveFsResult == ResultFs.TargetNotFound) return saveFsResult; + + if (saveDataId != SaveIndexerId) + { + if(hasFixedId) + { + // todo: remove save indexer entry + } + } + + return ResultFs.TargetNotFound; + } + + private bool IsSystemSaveDataId(ulong id) + { + return (long)id < 0; + } } } diff --git a/src/LibHac/FsService/FileSystemProxyCore.cs b/src/LibHac/FsService/FileSystemProxyCore.cs index 25c486c0..6f1a67a7 100644 --- a/src/LibHac/FsService/FileSystemProxyCore.cs +++ b/src/LibHac/FsService/FileSystemProxyCore.cs @@ -1,5 +1,6 @@ using System; using LibHac.Fs; +using LibHac.Fs.Save; using LibHac.FsService.Creators; namespace LibHac.FsService @@ -77,9 +78,129 @@ namespace LibHac.FsService public Result SetSdCardEncryptionSeed(ReadOnlySpan seed) { seed.CopyTo(SdEncryptionSeed); - FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed); + //FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed); return Result.Success; } + + public bool AllowDirectorySaveData(SaveDataSpaceId spaceId, string saveDataRootPath) + { + return spaceId == SaveDataSpaceId.User && !string.IsNullOrWhiteSpace(saveDataRootPath); + } + + public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, + string saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) + { + fileSystem = default; + + Result openSaveDirResult = OpenSaveDataDirectory(out IFileSystem saveDirFs, spaceId, saveDataRootPath, true); + if (openSaveDirResult.IsFailure()) return openSaveDirResult.Log(); + + bool allowDirectorySaveData = AllowDirectorySaveData(spaceId, saveDataRootPath); + bool useDeviceUniqueMac = Util.UseDeviceUniqueSaveMac(spaceId); + + if (allowDirectorySaveData) + { + try + { + saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId)); + } + catch (HorizonResultException ex) + { + return ex.ResultValue; + } + } + + // Missing save FS cache lookup + + Result saveFsResult = FsCreators.SaveDataFileSystemCreator.Create(out IFileSystem saveFs, + out ISaveDataExtraDataAccessor extraDataAccessor, saveDirFs, saveDataId, allowDirectorySaveData, + useDeviceUniqueMac, type, null); + + if (saveFsResult.IsFailure()) return saveFsResult.Log(); + + if (cacheExtraData) + { + // Missing extra data caching + } + + fileSystem = openReadOnly ? new ReadOnlyFileSystem(saveFs) : saveFs; + + return Result.Success; + } + + public Result OpenSaveDataDirectory(out IFileSystem fileSystem, SaveDataSpaceId spaceId, string saveDataRootPath, bool openOnHostFs) + { + if (openOnHostFs && AllowDirectorySaveData(spaceId, saveDataRootPath)) + { + Result hostFsResult = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, false); + + if (hostFsResult.IsFailure()) + { + fileSystem = default; + return hostFsResult.Log(); + } + + return Util.CreateSubFileSystem(out fileSystem, hostFs, saveDataRootPath, true); + } + + string dirName = spaceId == SaveDataSpaceId.TemporaryStorage ? "/temp" : "/save"; + + return OpenSaveDataDirectoryImpl(out fileSystem, spaceId, dirName, true); + } + + public Result OpenSaveDataDirectoryImpl(out IFileSystem fileSystem, SaveDataSpaceId spaceId, string saveDirName, bool createIfMissing) + { + fileSystem = default; + + switch (spaceId) + { + case SaveDataSpaceId.System: + Result sysFsResult = OpenBisFileSystem(out IFileSystem sysFs, string.Empty, BisPartitionId.System); + if (sysFsResult.IsFailure()) return sysFsResult.Log(); + + return Util.CreateSubFileSystem(out fileSystem, sysFs, saveDirName, createIfMissing); + + case SaveDataSpaceId.User: + case SaveDataSpaceId.TemporaryStorage: + Result userFsResult = OpenBisFileSystem(out IFileSystem userFs, string.Empty, BisPartitionId.System); + if (userFsResult.IsFailure()) return userFsResult.Log(); + + return Util.CreateSubFileSystem(out fileSystem, userFs, saveDirName, createIfMissing); + + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdCache: + Result sdFsResult = OpenSdCardFileSystem(out IFileSystem sdFs); + if (sdFsResult.IsFailure()) return sdFsResult.Log(); + + string sdSaveDirPath = $"/{NintendoDirectoryName}{saveDirName}"; + + Result sdSubResult = Util.CreateSubFileSystem(out IFileSystem sdSubFs, sdFs, sdSaveDirPath, createIfMissing); + if (sdSubResult.IsFailure()) return sdSubResult.Log(); + + return FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, sdSubFs, + EncryptedFsKeyId.Save, SdEncryptionSeed); + + case SaveDataSpaceId.ProperSystem: + Result sysProperFsResult = OpenBisFileSystem(out IFileSystem sysProperFs, string.Empty, BisPartitionId.SystemProperPartition); + if (sysProperFsResult.IsFailure()) return sysProperFsResult.Log(); + + return Util.CreateSubFileSystem(out fileSystem, sysProperFs, saveDirName, createIfMissing); + + case SaveDataSpaceId.Safe: + Result safeFsResult = OpenBisFileSystem(out IFileSystem safeFs, string.Empty, BisPartitionId.SafeMode); + if (safeFsResult.IsFailure()) return safeFsResult.Log(); + + return Util.CreateSubFileSystem(out fileSystem, safeFs, saveDirName, createIfMissing); + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + private string GetSaveDataIdPath(ulong saveDataId) + { + return $"/{saveDataId:x16}"; + } } } diff --git a/src/LibHac/FsService/IFileSystemProxy.cs b/src/LibHac/FsService/IFileSystemProxy.cs new file mode 100644 index 00000000..cd3ac661 --- /dev/null +++ b/src/LibHac/FsService/IFileSystemProxy.cs @@ -0,0 +1,20 @@ +using System; +using LibHac.Fs; + +namespace LibHac.FsService +{ + public interface IFileSystemProxy + { + Result SetCurrentProcess(long processId); + Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId); + Result OpenSdCardFileSystem(out IFileSystem fileSystem); + Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId); + Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, SaveDataAttribute attribute); + Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, SaveDataAttribute attribute); + Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId); + Result SetSdCardEncryptionSeed(ReadOnlySpan seed); + Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize); + Result SetSaveDataRootPath(string path); + Result DisableAutoSaveDataCreation(); + } +} \ No newline at end of file diff --git a/src/LibHac/FsService/Util.cs b/src/LibHac/FsService/Util.cs index 9ebcab9e..3b151c1e 100644 --- a/src/LibHac/FsService/Util.cs +++ b/src/LibHac/FsService/Util.cs @@ -43,5 +43,14 @@ namespace LibHac.FsService return Result.Success; } + + public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.System || + spaceId == SaveDataSpaceId.User || + spaceId == SaveDataSpaceId.TemporaryStorage || + spaceId == SaveDataSpaceId.ProperSystem || + spaceId == SaveDataSpaceId.Safe; + } } }