Ensure all of Shim.SaveDataManagement is updated for 13.1.0

This commit is contained in:
Alex Barney 2022-01-18 20:45:48 -07:00
parent dc2a1cfaef
commit 74ed435dee
12 changed files with 2230 additions and 1845 deletions

View file

@ -1135,5 +1135,33 @@ namespace LibHac.Fs.Impl
(byte)',', (byte)' ', (byte)'f', (byte)'u', (byte)'n', (byte)'c', (byte)'t', (byte)'i', (byte)',', (byte)' ', (byte)'f', (byte)'u', (byte)'n', (byte)'c', (byte)'t', (byte)'i',
(byte)'o', (byte)'n', (byte)':', (byte)' ', (byte)'"' (byte)'o', (byte)'n', (byte)':', (byte)' ', (byte)'"'
}; };
/// <summary>"<c>, cachestoragelist_handle: 0x</c>"</summary>
public static ReadOnlySpan<byte> LogCacheStorageListHandle =>
new[]
{
(byte)',', (byte)' ', (byte)'c', (byte)'a', (byte)'c', (byte)'h', (byte)'e', (byte)'s',
(byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', (byte)'l', (byte)'i',
(byte)'s', (byte)'t', (byte)'_', (byte)'h', (byte)'a', (byte)'n', (byte)'d', (byte)'l',
(byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
/// <summary>"<c>, infobuffercount: 0x</c>"</summary>
public static ReadOnlySpan<byte> LogInfoBufferCount =>
new[]
{
(byte)',', (byte)' ', (byte)'i', (byte)'n', (byte)'f', (byte)'o', (byte)'b', (byte)'u',
(byte)'f', (byte)'f', (byte)'e', (byte)'r', (byte)'c', (byte)'o', (byte)'u', (byte)'n',
(byte)'t', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
/// <summary>"<c>, cache_storage_count: </c>"</summary>
public static ReadOnlySpan<byte> LogCacheStorageCount =>
new[]
{
(byte)',', (byte)' ', (byte)'c', (byte)'a', (byte)'c', (byte)'h', (byte)'e', (byte)'_',
(byte)'s', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', (byte)'_',
(byte)'c', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)':', (byte)' '
};
} }
} }

View file

@ -29,7 +29,7 @@ public static class ApplicationSaveDataManagement
Result CreateAccountSaveFunc() Result CreateAccountSaveFunc()
{ {
UserId userId = ConvertAccountUidToFsUserId(uidLocal); UserId userId = Utility.ConvertAccountUidToFsUserId(uidLocal);
return fs.CreateSaveData(applicationId, userId, saveDataOwnerId, accountSaveDataSize, return fs.CreateSaveData(applicationId, userId, saveDataOwnerId, accountSaveDataSize,
accountSaveJournalSize, SaveDataFlags.None); accountSaveJournalSize, SaveDataFlags.None);
} }
@ -465,9 +465,4 @@ public static class ApplicationSaveDataManagement
return rc; return rc;
} }
public static UserId ConvertAccountUidToFsUserId(Uid uid)
{
return new UserId(uid.Id.High, uid.Id.Low);
}
} }

View file

@ -0,0 +1,19 @@
using LibHac.Common.FixedArrays;
namespace LibHac.Fs;
public readonly struct CacheStorageListHandle
{
internal readonly object Cache;
internal CacheStorageListHandle(object cache)
{
Cache = cache;
}
}
public struct CacheStorageInfo
{
public int Index;
public Array28<byte> Reserved;
}

