diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs
index bb3b9c40..a6cc7858 100644
--- a/src/LibHac/Fs/Common/Path.cs
+++ b/src/LibHac/Fs/Common/Path.cs
@@ -11,21 +11,37 @@ namespace LibHac.Fs;
public struct PathFlags
{
- private uint _value;
+ private PathFormatFlags _formatFlags;
- public void AllowWindowsPath() => _value |= 1 << 0;
- public void AllowRelativePath() => _value |= 1 << 1;
- public void AllowEmptyPath() => _value |= 1 << 2;
- public void AllowMountName() => _value |= 1 << 3;
- public void AllowBackslash() => _value |= 1 << 4;
- public void AllowAllCharacters() => _value |= 1 << 5;
+ [Flags]
+ public enum PathFormatFlags
+ {
+ AllowWindowsPath = 1 << 0,
+ AllowRelativePath = 1 << 1,
+ AllowEmptyPath = 1 << 2,
+ AllowMountName = 1 << 3,
+ AllowBackslash = 1 << 4,
+ AllowInvalidCharacter = 1 << 5
+ }
- public readonly bool IsWindowsPathAllowed() => (_value & (1 << 0)) != 0;
- public readonly bool IsRelativePathAllowed() => (_value & (1 << 1)) != 0;
- public readonly bool IsEmptyPathAllowed() => (_value & (1 << 2)) != 0;
- 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 PathFlags(PathFormatFlags formatFlags)
+ {
+ _formatFlags = formatFlags;
+ }
+
+ 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;
}
///
diff --git a/src/LibHac/Fs/Common/PathFormatter.cs b/src/LibHac/Fs/Common/PathFormatter.cs
index 4636e467..b2061e8c 100644
--- a/src/LibHac/Fs/Common/PathFormatter.cs
+++ b/src/LibHac/Fs/Common/PathFormatter.cs
@@ -517,7 +517,7 @@ public static class PathFormatter
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();
totalLength += length;
@@ -639,7 +639,7 @@ public static class PathFormatter
}
res = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative,
- flags.AreAllCharactersAllowed());
+ flags.IsInvalidCharacterAllowed());
if (res.IsFailure()) return res.Miss();
return Result.Success;
diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs
index c9841619..c0ddfbf4 100644
--- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs
+++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs
@@ -293,6 +293,8 @@ public class FileSystemInterfaceAdapter : IFileSystemSf
_selfReference.Destroy();
}
+ public static PathFlags GetDefaultPathFlags() => new PathFlags();
+
private static ReadOnlySpan RootDir => "/"u8;
private Result SetUpPath(ref Path fsPath, ref readonly PathSf sfPath)
diff --git a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs
index f6dafdc4..8f5e3b8d 100644
--- a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs
+++ b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs
@@ -115,19 +115,57 @@ public static class SaveDataProperties
public static bool IsObsoleteSystemSaveData(in SaveDataInfo info)
{
+ foreach (ulong id in ObsoleteSystemSaveDataIdList)
+ {
+ if (info.StaticSaveDataId == id)
+ return true;
+ }
+
return false;
- throw new NotImplementedException();
}
public static bool IsWipingNeededAtCleanUp(in SaveDataInfo info)
{
- return false;
- throw new NotImplementedException();
+ switch (info.Type)
+ {
+ 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)
{
- 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)
@@ -161,4 +199,16 @@ public static class SaveDataProperties
return default;
}
}
+
+ private static ReadOnlySpan ObsoleteSystemSaveDataIdList => [0x8000000000000060, 0x8000000000000075];
+
+ private static ReadOnlySpan 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
+ ];
}
\ No newline at end of file
diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs
index 23dffb7b..50f9695b 100644
--- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs
+++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs
@@ -32,6 +32,12 @@ using Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv;
+///
+/// Creates locks for incrementing and decrementing the save data open-count semaphore
+/// from a to keep track of how many save data files are currently open.
+///
+/// Used by objects such as s that open save data files.
+///
Based on nnSdk 17.5.0 (FS 17.0.0)
file class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager
{
private SharedRef _saveService;
@@ -115,7 +121,7 @@ file static class Anonymous
: SaveDataFormatType.NoJournal;
}
- public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId)
+ public static Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, ulong processId, SaveDataSpaceId spaceId)
{
Assert.SdkNotNull(programInfo);
@@ -142,6 +148,10 @@ file static class Anonymous
}
}
+///
+/// Determines if a specified program has access to perform various functions on a specified save data.
+///
+/// Based on nnSdk 17.5.0 (FS 17.0.0)
file static class SaveDataAccessibilityChecker
{
public delegate Result ExtraDataReader(out SaveDataExtraData extraData);
@@ -565,7 +575,7 @@ file static class SaveDataAccessibilityChecker
///
/// 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.
-///
Based on nnSdk 14.3.0 (FS 14.1.0)
+///
Based on nnSdk 17.5.0 (FS 17.0.0)
internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface
{
private const int OpenEntrySemaphoreCount = 256;
@@ -761,8 +771,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
return DeleteSaveDataFileSystemCommon(spaceId, saveDataId).Ret();
}
- public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId,
- in SaveDataAttribute attribute)
+ public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute)
{
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
@@ -1725,8 +1734,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using var fileSystem = new SharedRef();
// Open the file system
- res = OpenSaveDataFileSystemCore(ref fileSystem.Ref, out ulong saveDataId, spaceId, in attribute,
- openReadOnly, cacheExtraData: true);
+ res = OpenSaveDataFileSystemCore(ref fileSystem.Ref, out ulong saveDataId, spaceId, in attribute, openReadOnly,
+ cacheExtraData: true);
if (res.IsFailure()) return res.Miss();
// 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(new OpenCountFileSystem(ref asyncFileSystem.Ref,
ref openEntryCountAdapter.Ref, ref mountCountSemaphore.Ref));
- var pathFlags = new PathFlags();
+ PathFlags pathFlags = FileSystemInterfaceAdapter.GetDefaultPathFlags();
pathFlags.AllowBackslash();
- pathFlags.AllowAllCharacters();
+ pathFlags.AllowInvalidCharacter();
using SharedRef fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, false);
@@ -1862,7 +1871,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
Result ReadExtraData(out SaveDataExtraData data)
{
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
@@ -1891,25 +1900,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using var openCountFileSystem = new SharedRef(
new OpenCountFileSystem(ref asyncFileSystem.Ref, ref openEntryCountAdapter.Ref));
- var pathFlags = new PathFlags();
+ PathFlags pathFlags = FileSystemInterfaceAdapter.GetDefaultPathFlags();
pathFlags.AllowBackslash();
- pathFlags.AllowAllCharacters();
+ pathFlags.AllowInvalidCharacter();
using SharedRef fileSystemAdapter =
- FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags,
- allowAllOperations: false);
+ FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref, pathFlags, allowAllOperations: false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
-
return Result.Success;
}
// ReSharper disable once UnusedParameter.Local
- // Nintendo used isTemporarySaveData in older FS versions, but never removed the parameter.
- private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
+ // 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 outExtraData, SaveDataSpaceId spaceId,
ulong saveDataId, bool isTemporarySaveData)
{
- UnsafeHelpers.SkipParamInit(out extraData);
+ UnsafeHelpers.SkipParamInit(out outExtraData);
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
using var accessor = new UniqueRef();
@@ -1921,14 +1928,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss();
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();
}
- private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData,
+ private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData,
Optional spaceId, ulong saveDataId, in SaveDataExtraData extraDataMask)
{
- UnsafeHelpers.SkipParamInit(out extraData);
+ UnsafeHelpers.SkipParamInit(out outExtraData);
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
@@ -1971,7 +1978,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
res = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue _, saveDataId);
if (res.IsFailure()) return res.Miss();
- resolvedSpaceId = spaceId.ValueRo;
+ resolvedSpaceId = spaceId.Value;
res = accessor.Get.GetInterface().GetKey(out key, saveDataId);
if (res.IsFailure()) return res.Miss();
@@ -1984,7 +1991,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
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();
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
@@ -1993,7 +2001,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss();
MaskExtraData(ref tempExtraData, in extraDataMask);
- extraData = tempExtraData;
+ outExtraData = tempExtraData;
return Result.Success;
}
@@ -2003,6 +2011,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraData.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
// Make a mask for reading the entire extra data
Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
@@ -2017,6 +2028,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraData.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
ref SaveDataExtraData extraDataRef = ref extraData.As();
Result res = GetProgramInfo(out ProgramInfo programInfo);
@@ -2045,6 +2059,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraData.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
ref SaveDataExtraData extraDataRef = ref extraData.As();
// Make a mask for reading the entire extra data
@@ -2060,9 +2077,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraDataMask.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraDataMask.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
if (extraData.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraData.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
ref readonly SaveDataExtraData maskRef = ref extraDataMask.As();
ref SaveDataExtraData extraDataRef = ref extraData.As();
@@ -2101,13 +2124,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss();
using var accessor = new UniqueRef();
-
res = OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId);
if (res.IsFailure()) return res.Miss();
res = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId);
if (res.IsFailure()) return res.Miss();
+ SaveDataType saveDataType = key.Type;
+
Result ReadExtraData(out SaveDataExtraData data)
{
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
@@ -2120,13 +2144,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
res = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, saveDataId,
- key.Type, in saveDataRootPath);
+ saveDataType, in saveDataRootPath);
if (res.IsFailure()) return res.Miss();
ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask);
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)
@@ -2134,6 +2158,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraData.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
ref readonly SaveDataExtraData extraDataRef = ref extraData.As();
SaveDataExtraData extraDataMask = default;
@@ -2167,9 +2194,15 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (extraData.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraData.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
if (extraDataMask.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (extraDataMask.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
ref readonly SaveDataExtraData extraDataRef = ref extraData.As();
ref readonly SaveDataExtraData maskRef = ref extraDataMask.As();
@@ -2213,7 +2246,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
- res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId);
+ res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId);
if (res.IsFailure()) return res.Miss();
using var filterReader = new UniqueRef();
@@ -2250,7 +2283,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal))
return ResultFs.PermissionDenied.Log();
- res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId);
+ res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId);
if (res.IsFailure()) return res.Miss();
using var filterReader = new UniqueRef();
@@ -2303,17 +2336,20 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (saveDataInfoBuffer.Size != Unsafe.SizeOf())
return ResultFs.InvalidArgument.Log();
+ if (saveDataInfoBuffer.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.NonGameCard);
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
- res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId);
+ res = CheckOpenSaveDataInfoReaderAccessControl(programInfo, _processId, spaceId);
if (res.IsFailure())
{
if (!ResultFs.PermissionDenied.Includes(res))
- return res;
+ return res.Miss();
// Don't have full info reader permissions. Check if we have find permissions.
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);
- return FindSaveDataWithFilterImpl(out count, out saveDataInfoBuffer.As(), spaceId, in infoFilter).Ret();
+ res = FindSaveDataWithFilterImpl(out var outCount, out saveDataInfoBuffer.As(), spaceId, in infoFilter);
+ if (res.IsFailure()) return res.Miss();
+
+ count = outCount;
+ return Result.Success;
}
private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId)
@@ -2519,10 +2559,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure())
{
- spaceId = SaveDataSpaceId.User;
-
- if (!ResultFs.TargetNotFound.Includes(res))
- return res;
+ if (ResultFs.TargetNotFound.Includes(res))
+ {
+ spaceId = SaveDataSpaceId.User;
+ }
+ else
+ {
+ return res.Miss();
+ }
}
return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId).Ret();
@@ -2536,7 +2580,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
- if (spaceId != SaveDataSpaceId.SdUser && spaceId != SaveDataSpaceId.User)
+ if (spaceId != SaveDataSpaceId.User && spaceId != SaveDataSpaceId.SdUser)
return ResultFs.InvalidSaveDataSpaceId.Log();
using var filterReader = new UniqueRef();
@@ -2686,7 +2730,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (res.IsFailure()) return res.Miss();
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)
@@ -2694,7 +2738,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
UnsafeHelpers.SkipParamInit(out spaceId);
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())
{
res = DoesCacheStorageExist(out bool existsOnSdCard, SaveDataSpaceId.SdUser);
@@ -2722,8 +2766,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{
UnsafeHelpers.SkipParamInit(out exists);
- var filter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache,
- userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary);
+ var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(saveSpaceId), new ProgramId(programId),
+ SaveDataType.Cache, userId: default, saveDataId: default, index: default, (int)SaveDataRank.Primary);
Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in filter);
if (result.IsFailure()) return result;
@@ -2954,6 +2998,9 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (bufferIdCount > 0)
{
+ if (idBuffer.IsNull)
+ return ResultFs.NullptrArgument.Log();
+
ids = idBuffer.AsSpan();
if (ids.Length < bufferIdCount)
@@ -3015,8 +3062,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath();
res = _serviceImpl.OpenSaveDataFileSystem(ref fileSystem.Ref, value.SpaceId, saveDataId,
- in saveDataRootPath,
- openReadOnly: false, saveDataType, cacheExtraData: true);
+ in saveDataRootPath, openReadOnly: false, saveDataType, cacheExtraData: true);
if (res.IsFailure()) return res.Miss();
// Verify the file system.
@@ -3027,7 +3073,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
}
finally
{
- // Make sure we don't leak any invalid data.
+ // Make sure we don't leak any data.
workBuffer.Buffer.Clear();
}
}
@@ -3305,8 +3351,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
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,
- saveInfo.UserId, saveInfo.SaveDataId, saveInfo.Index);
+ saveInfo.UserId, saveDataId, saveInfo.Index);
if (res.IsFailure()) return res.Miss();
using var fileSystem = new SharedRef();
@@ -3333,8 +3383,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{
using SharedRef saveService = GetSharedFromThis();
- Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore,
- ref saveService.Ref);
+ Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, ref saveService.Ref);
if (res.IsFailure()) return res.Miss();
return Result.Success;
@@ -3344,8 +3393,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
{
using SharedRef saveService = GetSharedFromThis();
- Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore,
- ref saveService.Ref);
+ Result res = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, ref saveService.Ref);
if (res.IsFailure()) return res.Miss();
return Result.Success;
@@ -3407,8 +3455,23 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
if (isInitialOpen)
{
- CleanUpSaveData(accessor.Get).IgnoreResult();
- CompleteSaveDataExtension(accessor.Get).IgnoreResult();
+ Unsafe.SkipInit(out Array80 stringBuffer);
+
+ 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);
@@ -3419,14 +3482,13 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt,
bool leaveUnfinalized)
{
- return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt,
- leaveUnfinalized);
+ return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, leaveUnfinalized);
}
- Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData,
+ Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData outExtraData,
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,
diff --git a/src/LibHac/Sf/Buffers.cs b/src/LibHac/Sf/Buffers.cs
index 125e7ece..cac5fa40 100644
--- a/src/LibHac/Sf/Buffers.cs
+++ b/src/LibHac/Sf/Buffers.cs
@@ -11,6 +11,7 @@ public readonly ref struct InBuffer
public int Size => _buffer.Length;
public ReadOnlySpan Buffer => _buffer;
+ public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer));
public InBuffer(ReadOnlySpan buffer)
{
@@ -46,6 +47,7 @@ public readonly ref struct OutBuffer
public int Size => _buffer.Length;
public Span Buffer => _buffer;
+ public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer));
public OutBuffer(Span buffer)
{
@@ -81,6 +83,7 @@ public readonly ref struct InArray where T : unmanaged
public int Size => _array.Length;
public ReadOnlySpan Array => _array;
+ public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_array));
public InArray(ReadOnlySpan array)
{
@@ -96,6 +99,7 @@ public readonly ref struct OutArray where T : unmanaged
public int Size => _array.Length;
public Span Array => _array;
+ public bool IsNull => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_array));
public OutArray(Span array)
{
diff --git a/tests/LibHac.Tests/Fs/PathFormatterTests.cs b/tests/LibHac.Tests/Fs/PathFormatterTests.cs
index 7dfce412..1b81b02b 100644
--- a/tests/LibHac.Tests/Fs/PathFormatterTests.cs
+++ b/tests/LibHac.Tests/Fs/PathFormatterTests.cs
@@ -455,7 +455,7 @@ public class PathFormatterTests
flags.AllowWindowsPath();
break;
case 'C':
- flags.AllowAllCharacters();
+ flags.AllowInvalidCharacter();
break;
default:
throw new NotSupportedException();