Add or update some save data FS shim classes for 14.0.0

- SaveData
- SaveDataForDebug
- SaveDataManagement
This commit is contained in:
Alex Barney 2022-04-30 01:13:10 -07:00
parent 0875f5950c
commit 366aa51912
4 changed files with 345 additions and 24 deletions

View file

@ -241,6 +241,16 @@ namespace LibHac.Fs.Impl
}
}
public ReadOnlySpan<byte> 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<byte> ToString(ContentType value)
{
switch (value)
@ -1083,6 +1093,16 @@ namespace LibHac.Fs.Impl
(byte)'d', (byte)':', (byte)' '
};
/// <summary>"<c>, save_data_format_type: </c>"</summary>
public static ReadOnlySpan<byte> 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)' '
};
/// <summary>"<c>, save_data_time_stamp: </c>"</summary>
public static ReadOnlySpan<byte> LogSaveDataTimeStamp => // ", save_data_time_stamp: "
new[]

View file

@ -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;
/// <summary>
/// Contains functions for mounting save data and checking if save data already exists or not.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
/// <remarks>Based on nnSdk 14.3.0</remarks>
[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<IFileSystem> 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<IFileSystemProxy> 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,

View file

@ -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;
/// <summary>
/// Contains save data functions used for debugging during development.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0</remarks>
[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<byte> 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<IFileSystemProxy> 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<IFileSystemProxy> 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<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fileSystemProxy.Get.UnsetSaveDataRootPath();
}
fs.Impl.LogResultErrorMessage(rc);
Abort.DoAbortUnlessSuccess(rc);
}
/// <summary>
/// Ensures the current application's debug save data is at least as large as the specified values.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="saveDataSize">The size of the usable space in the save data.</param>
/// <param name="saveDataJournalSize">The size of the save data's journal.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.TargetNotFound"/>: The save data was not found.<br/>
/// <see cref="ResultFs.TargetLocked"/>: The save data is currently open or otherwise in use.<br/>
/// <see cref="ResultFs.UsableSpaceNotEnough"/>: Insufficient free space to create or extend the save data.<br/>
/// <see cref="ResultFs.PermissionDenied"/>: Insufficient permissions.</returns>
public static Result EnsureSaveDataForDebug(this FileSystemClient fs, long saveDataSize, long saveDataJournalSize)
{
Result rc;
Span<byte> 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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.TargetNotFound"/>: The save data was not found.<br/>
/// <see cref="ResultFs.TargetLocked"/>: The save data is currently open or otherwise in use.<br/>
/// <see cref="ResultFs.PermissionDenied"/>: Insufficient permissions.</returns>
public static Result MountSaveDataForDebug(this FileSystemClient fs, U8Span mountName)
{
Result rc;
Span<byte> 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;
}
}
}

View file

@ -21,7 +21,7 @@ namespace LibHac.Fs
/// <summary>
/// Allows iterating through the <see cref="SaveDataInfo"/> of a list of save data.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
/// <remarks>Based on nnSdk 14.3.0</remarks>
public class SaveDataIterator : IDisposable
{
private readonly FileSystemClient _fsClient;
@ -81,10 +81,12 @@ namespace LibHac.Fs.Shim
/// <summary>
/// Contains functions for creating, deleting, and otherwise managing save data.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
/// <remarks>Based on nnSdk 14.3.0</remarks>
[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<IFileSystemProxy> 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<byte> 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)
{