View file

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common; using LibHac.Common;
using LibHac.Diag; using LibHac.Diag;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
@ -12,8 +14,12 @@ using LibHac.Sf;
using LibHac.Time; using LibHac.Time;
using static LibHac.Fs.Impl.AccessLogStrings; using static LibHac.Fs.Impl.AccessLogStrings;
namespace LibHac.Fs.Shim; 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>
public class SaveDataIterator : IDisposable public class SaveDataIterator : IDisposable
{ {
private readonly FileSystemClient _fsClient; private readonly FileSystemClient _fsClient;
@ -66,10 +72,60 @@ public class SaveDataIterator : IDisposable
return Result.Success; return Result.Success;
} }
} }
}
namespace LibHac.Fs.Shim
{
/// <summary>
/// Contains functions for creating, deleting, and otherwise managing save data.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
[SkipLocalsInit] [SkipLocalsInit]
public static class SaveDataManagement public static class SaveDataManagement
{ {
private class CacheStorageListCache : IDisposable
{
public readonly struct CacheEntry
{
private readonly int _index;
public CacheEntry(int index) => _index = index;
public int GetCacheStorageIndex() => _index;
}
private int _position;
private List<CacheEntry> _entryList;
public CacheStorageListCache()
{
_position = 0;
_entryList = new List<CacheEntry>();
}
public void Dispose() { }
public Result PushBack(in CacheEntry entry)
{
_entryList.Add(entry);
// The original code can have allocation failures here
return Result.Success;
}
public ref readonly CacheEntry PopFront()
{
if (_position >= _entryList.Count)
return ref Unsafe.NullRef<CacheEntry>();
return ref CollectionsMarshal.AsSpan(_entryList)[_position++];
}
public static CacheStorageListCache GetCacheStorageListCache(CacheStorageListHandle handle)
{
return (CacheStorageListCache)handle.Cache;
}
}
public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs,
out SaveDataExtraData extraData, ulong saveDataId) out SaveDataExtraData extraData, ulong saveDataId)
{ {
@ -241,7 +297,7 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account,
userId, 0); userId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags,
@ -263,7 +319,7 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat,
Fs.SaveData.InvalidUserId, 0); Fs.SaveData.InvalidUserId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size,
@ -289,7 +345,7 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device,
Fs.SaveData.InvalidUserId, 0); Fs.SaveData.InvalidUserId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags,
@ -311,7 +367,7 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Cache, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Cache,
Fs.SaveData.InvalidUserId, 0, index); Fs.SaveData.InvalidUserId, staticSaveDataId: 0, index);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags,
@ -473,7 +529,7 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value), Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value),
SaveDataType.Device, Fs.SaveData.InvalidUserId, 0); SaveDataType.Device, Fs.SaveData.InvalidUserId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId.User, in attribute); return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId.User, in attribute);
@ -530,8 +586,8 @@ public static class SaveDataManagement
return Result.Success; return Result.Success;
} }
public static Result ReadSaveDataIteratorSaveDataInfo(out long readCount, Span<SaveDataInfo> buffer, public static Result ReadSaveDataIteratorSaveDataInfo(this FileSystemClientImpl fs, out long readCount,
in SaveDataIterator iterator) Span<SaveDataInfo> buffer, in SaveDataIterator iterator)
{ {
Result rc = iterator.ReadSaveDataInfo(out readCount, buffer); Result rc = iterator.ReadSaveDataInfo(out readCount, buffer);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
@ -687,7 +743,7 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account,
userId, 0); userId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags,
@ -764,10 +820,10 @@ public static class SaveDataManagement
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Temporary, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Temporary,
Fs.SaveData.InvalidUserId, 0); Fs.SaveData.InvalidUserId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, 0, ownerId, flags, rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize: 0, ownerId, flags,
SaveDataSpaceId.Temporary); SaveDataSpaceId.Temporary);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
@ -844,7 +900,7 @@ public static class SaveDataManagement
public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId,
SaveDataSpaceId spaceId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) SaveDataSpaceId spaceId, ulong ownerId, long size, long journalSize, SaveDataFlags flags)
{ {
return CreateCacheStorage(fs, applicationId, spaceId, ownerId, 0, size, journalSize, flags); return CreateCacheStorage(fs, applicationId, spaceId, ownerId, index: 0, size, journalSize, flags);
} }
public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId,
@ -912,7 +968,7 @@ public static class SaveDataManagement
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size,
long journalSize, SaveDataFlags flags) long journalSize, SaveDataFlags flags)
{ {
return CreateSystemSaveData(fs, saveDataId, userId, 0, size, journalSize, flags); return CreateSystemSaveData(fs, saveDataId, userId, ownerId: 0, size, journalSize, flags);
} }
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size,
@ -924,7 +980,7 @@ public static class SaveDataManagement
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size, public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
long journalSize, SaveDataFlags flags) long journalSize, SaveDataFlags flags)
{ {
return CreateSystemSaveData(fs, saveDataId, Fs.SaveData.InvalidUserId, 0, size, journalSize, flags); return CreateSystemSaveData(fs, saveDataId, Fs.SaveData.InvalidUserId, ownerId: 0, size, journalSize, flags);
} }
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
@ -1006,22 +1062,23 @@ public static class SaveDataManagement
return rc; return rc;
} }
public static Result QuerySaveDataTotalSize(this FileSystemClientImpl fs, out long totalSize, long size, public static Result QuerySaveDataTotalSize(this FileSystemClientImpl fs, out long totalSize, long saveDataSize,
long journalSize) long saveDataJournalSize)
{ {
UnsafeHelpers.SkipParamInit(out totalSize); UnsafeHelpers.SkipParamInit(out totalSize);
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fileSystemProxy.Get.QuerySaveDataTotalSize(out long tempTotalSize, size, journalSize); Result rc = fileSystemProxy.Get.QuerySaveDataTotalSize(out long tempTotalSize, saveDataSize,
saveDataJournalSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
totalSize = tempTotalSize; totalSize = tempTotalSize;
return Result.Success; return Result.Success;
} }
public static Result QuerySaveDataTotalSize(this FileSystemClient fs, out long totalSize, long size, public static Result QuerySaveDataTotalSize(this FileSystemClient fs, out long totalSize, long saveDataSize,
long journalSize) long saveDataJournalSize)
{ {
Result rc; Result rc;
Span<byte> logBuffer = stackalloc byte[0x50]; Span<byte> logBuffer = stackalloc byte[0x50];
@ -1029,18 +1086,18 @@ public static class SaveDataManagement
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null))
{ {
Tick start = fs.Hos.Os.GetSystemTick(); Tick start = fs.Hos.Os.GetSystemTick();
rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, size, journalSize); rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, saveDataSize, saveDataJournalSize);
Tick end = fs.Hos.Os.GetSystemTick(); Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true); var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogSaveDataSize).AppendFormat(size, 'd') sb.Append(LogSaveDataSize).AppendFormat(saveDataSize, 'd')
.Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); .Append(LogSaveDataJournalSize).AppendFormat(saveDataJournalSize, 'd');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
} }
else else
{ {
rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, size, journalSize); rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, saveDataSize, saveDataJournalSize);
} }
fs.Impl.AbortIfNeeded(rc); fs.Impl.AbortIfNeeded(rc);
@ -1329,10 +1386,10 @@ public static class SaveDataManagement
static Result SetFlags(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId, static Result SetFlags(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId,
SaveDataFlags flags) SaveDataFlags flags)
{ {
var extraDataMask = new SaveDataExtraData(); SaveDataExtraData extraDataMask = default;
extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF);
var extraData = new SaveDataExtraData(); SaveDataExtraData extraData = default;
extraData.Flags = flags; extraData.Flags = flags;
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId,
@ -1411,10 +1468,10 @@ public static class SaveDataManagement
static Result SetTimeStamp(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, static Result SetTimeStamp(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
PosixTime timeStamp) PosixTime timeStamp)
{ {
var extraDataMask = new SaveDataExtraData(); SaveDataExtraData extraDataMask = default;
extraDataMask.TimeStamp = unchecked((long)0xFFFFFFFFFFFFFFFF); extraDataMask.TimeStamp = unchecked((long)0xFFFFFFFFFFFFFFFF);
var extraData = new SaveDataExtraData(); SaveDataExtraData extraData = default;
extraData.TimeStamp = timeStamp.Value; extraData.TimeStamp = timeStamp.Value;
return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask); return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask);
@ -1689,10 +1746,10 @@ public static class SaveDataManagement
static Result SetCommitId(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, long commitId) static Result SetCommitId(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, long commitId)
{ {
var extraDataMask = new SaveDataExtraData(); SaveDataExtraData extraDataMask = default;
extraDataMask.CommitId = unchecked((long)0xFFFFFFFFFFFFFFFF); extraDataMask.CommitId = unchecked((long)0xFFFFFFFFFFFFFFFF);
var extraData = new SaveDataExtraData(); SaveDataExtraData extraData = default;
extraData.CommitId = commitId; extraData.CommitId = commitId;
return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask); return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask);
@ -1885,12 +1942,165 @@ public static class SaveDataManagement
} }
} }
public static Result OpenCacheStorageList(this FileSystemClient fs, out CacheStorageListHandle handle)
{
UnsafeHelpers.SkipParamInit(out handle);
Result rc;
Span<byte> logBuffer = stackalloc byte[0x40];
CacheStorageListCache listCache;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Open(fs, out listCache);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogCacheStorageListHandle).AppendFormat(listCache.GetHashCode(), 'x');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Open(fs, out listCache);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
handle = new CacheStorageListHandle(listCache);
return Result.Success;
static Result Open(FileSystemClient fs, out CacheStorageListCache listCache)
{
UnsafeHelpers.SkipParamInit(out listCache);
CacheStorageListCache tempListCache = null;
Result rc = Utility.DoContinuouslyUntilSaveDataListFetched(fs.Hos, () =>
{
// Note: Nintendo uses the same CacheStorageListCache for every attempt to fetch the save data list
// without clearing it between runs. This means that if it has to retry fetching the list, the
// CacheStorageListCache may contain duplicate entries if the save data indexer was reset while this
// function was running.
tempListCache = new CacheStorageListCache();
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
using var reader = new SharedRef<ISaveDataInfoReader>();
Result result = fileSystemProxy.Get.OpenSaveDataInfoReaderOnlyCacheStorage(ref reader.Ref());
if (result.IsFailure()) return result.Miss();
while (true)
{
Unsafe.SkipInit(out SaveDataInfo info);
result = reader.Get.Read(out long readCount, new OutBuffer(SpanHelpers.AsByteSpan(ref info)));
if (result.IsFailure()) return result.Miss();
if (readCount == 0)
break;
var cacheEntry = new CacheStorageListCache.CacheEntry(info.Index);
result = tempListCache.PushBack(in cacheEntry);
if (result.IsFailure()) return result.Miss();
}
return Result.Success;
});
if (rc.IsFailure()) return rc.Miss();
Assert.SdkRequiresNotNull(tempListCache);
listCache = tempListCache;
return Result.Success;
}
}
public static Result ReadCacheStorageList(this FileSystemClient fs, out int storageInfoReadCount,
Span<CacheStorageInfo> storageInfoBuffer, CacheStorageListHandle handle)
{
UnsafeHelpers.SkipParamInit(out storageInfoReadCount);
Result rc;
Span<byte> logBuffer = stackalloc byte[0x70];
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Read(out storageInfoReadCount, storageInfoBuffer, handle);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogCacheStorageListHandle).AppendFormat(handle.Cache.GetHashCode(), 'x')
.Append(LogInfoBufferCount).AppendFormat(storageInfoBuffer.Length, 'X')
.Append(LogCacheStorageCount).AppendFormat(AccessLogImpl.DereferenceOutValue(in storageInfoReadCount, rc), 'd');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Read(out storageInfoReadCount, storageInfoBuffer, handle);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
static Result Read(out int storageCount, Span<CacheStorageInfo> infoBuffer, CacheStorageListHandle handle)
{
int count = 0;
var listCache = CacheStorageListCache.GetCacheStorageListCache(handle);
while (count < infoBuffer.Length)
{
ref readonly CacheStorageListCache.CacheEntry entry = ref listCache.PopFront();
// We're done iterating if we get a null ref
if (Unsafe.IsNullRef(ref Unsafe.AsRef(in entry)))
break;
infoBuffer[count] = default;
infoBuffer[count].Index = entry.GetCacheStorageIndex();
count++;
}
storageCount = count;
return Result.Success;
}
}
public static void CloseCacheStorageList(this FileSystemClient fs, CacheStorageListHandle handle)
{
Span<byte> logBuffer = stackalloc byte[0x40];
var listCache = CacheStorageListCache.GetCacheStorageListCache(handle);
listCache?.Dispose();
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null))
{
Tick start = fs.Hos.Os.GetSystemTick();
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogCacheStorageListHandle).AppendFormat(handle.Cache.GetHashCode(), 'x');
fs.Impl.OutputAccessLog(Result.Success, start, end, null, new U8Span(sb.Buffer));
}
}
public static Result UpdateSaveDataMacForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, public static Result UpdateSaveDataMacForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId,
ulong saveDataId) ulong saveDataId)
{ {
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); using SharedRef<IFileSystemProxy> fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject();
return fileSystemProxy.Get.UpdateSaveDataMacForDebug(spaceId, saveDataId); Result rc = fileSystemProxy.Get.UpdateSaveDataMacForDebug(spaceId, saveDataId);
fs.Impl.AbortIfNeeded(rc);
return rc;
} }
public static Result ListApplicationAccessibleSaveDataOwnerId(this FileSystemClient fs, out int readCount, public static Result ListApplicationAccessibleSaveDataOwnerId(this FileSystemClient fs, out int readCount,
@ -1979,7 +2189,7 @@ public static class SaveDataManagement
if (attribute.ProgramId == Fs.SaveData.InvalidProgramId) if (attribute.ProgramId == Fs.SaveData.InvalidProgramId)
attribute.ProgramId = Fs.SaveData.AutoResolveCallerProgramId; attribute.ProgramId = Fs.SaveData.AutoResolveCallerProgramId;
var extraDataMask = new SaveDataExtraData(); SaveDataExtraData extraDataMask = default;
extraDataMask.Flags = SaveDataFlags.Restore; extraDataMask.Flags = SaveDataFlags.Restore;
rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User, rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User,
@ -2024,12 +2234,12 @@ public static class SaveDataManagement
{ {
UnsafeHelpers.SkipParamInit(out saveSize, out journalSize); UnsafeHelpers.SkipParamInit(out saveSize, out journalSize);
var extraDataMask = new SaveDataExtraData(); SaveDataExtraData extraDataMask = default;
extraDataMask.DataSize = unchecked((long)0xFFFFFFFFFFFFFFFF); extraDataMask.DataSize = unchecked((long)0xFFFFFFFFFFFFFFFF);
extraDataMask.JournalSize = unchecked((long)0xFFFFFFFFFFFFFFFF); extraDataMask.JournalSize = unchecked((long)0xFFFFFFFFFFFFFFFF);
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value), Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value),
SaveDataType.Device, Fs.SaveData.InvalidUserId, 0); SaveDataType.Device, Fs.SaveData.InvalidUserId, staticSaveDataId: 0);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User, rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User,
@ -2043,3 +2253,4 @@ public static class SaveDataManagement
} }
} }
} }
}

