Update SaveDataFileSystemService for FS 17

This commit is contained in:
Alex Barney 2024-02-03 22:58:20 -07:00
parent e6d32b96d0
commit 037c5ace9e
7 changed files with 208 additions and 74 deletions

View file

@ -11,21 +11,37 @@ namespace LibHac.Fs;
public struct PathFlags public struct PathFlags
{ {
private uint _value; private PathFormatFlags _formatFlags;
public void AllowWindowsPath() => _value |= 1 << 0; [Flags]
public void AllowRelativePath() => _value |= 1 << 1; public enum PathFormatFlags
public void AllowEmptyPath() => _value |= 1 << 2; {
public void AllowMountName() => _value |= 1 << 3; AllowWindowsPath = 1 << 0,
public void AllowBackslash() => _value |= 1 << 4; AllowRelativePath = 1 << 1,
public void AllowAllCharacters() => _value |= 1 << 5; AllowEmptyPath = 1 << 2,
AllowMountName = 1 << 3,
AllowBackslash = 1 << 4,
AllowInvalidCharacter = 1 << 5
}
public readonly bool IsWindowsPathAllowed() => (_value & (1 << 0)) != 0; public PathFlags(PathFormatFlags formatFlags)
public readonly bool IsRelativePathAllowed() => (_value & (1 << 1)) != 0; {
public readonly bool IsEmptyPathAllowed() => (_value & (1 << 2)) != 0; _formatFlags = formatFlags;
public readonly bool IsMountNameAllowed() => (_value & (1 << 3)) != 0; }
public readonly bool IsBackslashAllowed() => (_value & (1 << 4)) != 0;
public readonly bool AreAllCharactersAllowed() => (_value & (1 << 5)) != 0; public void AllowWindowsPath() => _formatFlags |= PathFormatFlags.AllowWindowsPath;
public void AllowRelativePath() => _formatFlags |= PathFormatFlags.AllowRelativePath;
public void AllowEmptyPath() => _formatFlags |= PathFormatFlags.AllowEmptyPath;
public void AllowMountName() => _formatFlags |= PathFormatFlags.AllowMountName;
public void AllowBackslash() => _formatFlags |= PathFormatFlags.AllowBackslash;
public void AllowInvalidCharacter() => _formatFlags |= PathFormatFlags.AllowInvalidCharacter;
public readonly bool IsWindowsPathAllowed() => (_formatFlags & PathFormatFlags.AllowWindowsPath) != 0;
public readonly bool IsRelativePathAllowed() => (_formatFlags & PathFormatFlags.AllowRelativePath) != 0;
public readonly bool IsEmptyPathAllowed() => (_formatFlags & PathFormatFlags.AllowEmptyPath) != 0;
public readonly bool IsMountNameAllowed() => (_formatFlags & PathFormatFlags.AllowMountName) != 0;
public readonly bool IsBackslashAllowed() => (_formatFlags & PathFormatFlags.AllowBackslash) != 0;
public readonly bool IsInvalidCharacterAllowed() => (_formatFlags & PathFormatFlags.AllowInvalidCharacter) != 0;
} }
/// <summary> /// <summary>

View file

@ -517,7 +517,7 @@ public static class PathFormatter
return Result.Success; return Result.Success;
} }
res = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer, flags.AreAllCharactersAllowed()); res = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer, flags.IsInvalidCharacterAllowed());
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
totalLength += length; totalLength += length;
@ -639,7 +639,7 @@ public static class PathFormatter
} }
res = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative, res = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative,
flags.AreAllCharactersAllowed()); flags.IsInvalidCharacterAllowed());
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
return Result.Success; return Result.Success;

View file

@ -293,6 +293,8 @@ public class FileSystemInterfaceAdapter : IFileSystemSf
_selfReference.Destroy(); _selfReference.Destroy();
} }
public static PathFlags GetDefaultPathFlags() => new PathFlags();
private static ReadOnlySpan<byte> RootDir => "/"u8; private static ReadOnlySpan<byte> RootDir => "/"u8;
private Result SetUpPath(ref Path fsPath, ref readonly PathSf sfPath) private Result SetUpPath(ref Path fsPath, ref readonly PathSf sfPath)

