diff --git a/src/LibHac/Fs/AccessLog.cs b/src/LibHac/Fs/AccessLog.cs index 74672ee4..7f631a58 100644 --- a/src/LibHac/Fs/AccessLog.cs +++ b/src/LibHac/Fs/AccessLog.cs @@ -241,6 +241,16 @@ namespace LibHac.Fs.Impl } } + public ReadOnlySpan ToString(SaveDataFormatType value) + { + switch (value) + { + case SaveDataFormatType.Normal: return new[] { (byte)'N', (byte)'o', (byte)'r', (byte)'m', (byte)'a', (byte)'l' }; + case SaveDataFormatType.NoJournal: return new[] { (byte)'N', (byte)'o', (byte)'J', (byte)'o', (byte)'u', (byte)'r', (byte)'n', (byte)'a', (byte)'l' }; + default: return ToValueString((int)value); + } + } + public ReadOnlySpan ToString(ContentType value) { switch (value) @@ -1083,6 +1093,16 @@ namespace LibHac.Fs.Impl (byte)'d', (byte)':', (byte)' ' }; + /// ", save_data_format_type: " + public static ReadOnlySpan LogSaveDataFormatType => // ", save_data_format_type: " + new[] + { + (byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'_', (byte)'d', + (byte)'a', (byte)'t', (byte)'a', (byte)'_', (byte)'f', (byte)'o', (byte)'r', (byte)'m', + (byte)'a', (byte)'t', (byte)'_', (byte)'t', (byte)'y', (byte)'p', (byte)'e', (byte)':', + (byte)' ' + }; + /// ", save_data_time_stamp: " public static ReadOnlySpan LogSaveDataTimeStamp => // ", save_data_time_stamp: " new[] diff --git a/src/LibHac/Fs/Shim/SaveData.cs b/src/LibHac/Fs/Shim/SaveData.cs index d64bede5..0dc83873 100644 --- a/src/LibHac/Fs/Shim/SaveData.cs +++ b/src/LibHac/Fs/Shim/SaveData.cs @@ -6,7 +6,7 @@ using LibHac.Fs.Impl; using LibHac.FsSrv.Sf; using LibHac.Ncm; using LibHac.Os; - +using LibHac.Util; using static LibHac.Fs.Impl.AccessLogStrings; using static LibHac.Fs.SaveData; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; @@ -17,12 +17,12 @@ namespace LibHac.Fs.Shim; /// /// Contains functions for mounting save data and checking if save data already exists or not. /// -/// Based on nnSdk 13.4.0 +/// Based on nnSdk 14.3.0 [SkipLocalsInit] public static class SaveData { - private const long SaveDataSizeForDebug = 0x2000000; - private const long SaveDataJournalSizeForDebug = 0x2000000; + private const long SaveDataTotalSizeMax = 0xFA000000; + private const int SaveDataBlockSize = 0x4000; private static Result OpenSaveDataInternalStorageFileSystemImpl(FileSystemClient fs, ref UniqueRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId) @@ -42,6 +42,39 @@ public static class SaveData return Result.Success; } + private static Result ExtendSaveDataIfNeeded(FileSystemClient fs, UserId userId, long saveDataSize, + long saveDataJournalSize) + { + // Find the save data for the current program. + Result rc = SaveDataFilter.Make(out SaveDataFilter filter, InvalidProgramId.Value, SaveDataType.Account, userId, + InvalidSystemSaveDataId, index: 0, SaveDataRank.Primary); + if (rc.IsFailure()) return rc.Miss(); + + rc = fs.Impl.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.User, in filter); + if (rc.IsFailure()) return rc.Miss(); + + SaveDataSpaceId spaceId = info.SpaceId; + ulong saveDataId = info.SaveDataId; + + // Get the current save data's sizes. + rc = fs.Impl.GetSaveDataAvailableSize(out long availableSize, spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + rc = fs.Impl.GetSaveDataJournalSize(out long journalSize, spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + // Extend the save data if it's not large enough. + if (availableSize < saveDataSize || journalSize < saveDataJournalSize) + { + long newSaveDataSize = Math.Max(saveDataSize, availableSize); + long newJournalSize = Math.Max(saveDataJournalSize, journalSize); + rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, newSaveDataSize, newJournalSize); + if (rc.IsFailure()) return rc.Miss(); + } + + return Result.Success; + } + private static Result MountSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName, SaveDataSpaceId spaceId, ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index) { @@ -84,16 +117,26 @@ public static class SaveData return Result.Success; } - public static Result EnsureSaveDataForDebug(this FileSystemClientImpl fs, UserId userId) + public static Result EnsureSaveDataImpl(this FileSystemClientImpl fs, UserId userId, long saveDataSize, + long saveDataJournalSize, bool extendIfNeeded) { + if (!Alignment.IsAlignedPow2(saveDataSize, SaveDataBlockSize)) + return ResultFs.InvalidSize.Log(); + + if (!Alignment.IsAlignedPow2(saveDataJournalSize, SaveDataBlockSize)) + return ResultFs.InvalidSize.Log(); + + if (saveDataSize + saveDataJournalSize > SaveDataTotalSizeMax) + return ResultFs.InvalidSize.Log(); + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, ProgramId.InvalidId, SaveDataType.Account, - InvalidUserId, InvalidSystemSaveDataId); + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, InvalidProgramId, SaveDataType.Account, + userId, InvalidSystemSaveDataId); if (rc.IsFailure()) return rc; - rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, SaveDataSizeForDebug, - SaveDataJournalSizeForDebug, default, SaveDataFlags.None, SaveDataSpaceId.User); + rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, saveDataSize, saveDataJournalSize, + ownerId: 0, SaveDataFlags.None, SaveDataSpaceId.User); if (rc.IsFailure()) return rc.Miss(); var metaInfo = new SaveDataMetaInfo @@ -106,17 +149,25 @@ public static class SaveData if (rc.IsFailure()) { - // Return successfully if the save data already exists + // Ensure the save is large enough if it already exists if (ResultFs.PathAlreadyExists.Includes(rc)) - rc.Catch(); + { + if (extendIfNeeded) + { + rc = ExtendSaveDataIfNeeded(fs.Fs, userId, saveDataSize, saveDataJournalSize); + if (rc.IsFailure()) return rc.Miss(); + } + } else + { return rc.Miss(); + } } return Result.Success; } - public static Result MountSaveData(this FileSystemClientImpl fs, U8Span mountName, UserId userId) + public static Result MountSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName, UserId userId) { return MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, ProgramId.InvalidId, userId, SaveDataType.Account, openReadOnly: false, index: 0); @@ -130,7 +181,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) { Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveData(fs.Impl, mountName, InvalidUserId); + rc = MountSaveDataImpl(fs.Impl, mountName, InvalidUserId); Tick end = fs.Hos.Os.GetSystemTick(); var sb = new U8StringBuilder(logBuffer, true); @@ -141,7 +192,7 @@ public static class SaveData } else { - rc = MountSaveData(fs.Impl, mountName, InvalidUserId); + rc = MountSaveDataImpl(fs.Impl, mountName, InvalidUserId); } fs.Impl.AbortIfNeeded(rc); @@ -150,7 +201,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, @@ -185,7 +236,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, @@ -220,7 +271,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result IsSaveDataExisting(this FileSystemClientImpl fs, out bool exists, UserId userId) @@ -292,7 +343,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName) @@ -324,7 +375,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, int index) @@ -358,7 +409,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId) @@ -391,7 +442,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, @@ -426,7 +477,7 @@ public static class SaveData if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - return rc; + return Result.Success; } public static Result OpenSaveDataInternalStorageFileSystem(this FileSystemClient fs, diff --git a/src/LibHac/Fs/Shim/SaveDataForDebug.cs b/src/LibHac/Fs/Shim/SaveDataForDebug.cs new file mode 100644 index 00000000..88353f83 --- /dev/null +++ b/src/LibHac/Fs/Shim/SaveDataForDebug.cs @@ -0,0 +1,189 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs.Impl; +using LibHac.FsSrv.Sf; +using LibHac.Os; + +using static LibHac.Fs.Impl.AccessLogStrings; +using static LibHac.Fs.SaveData; + +namespace LibHac.Fs.Shim; + +/// +/// Contains save data functions used for debugging during development. +/// +/// Based on nnSdk 14.3.0 +[SkipLocalsInit] +public static class SaveDataForDebug +{ + private const long SaveDataSizeForDebug = 0x2000000; + private const long SaveDataJournalSizeForDebug = 0x2000000; + + public static void SetSaveDataRootPath(this FileSystemClient fs, U8Span path) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = SetRootPath(fs, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogQuote); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = SetRootPath(fs, path); + } + + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnlessSuccess(rc); + + static Result SetRootPath(FileSystemClient fs, U8Span path) + { + Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); + if (rc.IsFailure()) return rc.Miss(); + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + rc = fileSystemProxy.Get.SetSaveDataRootPath(in sfPath); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + public static void UnsetSaveDataRootPath(this FileSystemClient fs) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + rc = fileSystemProxy.Get.UnsetSaveDataRootPath(); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, U8Span.Empty); + } + else + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + rc = fileSystemProxy.Get.UnsetSaveDataRootPath(); + } + + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnlessSuccess(rc); + } + + /// + /// Ensures the current application's debug save data is at least as large as the specified values. + /// + /// Each application can have a single debug save. This save data is not associated with any + /// user account and is intended for debug use when developing an application. + /// The to use. + /// The size of the usable space in the save data. + /// The size of the save data's journal. + /// : The operation was successful.
+ /// : The save data was not found.
+ /// : The save data is currently open or otherwise in use.
+ /// : Insufficient free space to create or extend the save data.
+ /// : Insufficient permissions.
+ public static Result EnsureSaveDataForDebug(this FileSystemClient fs, long saveDataSize, long saveDataJournalSize) + { + Result rc; + Span logBuffer = stackalloc byte[0x60]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Ensure(fs, saveDataSize, saveDataJournalSize); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSize).AppendFormat(saveDataSize, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(saveDataJournalSize, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Ensure(fs, saveDataSize, saveDataJournalSize); + } + + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + + static Result Ensure(FileSystemClient fs, long saveDataSize, long saveDataJournalSize) + { + UserId userIdForDebug = InvalidUserId; + + Result rc = fs.Impl.EnsureSaveDataImpl(userIdForDebug, saveDataSize, saveDataJournalSize, + extendIfNeeded: true); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + /// + /// Mounts the debug save data for the current application. Each application can have its own debug save + /// that is not associated with any user account. + /// + /// Each application can have a single debug save. This save data is not associated with any + /// user account and is intended for debug use when developing an application. + /// The to use. + /// The mount name at which the file system will be mounted. + /// : The operation was successful.
+ /// : The save data was not found.
+ /// : The save data is currently open or otherwise in use.
+ /// : Insufficient permissions.
+ public static Result MountSaveDataForDebug(this FileSystemClient fs, U8Span mountName) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName); + } + + if (rc.IsFailure()) return rc.Miss(); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName) + { + UserId userIdForDebug = InvalidUserId; + + Result rc = fs.Impl.EnsureSaveDataImpl(userIdForDebug, SaveDataSizeForDebug, SaveDataJournalSizeForDebug, + extendIfNeeded: false); + if (rc.IsFailure()) return rc.Miss(); + + rc = fs.Impl.MountSaveDataImpl(mountName, userIdForDebug); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index 2c978a9e..84f77f32 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -21,7 +21,7 @@ namespace LibHac.Fs /// /// Allows iterating through the of a list of save data. /// - /// Based on nnSdk 13.4.0 + /// Based on nnSdk 14.3.0 public class SaveDataIterator : IDisposable { private readonly FileSystemClient _fsClient; @@ -81,10 +81,12 @@ namespace LibHac.Fs.Shim /// /// Contains functions for creating, deleting, and otherwise managing save data. /// - /// Based on nnSdk 13.4.0 + /// Based on nnSdk 14.3.0 [SkipLocalsInit] public static class SaveDataManagement { + private const int SaveDataBlockSize = 0x4000; + private class CacheStorageListCache : IDisposable { public readonly struct CacheEntry @@ -990,6 +992,65 @@ namespace LibHac.Fs.Shim return CreateSystemSaveData(fs, spaceId, saveDataId, InvalidUserId, ownerId, size, journalSize, flags); } + public static Result CreateSystemSaveData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags, + SaveDataFormatType formatType) + { + if (formatType == SaveDataFormatType.NoJournal && journalSize != 0) + return ResultFs.InvalidArgument.Log(); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, InvalidProgramId, SaveDataType.System, + userId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + rc = SaveDataCreationInfo2.Make(out SaveDataCreationInfo2 creationInfo, in attribute, size, journalSize, + SaveDataBlockSize, ownerId, flags, spaceId, formatType); + if (rc.IsFailure()) return rc.Miss(); + + creationInfo.MetaType = SaveDataMetaType.None; + creationInfo.MetaSize = 0; + + return fileSystemProxy.Get.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo).Ret(); + } + + public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags, SaveDataFormatType formatType) + { + Result rc; + Span logBuffer = stackalloc byte[0x180]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = CreateSystemSaveData(fs.Impl, spaceId, saveDataId, InvalidUserId, ownerId, size, journalSize, + flags, formatType); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogUserId).AppendFormat(InvalidUserId.Id.High, 'X', 16).AppendFormat(InvalidUserId.Id.Low, 'X', 16) + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8) + .Append(LogSaveDataFormatType).Append(idString.ToString(formatType)); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = CreateSystemSaveData(fs.Impl, spaceId, saveDataId, InvalidUserId, ownerId, size, journalSize, + flags, formatType); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + public static Result ExtendSaveData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, ulong saveDataId, long saveDataSize, long journalSize) {