50
src/LibHac/Fs/Utility.cs Normal file
View file

@ -0,0 +1,50 @@
using System;
using LibHac.Account;
using LibHac.Os;
namespace LibHac.Fs;
/// <summary>
/// Contains various utility functions used by FS client code.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0</remarks>
public static class Utility
{
public static UserId ConvertAccountUidToFsUserId(Uid uid)
{
return new UserId(uid.Id.High, uid.Id.Low);
}
public static Result CheckUid(HorizonClient hos, Uid uid)
{
throw new NotImplementedException();
}
public static Result DoContinuouslyUntilSaveDataListFetched(HorizonClient hos, Func<Result> listGetter)
{
const int maxTryCount = 5;
const int initialSleepTimeMs = 5;
const int sleepTimeMultiplier = 2;
Result lastResult = Result.Success;
long sleepTime = initialSleepTimeMs;
for (int i = 0; i < maxTryCount; i++)
{
Result rc = listGetter();
if (rc.IsSuccess())
return rc;
// Try again if any save data were added or removed while getting the list
if (!ResultFs.InvalidHandle.Includes(rc))
return rc.Miss();
lastResult = rc;
hos.Os.SleepThread(TimeSpan.FromMilliSeconds(sleepTime));
sleepTime *= sleepTimeMultiplier;
}
return lastResult.Log();
}
}