View file

@ -115,19 +115,57 @@ public static class SaveDataProperties
public static bool IsObsoleteSystemSaveData(in SaveDataInfo info) public static bool IsObsoleteSystemSaveData(in SaveDataInfo info)
{ {
foreach (ulong id in ObsoleteSystemSaveDataIdList)
{
if (info.StaticSaveDataId == id)
return true;
}
return false; return false;
throw new NotImplementedException();
} }
public static bool IsWipingNeededAtCleanUp(in SaveDataInfo info) public static bool IsWipingNeededAtCleanUp(in SaveDataInfo info)
{ {
return false; switch (info.Type)
throw new NotImplementedException(); {
case SaveDataType.System:
break;
case SaveDataType.Account:
case SaveDataType.Bcat:
case SaveDataType.Device:
case SaveDataType.Temporary:
case SaveDataType.Cache:
return true;
default:
Abort.UnexpectedDefault();
break;
}
foreach (ulong id in SystemSaveDataIdWipingExceptionList)
{
if (info.StaticSaveDataId == id)
return false;
}
return true;
} }
public static bool IsValidSpaceIdForSaveDataMover(SaveDataType type, SaveDataSpaceId spaceId) public static bool IsValidSpaceIdForSaveDataMover(SaveDataType type, SaveDataSpaceId spaceId)
{ {
throw new NotImplementedException(); switch (type)
{
case SaveDataType.System:
case SaveDataType.Account:
case SaveDataType.Bcat:
case SaveDataType.Device:
case SaveDataType.Temporary:
return false;
case SaveDataType.Cache:
return spaceId == SaveDataSpaceId.User || spaceId == SaveDataSpaceId.SdUser;
default:
Abort.UnexpectedDefault();
return default;
}
} }
public static bool IsReconstructible(SaveDataType type, SaveDataSpaceId spaceId) public static bool IsReconstructible(SaveDataType type, SaveDataSpaceId spaceId)
@ -161,4 +199,16 @@ public static class SaveDataProperties
return default; return default;
} }
} }
private static ReadOnlySpan<ulong> ObsoleteSystemSaveDataIdList => [0x8000000000000060, 0x8000000000000075];
private static ReadOnlySpan<ulong> SystemSaveDataIdWipingExceptionList =>
[
0x8000000000000040, 0x8000000000000041, 0x8000000000000043, 0x8000000000000044, 0x8000000000000045,
0x8000000000000046, 0x8000000000000047, 0x8000000000000048, 0x8000000000000049, 0x800000000000004A,
0x8000000000000070, 0x8000000000000071, 0x8000000000000072, 0x8000000000000074, 0x8000000000000076,
0x8000000000000077, 0x8000000000000090, 0x8000000000000091, 0x8000000000000092, 0x80000000000000B0,
0x80000000000000C1, 0x80000000000000C2, 0x8000000000000120, 0x8000000000000121, 0x8000000000000180,
0x8000000000010003, 0x8000000000010004
];
} }

View file