View file

@ -6,7 +6,8 @@ using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Impl;
using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv; namespace LibHac.FsSrv;

View file

@ -2,7 +2,8 @@
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv.FsCreator; namespace LibHac.FsSrv.FsCreator;

View file

@ -2,9 +2,10 @@
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using LibHac.Util; using LibHac.Util;
using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv.FsCreator; namespace LibHac.FsSrv.FsCreator;
public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator, IDisposable public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator, IDisposable

View file

@ -10,6 +10,7 @@ using LibHac.FsSystem;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
using LibHac.Util; using LibHac.Util;
using Path = LibHac.Fs.Path; using Path = LibHac.Fs.Path;
using Utility = LibHac.FsSystem.Utility;
namespace LibHac.Tools.FsSystem; namespace LibHac.Tools.FsSystem;

View file

@ -35,7 +35,7 @@ public class ApplicationSaveDataManagementTests
Assert.Equal(1, entriesRead); Assert.Equal(1, entriesRead);
Assert.Equal(applicationId, info[0].ProgramId); Assert.Equal(applicationId, info[0].ProgramId);
Assert.Equal(ConvertAccountUidToFsUserId(userId), info[0].UserId); Assert.Equal(Utility.ConvertAccountUidToFsUserId(userId), info[0].UserId);
Assert.Equal(SaveDataType.Account, info[0].Type); Assert.Equal(SaveDataType.Account, info[0].Type);
} }