@ -32,6 +32,12 @@ using Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv; namespace LibHac.FsSrv;
/// <summary>
/// Creates locks for incrementing and decrementing the save data open-count semaphore
/// from a <see cref="SaveDataFileSystemService"/> to keep track of how many save data files are currently open.
/// </summary>
/// <remarks>Used by objects such as <see cref="IFileSystem"/>s that open save data files.
/// <br/>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
file class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager file class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager
{ {
private SharedRef<SaveDataFileSystemService> _saveService; private SharedRef<SaveDataFileSystemService> _saveService;
@ -115,7 +121,7 @@ file static class Anonymous
: SaveDataFormatType.NoJournal; : SaveDataFormatType.NoJournal;
} }
public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, ulong processId, SaveDataSpaceId spaceId)
{ {
Assert.SdkNotNull(programInfo); Assert.SdkNotNull(programInfo);
@ -142,6 +148,10 @@ file static class Anonymous
} }
} }
/// <summary>
/// Determines if a specified program has access to perform various functions on a specified save data.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
file static class SaveDataAccessibilityChecker file static class SaveDataAccessibilityChecker
{ {
public delegate Result ExtraDataReader(out SaveDataExtraData extraData); public delegate Result ExtraDataReader(out SaveDataExtraData extraData);
@ -565,7 +575,7 @@ file static class SaveDataAccessibilityChecker
/// </summary> /// </summary>
/// <remarks>FS will have one instance of this class for every connected process. /// <remarks>FS will have one instance of this class for every connected process.
/// The FS permissions of the calling process are checked on every function call. /// The FS permissions of the calling process are checked on every function call.
/// <br/>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks> /// <br/>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface
{ {
private const int OpenEntrySemaphoreCount = 256; private const int OpenEntrySemaphoreCount = 256;
@ -761,8 +771,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
return DeleteSaveDataFileSystemCommon(spaceId, saveDataId).Ret(); return DeleteSaveDataFileSystemCommon(spaceId, saveDataId).Ret();
} }
public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute)
in SaveDataAttribute attribute)
{ {
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
@ -1725,8 +1734,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using var fileSystem = new SharedRef<IFileSystem>(); using var fileSystem = new SharedRef<IFileSystem>();
// Open the file system // Open the file system
res = OpenSaveDataFileSystemCore(ref fileSystem.Ref, out ulong saveDataId, spaceId, in attribute, res = OpenSaveDataFileSystemCore(ref fileSystem.Ref, out ulong saveDataId, spaceId, in attribute, openReadOnly,
openReadOnly, cacheExtraData: true); cacheExtraData: true);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
// Can't use attribute in a closure, so copy the needed field // Can't use attribute in a closure, so copy the needed field
@ -1764,9 +1773,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using var openCountFileSystem = new SharedRef<IFileSystem>(new OpenCountFileSystem(ref asyncFileSystem.Ref, using var openCountFileSystem = new SharedRef<IFileSystem>(new OpenCountFileSystem(ref asyncFileSystem.Ref,
ref openEntryCountAdapter.Ref, ref mountCountSemaphore.Ref)); ref openEntryCountAdapter.Ref, ref mountCountSemaphore.Ref));
var pathFlags = new PathFlags(); PathFlags pathFlags = FileSystemInterfaceAdapter.GetDefaultPathFlags();
pathFlags.AllowBackslash(); pathFlags.AllowBackslash();
pathFlags.AllowAllCharacters(); pathFlags.AllowInvalidCharacter();
using SharedRef<IFileSystemSf> fileSystemAdapter = using SharedRef<IFileSystemSf> fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, false); FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, false);
@ -1862,7 +1871,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
Result ReadExtraData(out SaveDataExtraData data) Result ReadExtraData(out SaveDataExtraData data)
{ {
using Path savePath = _saveDataRootPath.DangerousGetPath(); using Path savePath = _saveDataRootPath.DangerousGetPath();
return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, in savePath); return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, in savePath).Ret();
} }
// Check if we have permissions to open this save data // Check if we have permissions to open this save data
@ -1891,25 +1900,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using var openCountFileSystem = new SharedRef<IFileSystem>( using var openCountFileSystem = new SharedRef<IFileSystem>(
new OpenCountFileSystem(ref asyncFileSystem.Ref, ref openEntryCountAdapter.Ref)); new OpenCountFileSystem(ref asyncFileSystem.Ref, ref openEntryCountAdapter.Ref));
var pathFlags = new PathFlags(); PathFlags pathFlags = FileSystemInterfaceAdapter.GetDefaultPathFlags();
pathFlags.AllowBackslash(); pathFlags.AllowBackslash();
pathFlags.AllowAllCharacters(); pathFlags.AllowInvalidCharacter();
using SharedRef<IFileSystemSf> fileSystemAdapter = using SharedRef<IFileSystemSf> fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, allowAllOperations: false);
allowAllOperations: false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref); outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
return Result.Success; return Result.Success;
} }
// ReSharper disable once UnusedParameter.Local // ReSharper disable once UnusedParameter.Local
// Nintendo used isTemporarySaveData in older FS versions, but never removed the parameter. // The unused parameter hasn't been removed because this method is part of an interface. It was used in older FS versions.
private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData, SaveDataSpaceId spaceId,
ulong saveDataId, bool isTemporarySaveData) ulong saveDataId, bool isTemporarySaveData)
{ {
UnsafeHelpers.SkipParamInit(out extraData); UnsafeHelpers.SkipParamInit(out outExtraData);
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
using var accessor = new UniqueRef<SaveDataIndexerAccessor>(); using var accessor = new UniqueRef<SaveDataIndexerAccessor>();
@ -1921,14 +1928,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, return _serviceImpl.ReadSaveDataFileSystemExtraData(out outExtraData, spaceId, saveDataId, key.Type,
in saveDataRootPath).Ret(); in saveDataRootPath).Ret();
} }
private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData,
Optional<SaveDataSpaceId> spaceId, ulong saveDataId, in SaveDataExtraData extraDataMask) Optional<SaveDataSpaceId> spaceId, ulong saveDataId, in SaveDataExtraData extraDataMask)
{ {
UnsafeHelpers.SkipParamInit(out extraData); UnsafeHelpers.SkipParamInit(out outExtraData);
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
@ -1971,7 +1978,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue _, saveDataId); res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue _, saveDataId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
resolvedSpaceId = spaceId.ValueRo; resolvedSpaceId = spaceId.Value;
res = accessor.Get.GetInterface().GetKey(out key, saveDataId); res = accessor.Get.GetInterface().GetKey(out key, saveDataId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
@ -1984,7 +1991,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
in savePath); in savePath);
} }
res = SaveDataAccessibilityChecker.CheckReadExtraData(resolvedSpaceId, in key, in extraDataMask, programInfo, _processId, ReadExtraData); res = SaveDataAccessibilityChecker.CheckReadExtraData(resolvedSpaceId, in key, in extraDataMask, programInfo,
_processId, ReadExtraData);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
@ -1993,7 +2001,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
MaskExtraData(ref tempExtraData, in extraDataMask); MaskExtraData(ref tempExtraData, in extraDataMask);
extraData = tempExtraData; outExtraData = tempExtraData;
return Result.Success; return Result.Success;
} }
@ -2003,6 +2011,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraData.IsNull)
return ResultFs.NullptrArgument.Log();
// Make a mask for reading the entire extra data // Make a mask for reading the entire extra data
Unsafe.SkipInit(out SaveDataExtraData extraDataMask); Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
@ -2017,6 +2028,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraData.IsNull)
return ResultFs.NullptrArgument.Log();
ref SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>(); ref SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>();
Result res = GetProgramInfo(out ProgramInfo programInfo); Result res = GetProgramInfo(out ProgramInfo programInfo);
@ -2045,6 +2059,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraData.IsNull)
return ResultFs.NullptrArgument.Log();
ref SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>(); ref SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>();
// Make a mask for reading the entire extra data // Make a mask for reading the entire extra data
@ -2060,9 +2077,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraDataMask.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraDataMask.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraDataMask.IsNull)
return ResultFs.NullptrArgument.Log();
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraData.IsNull)
return ResultFs.NullptrArgument.Log();
ref readonly SaveDataExtraData maskRef = ref extraDataMask.As<SaveDataExtraData>(); ref readonly SaveDataExtraData maskRef = ref extraDataMask.As<SaveDataExtraData>();
ref SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>(); ref SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>();
@ -2101,13 +2124,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using var accessor = new UniqueRef<SaveDataIndexerAccessor>(); using var accessor = new UniqueRef<SaveDataIndexerAccessor>();
res = OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId); res = OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
res = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); res = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
SaveDataType saveDataType = key.Type;
Result ReadExtraData(out SaveDataExtraData data) Result ReadExtraData(out SaveDataExtraData data)
{ {
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
@ -2120,13 +2144,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
res = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, saveDataId, res = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, saveDataId,
key.Type, in saveDataRootPath); saveDataType, in saveDataRootPath);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask); ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask);
return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify,
in saveDataRootPath, key.Type, updateTimeStamp: false).Ret(); in saveDataRootPath, saveDataType, updateTimeStamp: false).Ret();
} }
public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData) public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData)
@ -2134,6 +2158,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraData.IsNull)
return ResultFs.NullptrArgument.Log();
ref readonly SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>(); ref readonly SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>();
SaveDataExtraData extraDataMask = default; SaveDataExtraData extraDataMask = default;
@ -2167,9 +2194,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraData.IsNull)
return ResultFs.NullptrArgument.Log();
if (extraDataMask.Size != Unsafe.SizeOf<SaveDataExtraData>()) if (extraDataMask.Size != Unsafe.SizeOf<SaveDataExtraData>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (extraDataMask.IsNull)
return ResultFs.NullptrArgument.Log();
ref readonly SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>(); ref readonly SaveDataExtraData extraDataRef = ref extraData.As<SaveDataExtraData>();
ref readonly SaveDataExtraData maskRef = ref extraDataMask.As<SaveDataExtraData>(); ref readonly SaveDataExtraData maskRef = ref extraDataMask.As<SaveDataExtraData>();
@ -2213,7 +2246,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
Result res = GetProgramInfo(out ProgramInfo programInfo); Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using var filterReader = new UniqueRef<SaveDataInfoFilterReader>(); using var filterReader = new UniqueRef<SaveDataInfoFilterReader>();
@ -2250,7 +2283,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal)) if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal))
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using var filterReader = new UniqueRef<SaveDataInfoFilterReader>(); using var filterReader = new UniqueRef<SaveDataInfoFilterReader>();
@ -2303,17 +2336,20 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (saveDataInfoBuffer.Size != Unsafe.SizeOf<SaveDataInfo>()) if (saveDataInfoBuffer.Size != Unsafe.SizeOf<SaveDataInfo>())
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
if (saveDataInfoBuffer.IsNull)
return ResultFs.NullptrArgument.Log();
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
Result res = GetProgramInfo(out ProgramInfo programInfo); Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId);
if (res.IsFailure()) if (res.IsFailure())
{ {
if (!ResultFs.PermissionDenied.Includes(res)) if (!ResultFs.PermissionDenied.Includes(res))
return res; return res.Miss();
// Don't have full info reader permissions. Check if we have find permissions. // Don't have full info reader permissions. Check if we have find permissions.
res = SaveDataAccessibilityChecker.CheckFind(spaceId, in filter, programInfo, _processId); res = SaveDataAccessibilityChecker.CheckFind(spaceId, in filter, programInfo, _processId);
@ -2328,7 +2364,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in tempFilter); var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in tempFilter);
return FindSaveDataWithFilterImpl(out count, out saveDataInfoBuffer.As<SaveDataInfo>(), spaceId, in infoFilter).Ret(); res = FindSaveDataWithFilterImpl(out var outCount, out saveDataInfoBuffer.As<SaveDataInfo>(), spaceId, in infoFilter);
if (res.IsFailure()) return res.Miss();
count = outCount;
return Result.Success;
} }
private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId)
@ -2519,10 +2559,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) if (res.IsFailure())
{ {
spaceId = SaveDataSpaceId.User; if (ResultFs.TargetNotFound.Includes(res))
{
if (!ResultFs.TargetNotFound.Includes(res)) spaceId = SaveDataSpaceId.User;
return res; }
else
{
return res.Miss();
}
} }
return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId).Ret(); return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId).Ret();
@ -2536,7 +2580,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
Result res = GetProgramInfo(out ProgramInfo programInfo); Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
if (spaceId != SaveDataSpaceId.SdUser && spaceId != SaveDataSpaceId.User) if (spaceId != SaveDataSpaceId.User && spaceId != SaveDataSpaceId.SdUser)
return ResultFs.InvalidSaveDataSpaceId.Log(); return ResultFs.InvalidSaveDataSpaceId.Log();
using var filterReader = new UniqueRef<SaveDataInfoFilterReader>(); using var filterReader = new UniqueRef<SaveDataInfoFilterReader>();
@ -2686,7 +2730,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
ulong resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; ulong resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value;
return GetCacheStorageSpaceId(out spaceId, resolvedProgramId); return GetCacheStorageSpaceId(out spaceId, resolvedProgramId).Ret();
} }
private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId) private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId)
@ -2694,7 +2738,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
UnsafeHelpers.SkipParamInit(out spaceId); UnsafeHelpers.SkipParamInit(out spaceId);
Result res; Result res;
// Cache storage on the SD card will always take priority over case storage in NAND // Cache storage on the SD card will always take priority over cache storage in NAND
if (_serviceImpl.IsSdCardAccessible()) if (_serviceImpl.IsSdCardAccessible())
{ {
res = DoesCacheStorageExist(out bool existsOnSdCard, SaveDataSpaceId.SdUser); res = DoesCacheStorageExist(out bool existsOnSdCard, SaveDataSpaceId.SdUser);
@ -2722,8 +2766,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{ {
UnsafeHelpers.SkipParamInit(out exists); UnsafeHelpers.SkipParamInit(out exists);
var filter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(saveSpaceId), new ProgramId(programId),
userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary); SaveDataType.Cache, userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary);
Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in filter); Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in filter);
if (result.IsFailure()) return result; if (result.IsFailure()) return result;
@ -2954,6 +2998,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (bufferIdCount > 0) if (bufferIdCount > 0)
{ {
if (idBuffer.IsNull)
return ResultFs.NullptrArgument.Log();
ids = idBuffer.AsSpan<Ncm.ApplicationId>(); ids = idBuffer.AsSpan<Ncm.ApplicationId>();
if (ids.Length < bufferIdCount) if (ids.Length < bufferIdCount)
@ -3015,8 +3062,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
res = _serviceImpl.OpenSaveDataFileSystem(ref fileSystem.Ref, value.SpaceId, saveDataId, res = _serviceImpl.OpenSaveDataFileSystem(ref fileSystem.Ref, value.SpaceId, saveDataId,
in saveDataRootPath, in saveDataRootPath, openReadOnly: false, saveDataType, cacheExtraData: true);
openReadOnly: false, saveDataType, cacheExtraData: true);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
// Verify the file system. // Verify the file system.
@ -3027,7 +3073,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
} }
finally finally
{ {
// Make sure we don't leak any invalid data. // Make sure we don't leak any data.
workBuffer.Buffer.Clear(); workBuffer.Buffer.Clear();
} }
} }
@ -3305,8 +3351,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback)
{ {
ulong saveDataId = IsStaticSaveDataIdValueRange(saveInfo.SaveDataId)
? saveInfo.SaveDataId
: InvalidSystemSaveDataId;
Result res = SaveDataAttribute.Make(out SaveDataAttribute attribute, saveInfo.ProgramId, saveInfo.Type, Result res = SaveDataAttribute.Make(out SaveDataAttribute attribute, saveInfo.ProgramId, saveInfo.Type,
saveInfo.UserId, saveInfo.SaveDataId, saveInfo.Index); saveInfo.UserId, saveDataId, saveInfo.Index);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using var fileSystem = new SharedRef<IFileSystem>(); using var fileSystem = new SharedRef<IFileSystem>();
@ -3333,8 +3383,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{ {
using SharedRef<SaveDataFileSystemService> saveService = GetSharedFromThis(); using SharedRef<SaveDataFileSystemService> saveService = GetSharedFromThis();
Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, ref saveService.Ref);
ref saveService.Ref);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
return Result.Success; return Result.Success;
@ -3344,8 +3393,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{ {
using SharedRef<SaveDataFileSystemService> saveService = GetSharedFromThis(); using SharedRef<SaveDataFileSystemService> saveService = GetSharedFromThis();
Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, ref saveService.Ref);
ref saveService.Ref);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
return Result.Success; return Result.Success;
@ -3407,8 +3455,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (isInitialOpen) if (isInitialOpen)
{ {
CleanUpSaveData(accessor.Get).IgnoreResult(); Unsafe.SkipInit(out Array80<byte> stringBuffer);
CompleteSaveDataExtension(accessor.Get).IgnoreResult();
Result result = CleanUpSaveData(accessor.Get);
if (result.IsFailure())
{
var sb = new U8StringBuilder(stringBuffer, true);
sb.Append("[fs] Failed to clean up save data ("u8).AppendFormat(result.Value, 'x').Append(")\n"u8);
Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, sb.Buffer);
}
result = CompleteSaveDataExtension(accessor.Get);
if (result.IsFailure())
{
var sb = new U8StringBuilder(stringBuffer, true);
sb.Append("[fs] Failed to complete save data extension ("u8).AppendFormat(result.Value, 'x').Append(")\n"u8);
Hos.Diag.Impl.LogImpl(Log.EmptyModuleName, LogSeverity.Info, sb.Buffer);
}
} }
outAccessor.Set(ref accessor.Ref); outAccessor.Set(ref accessor.Ref);
@ -3419,14 +3482,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional<HashSalt> hashSalt, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional<HashSalt> hashSalt,
bool leaveUnfinalized) bool leaveUnfinalized)
{ {
return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, leaveUnfinalized);
leaveUnfinalized);
} }
Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData,
SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData) SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData)
{ {
return ReadSaveDataFileSystemExtraDataCore(out extraData, spaceId, saveDataId, isTemporarySaveData); return ReadSaveDataFileSystemExtraDataCore(out outExtraData, spaceId, saveDataId, isTemporarySaveData);
} }
Result ISaveDataTransferCoreInterface.WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, Result ISaveDataTransferCoreInterface.WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId,

View file

@ -11,6 +11,7 @@ public readonly ref struct InBuffer
public int Size => _buffer.Length; public int Size => _buffer.Length;
public ReadOnlySpan<byte> Buffer => _buffer; public ReadOnlySpan<byte> Buffer => _buffer;
public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer));
public InBuffer(ReadOnlySpan<byte> buffer) public InBuffer(ReadOnlySpan<byte> buffer)
{ {
@ -46,6 +47,7 @@ public readonly ref struct OutBuffer
public int Size => _buffer.Length; public int Size => _buffer.Length;
public Span<byte> Buffer => _buffer; public Span<byte> Buffer => _buffer;
public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer));
public OutBuffer(Span<byte> buffer) public OutBuffer(Span<byte> buffer)
{ {
@ -81,6 +83,7 @@ public readonly ref struct InArray<T> where T : unmanaged
public int Size => _array.Length; public int Size => _array.Length;
public ReadOnlySpan<T> Array => _array; public ReadOnlySpan<T> Array => _array;
public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_array));
public InArray(ReadOnlySpan<T> array) public InArray(ReadOnlySpan<T> array)
{ {
@ -96,6 +99,7 @@ public readonly ref struct OutArray<T> where T : unmanaged
public int Size => _array.Length; public int Size => _array.Length;
public Span<T> Array => _array; public Span<T> Array => _array;
public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_array));
public OutArray(Span<T> array) public OutArray(Span<T> array)
{ {

View file

@ -455,7 +455,7 @@ public class PathFormatterTests
flags.AllowWindowsPath(); flags.AllowWindowsPath();
break; break;
case 'C': case 'C':
flags.AllowAllCharacters(); flags.AllowInvalidCharacter();
break; break;
default: default:
throw new NotSupportedException(); throw new NotSupportedException();