View file

@ -577,6 +577,73 @@ public class SaveDataManagement
Assert.Equal(timeStamp, actualTimeStamp); Assert.Equal(timeStamp, actualTimeStamp);
} }
[Fact]
public void OpenCacheStorageList_ReadIntoLargeBuffer_AllIndexesAreRead()
{
var applicationId = new Ncm.ApplicationId(1);
Horizon hos = HorizonFactory.CreateBasicHorizon();
HorizonClient client = hos.CreateHorizonClient(new ProgramLocation(applicationId, StorageId.BuiltInSystem),
(AccessControlBits.Bits)ulong.MaxValue);
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 6, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 2, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 3, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.OpenCacheStorageList(out CacheStorageListHandle handle));
var infoBuffer = new CacheStorageInfo[5];
Assert.Success(client.Fs.ReadCacheStorageList(out int readCount, infoBuffer, handle));
Assert.Equal(4, readCount);
Assert.Equal(0, infoBuffer[0].Index);
Assert.Equal(2, infoBuffer[1].Index);
Assert.Equal(3, infoBuffer[2].Index);
Assert.Equal(6, infoBuffer[3].Index);
client.Fs.CloseCacheStorageList(handle);
}
[Fact]
public void OpenCacheStorageList_ReadIntoMultipleBuffers_AllIndexesAreRead()
{
var applicationId = new Ncm.ApplicationId(1);
Horizon hos = HorizonFactory.CreateBasicHorizon();
HorizonClient client = hos.CreateHorizonClient(new ProgramLocation(applicationId, StorageId.BuiltInSystem),
(AccessControlBits.Bits)ulong.MaxValue);
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 6, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 2, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 3, 0, 0, SaveDataFlags.None));
Assert.Success(client.Fs.OpenCacheStorageList(out CacheStorageListHandle handle));
var infoBuffer = new CacheStorageInfo[2];
Assert.Success(client.Fs.ReadCacheStorageList(out int readCount, infoBuffer, handle));
Assert.Equal(2, readCount);
Assert.Equal(0, infoBuffer[0].Index);
Assert.Equal(2, infoBuffer[1].Index);
Assert.Success(client.Fs.ReadCacheStorageList(out readCount, infoBuffer, handle));
Assert.Equal(2, readCount);
Assert.Equal(3, infoBuffer[0].Index);
Assert.Equal(6, infoBuffer[1].Index);
Assert.Success(client.Fs.ReadCacheStorageList(out readCount, infoBuffer, handle));
Assert.Equal(0, readCount);
client.Fs.CloseCacheStorageList(handle);
}
private static Result PopulateSaveData(FileSystemClient fs, int count, int seed = -1) private static Result PopulateSaveData(FileSystemClient fs, int count, int seed = -1)
{ {
if (seed == -1) if (seed == -1)

View file

@ -354,4 +354,15 @@ public class TypeLayoutTests
Assert.Equal(3, GetOffset(in s, in s.CompressionRate)); Assert.Equal(3, GetOffset(in s, in s.CompressionRate));
Assert.Equal(4, GetOffset(in s, in s.Reserved)); Assert.Equal(4, GetOffset(in s, in s.Reserved));
} }
[Fact]
public static void CacheStorageInfo_Layout()
{
var s = new CacheStorageInfo();
Assert.Equal(0x20, Unsafe.SizeOf<CacheStorageInfo>());
Assert.Equal(0, GetOffset(in s, in s.Index));
Assert.Equal(4, GetOffset(in s, in s.Reserved));
}
} }