mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Implement extra data functions in SaveDataFileSystemService
This commit is contained in:
parent
e99d05cc84
commit
3056c5c296
6 changed files with 511 additions and 20 deletions
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using LibHac.Common.Keys;
|
|
||||||
using LibHac.Fs.Impl;
|
using LibHac.Fs.Impl;
|
||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
using LibHac.FsSrv.FsCreator;
|
using LibHac.FsSrv.FsCreator;
|
||||||
|
|
|
@ -162,6 +162,17 @@ namespace LibHac.FsSrv.Impl
|
||||||
return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, path);
|
return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long ConvertZeroCommitId(in SaveDataExtraData extraData)
|
||||||
|
{
|
||||||
|
if (extraData.CommitId != 0)
|
||||||
|
return extraData.CommitId;
|
||||||
|
|
||||||
|
Span<byte> hash = stackalloc byte[Crypto.Sha256.DigestSize];
|
||||||
|
|
||||||
|
Crypto.Sha256.GenerateSha256Hash(SpanHelpers.AsReadOnlyByteSpan(in extraData), hash);
|
||||||
|
return BitConverter.ToInt64(hash);
|
||||||
|
}
|
||||||
|
|
||||||
private static ReadOnlySpan<byte> FileSystemRootPath => // /
|
private static ReadOnlySpan<byte> FileSystemRootPath => // /
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
|
|
|
@ -301,6 +301,75 @@ namespace LibHac.FsSrv
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Result CheckWriteExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask,
|
||||||
|
ProgramInfo programInfo, ExtraDataGetter extraDataGetter)
|
||||||
|
{
|
||||||
|
AccessControl accessControl = programInfo.AccessControl;
|
||||||
|
|
||||||
|
if (mask.Flags != SaveDataFlags.None)
|
||||||
|
{
|
||||||
|
bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) ||
|
||||||
|
accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataFlags);
|
||||||
|
|
||||||
|
if (SaveDataProperties.IsSystemSaveData(attribute.Type))
|
||||||
|
{
|
||||||
|
Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo,
|
||||||
|
extraDataGetter);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
canAccess |= accessibility.CanWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mask.Flags & ~SaveDataFlags.Restore) == 0)
|
||||||
|
{
|
||||||
|
Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo,
|
||||||
|
extraDataGetter);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
canAccess |= accessibility.CanWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canAccess)
|
||||||
|
return ResultFs.PermissionDenied.Log();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask.TimeStamp != 0)
|
||||||
|
{
|
||||||
|
bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) ||
|
||||||
|
accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataTimeStamp);
|
||||||
|
|
||||||
|
if (!canAccess)
|
||||||
|
return ResultFs.PermissionDenied.Log();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask.CommitId != 0)
|
||||||
|
{
|
||||||
|
bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) ||
|
||||||
|
accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataCommitId);
|
||||||
|
|
||||||
|
if (!canAccess)
|
||||||
|
return ResultFs.PermissionDenied.Log();
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveDataExtraData emptyMask = default;
|
||||||
|
SaveDataExtraData maskWithoutFlags = mask;
|
||||||
|
maskWithoutFlags.Flags = SaveDataFlags.None;
|
||||||
|
maskWithoutFlags.TimeStamp = 0;
|
||||||
|
maskWithoutFlags.CommitId = 0;
|
||||||
|
|
||||||
|
// Full write access is needed for writing anything other than flags, timestamp or commit ID
|
||||||
|
if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask)
|
||||||
|
.SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutFlags)))
|
||||||
|
{
|
||||||
|
bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll);
|
||||||
|
|
||||||
|
if (!canAccess)
|
||||||
|
return ResultFs.PermissionDenied.Log();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo)
|
public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo)
|
||||||
{
|
{
|
||||||
bool canAccess;
|
bool canAccess;
|
||||||
|
@ -1199,68 +1268,297 @@ namespace LibHac.FsSrv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedParameter.Local
|
||||||
|
// Nintendo used this parameter in older FS versions, but never removed it.
|
||||||
private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
|
private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
|
||||||
ulong saveDataId, bool isTemporarySaveData)
|
ulong saveDataId, bool isTemporarySaveData)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
UnsafeHelpers.SkipParamInit(out extraData);
|
||||||
|
|
||||||
|
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard);
|
||||||
|
|
||||||
|
SaveDataIndexerAccessor accessor = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Result rc = OpenSaveDataIndexerAccessor(out accessor, spaceId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
return ServiceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type,
|
||||||
|
SaveDataRootPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
accessor?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
|
private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId,
|
||||||
ulong saveDataId, in SaveDataExtraData extraDataMask)
|
ulong saveDataId, in SaveDataExtraData extraDataMask)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
UnsafeHelpers.SkipParamInit(out extraData);
|
||||||
|
|
||||||
|
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard);
|
||||||
|
|
||||||
|
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
SaveDataSpaceId resolvedSpaceId;
|
||||||
|
SaveDataAttribute key;
|
||||||
|
|
||||||
|
if (spaceId == SaveDataSpaceId.BisAuto)
|
||||||
|
{
|
||||||
|
SaveDataIndexerAccessor accessor = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsStaticSaveDataIdValueRange(saveDataId))
|
||||||
|
{
|
||||||
|
rc = OpenSaveDataIndexerAccessor(out accessor, SaveDataSpaceId.System);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rc = OpenSaveDataIndexerAccessor(out accessor, SaveDataSpaceId.User);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
resolvedSpaceId = value.SpaceId;
|
||||||
|
|
||||||
|
rc = accessor.Indexer.GetKey(out key, saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
accessor?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SaveDataIndexerAccessor accessor = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rc = OpenSaveDataIndexerAccessor(out accessor, spaceId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
resolvedSpaceId = value.SpaceId;
|
||||||
|
|
||||||
|
rc = accessor.Indexer.GetKey(out key, saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
accessor?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ReadExtraData(out SaveDataExtraData data) => ServiceImpl.ReadSaveDataFileSystemExtraData(out data,
|
||||||
|
resolvedSpaceId, saveDataId, key.Type, new U8Span(SaveDataRootPath.Str));
|
||||||
|
|
||||||
|
rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo,
|
||||||
|
ReadExtraData);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId,
|
||||||
|
saveDataId, key.Type, new U8Span(SaveDataRootPath.Str));
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
MaskExtraData(ref tempExtraData, in extraDataMask);
|
||||||
|
extraData = tempExtraData;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result ReadSaveDataFileSystemExtraData(OutBuffer extraData, ulong saveDataId)
|
public Result ReadSaveDataFileSystemExtraData(OutBuffer extraData, ulong saveDataId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
// Make a mask for reading the entire extra data
|
||||||
|
Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
|
||||||
|
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
|
||||||
|
|
||||||
|
return ReadSaveDataFileSystemExtraDataCore(out SpanHelpers.AsStruct<SaveDataExtraData>(extraData.Buffer),
|
||||||
|
SaveDataSpaceId.BisAuto, saveDataId, in extraDataMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData,
|
public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData,
|
||||||
SaveDataSpaceId spaceId, in SaveDataAttribute attribute)
|
SaveDataSpaceId spaceId, in SaveDataAttribute attribute)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct<SaveDataExtraData>(extraData.Buffer);
|
||||||
|
|
||||||
|
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
SaveDataAttribute tempAttribute = attribute;
|
||||||
|
|
||||||
|
if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId)
|
||||||
|
{
|
||||||
|
tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
// Make a mask for reading the entire extra data
|
||||||
|
Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
|
||||||
|
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
|
||||||
|
|
||||||
|
return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in extraDataMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData,
|
public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData,
|
||||||
SaveDataSpaceId spaceId, ulong saveDataId)
|
SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct<SaveDataExtraData>(extraData.Buffer);
|
||||||
|
|
||||||
|
// Make a mask for reading the entire extra data
|
||||||
|
Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
|
||||||
|
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
|
||||||
|
|
||||||
|
return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, saveDataId, in extraDataMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraData,
|
public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraData,
|
||||||
SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer extraDataMask)
|
SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer extraDataMask)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (extraDataMask.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
ref readonly SaveDataExtraData maskRef =
|
||||||
|
ref SpanHelpers.AsReadOnlyStruct<SaveDataExtraData>(extraDataMask.Buffer);
|
||||||
|
|
||||||
|
ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct<SaveDataExtraData>(extraData.Buffer);
|
||||||
|
|
||||||
|
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
SaveDataAttribute tempAttribute = attribute;
|
||||||
|
|
||||||
|
if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId)
|
||||||
|
{
|
||||||
|
tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute,
|
rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute);
|
||||||
SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask)
|
if (rc.IsFailure()) return rc;
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in maskRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId,
|
private Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId,
|
||||||
in SaveDataExtraData extraData, SaveDataType saveType, bool updateTimeStamp)
|
in SaveDataExtraData extraData, SaveDataType saveType, bool updateTimeStamp)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard);
|
||||||
|
|
||||||
|
return ServiceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, SaveDataRootPath,
|
||||||
|
saveType, updateTimeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result WriteSaveDataFileSystemExtraDataWithMaskCore(ulong saveDataId, SaveDataSpaceId spaceId,
|
private Result WriteSaveDataFileSystemExtraDataWithMaskCore(ulong saveDataId, SaveDataSpaceId spaceId,
|
||||||
in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask)
|
in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard);
|
||||||
|
|
||||||
|
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
SaveDataIndexerAccessor accessor = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rc = OpenSaveDataIndexerAccessor(out accessor, spaceId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
Result ReadExtraData(out SaveDataExtraData data) => ServiceImpl.ReadSaveDataFileSystemExtraData(out data,
|
||||||
|
spaceId, saveDataId, key.Type, new U8Span(SaveDataRootPath.Str));
|
||||||
|
|
||||||
|
rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo,
|
||||||
|
ReadExtraData);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId,
|
||||||
|
saveDataId, key.Type, SaveDataRootPath);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask);
|
||||||
|
|
||||||
|
return ServiceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify,
|
||||||
|
SaveDataRootPath, key.Type, false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
accessor?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData)
|
public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
ref readonly SaveDataExtraData extraDataRef =
|
||||||
|
ref SpanHelpers.AsReadOnlyStruct<SaveDataExtraData>(extraData.Buffer);
|
||||||
|
|
||||||
|
var extraDataMask = new SaveDataExtraData();
|
||||||
|
extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF);
|
||||||
|
|
||||||
|
return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in extraDataMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute,
|
||||||
|
SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask)
|
||||||
|
{
|
||||||
|
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
SaveDataAttribute tempAttribute = attribute;
|
||||||
|
|
||||||
|
if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId)
|
||||||
|
{
|
||||||
|
tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
return WriteSaveDataFileSystemExtraDataWithMask(info.SaveDataId, spaceId, extraData, extraDataMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId,
|
public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId,
|
||||||
InBuffer extraData, InBuffer extraDataMask)
|
InBuffer extraData, InBuffer extraDataMask)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (extraDataMask.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
if (extraData.Size != Unsafe.SizeOf<SaveDataExtraData>())
|
||||||
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
ref readonly SaveDataExtraData maskRef =
|
||||||
|
ref SpanHelpers.AsReadOnlyStruct<SaveDataExtraData>(extraDataMask.Buffer);
|
||||||
|
|
||||||
|
ref readonly SaveDataExtraData extraDataRef =
|
||||||
|
ref SpanHelpers.AsReadOnlyStruct<SaveDataExtraData>(extraData.Buffer);
|
||||||
|
|
||||||
|
return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in maskRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
|
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
|
||||||
|
@ -1462,7 +1760,21 @@ namespace LibHac.FsSrv
|
||||||
|
|
||||||
public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId)
|
public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
UnsafeHelpers.SkipParamInit(out commitId);
|
||||||
|
|
||||||
|
Result rc = GetProgramInfo(out ProgramInfo programInfo);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
if (!programInfo.AccessControl.CanCall(OperationType.GetSaveDataCommitId))
|
||||||
|
return ResultFs.PermissionDenied.Log();
|
||||||
|
|
||||||
|
Unsafe.SkipInit(out SaveDataExtraData extraData);
|
||||||
|
rc = ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer.FromStruct(ref extraData), spaceId,
|
||||||
|
saveDataId);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
commitId = Impl.Utility.ConvertZeroCommitId(in extraData);
|
||||||
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result OpenSaveDataInfoReaderOnlyCacheStorage(
|
public Result OpenSaveDataInfoReaderOnlyCacheStorage(
|
||||||
|
@ -1700,9 +2012,9 @@ namespace LibHac.FsSrv
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProgramId ResolveDefaultSaveDataReferenceProgramId(in ProgramId programId)
|
private ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId)
|
||||||
{
|
{
|
||||||
return ServiceImpl.ResolveDefaultSaveDataReferenceProgramId(in programId);
|
return ServiceImpl.ResolveDefaultSaveDataReferenceProgramId(programId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId,
|
public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId,
|
||||||
|
@ -1995,6 +2307,31 @@ namespace LibHac.FsSrv
|
||||||
return (long)id < 0;
|
return (long)id < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ModifySaveDataExtraData(ref SaveDataExtraData currentExtraData, in SaveDataExtraData extraData,
|
||||||
|
in SaveDataExtraData extraDataMask)
|
||||||
|
{
|
||||||
|
Span<byte> currentExtraDataBytes = SpanHelpers.AsByteSpan(ref currentExtraData);
|
||||||
|
ReadOnlySpan<byte> extraDataBytes = SpanHelpers.AsReadOnlyByteSpan(in extraData);
|
||||||
|
ReadOnlySpan<byte> extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask);
|
||||||
|
|
||||||
|
for (int i = 0; i < Unsafe.SizeOf<SaveDataExtraData>(); i++)
|
||||||
|
{
|
||||||
|
currentExtraDataBytes[i] = (byte)(extraDataBytes[i] & extraDataMaskBytes[i] |
|
||||||
|
currentExtraDataBytes[i] & ~extraDataMaskBytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MaskExtraData(ref SaveDataExtraData extraData, in SaveDataExtraData extraDataMask)
|
||||||
|
{
|
||||||
|
Span<byte> extraDataBytes = SpanHelpers.AsByteSpan(ref extraData);
|
||||||
|
ReadOnlySpan<byte> extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask);
|
||||||
|
|
||||||
|
for (int i = 0; i < Unsafe.SizeOf<SaveDataExtraData>(); i++)
|
||||||
|
{
|
||||||
|
extraDataBytes[i] &= extraDataMaskBytes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private StorageType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId)
|
private StorageType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId)
|
||||||
{
|
{
|
||||||
if (type == SaveDataType.Cache || type == SaveDataType.Bcat)
|
if (type == SaveDataType.Cache || type == SaveDataType.Bcat)
|
||||||
|
|
|
@ -774,7 +774,7 @@ namespace LibHac.FsSrv
|
||||||
/// for their save data. The main program always has a program index of 0.</remarks>
|
/// for their save data. The main program always has a program index of 0.</remarks>
|
||||||
/// <param name="programId">The program ID to get the save data program ID for.</param>
|
/// <param name="programId">The program ID to get the save data program ID for.</param>
|
||||||
/// <returns>The program ID of the save data.</returns>
|
/// <returns>The program ID of the save data.</returns>
|
||||||
public ProgramId ResolveDefaultSaveDataReferenceProgramId(in ProgramId programId)
|
public ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId)
|
||||||
{
|
{
|
||||||
// First check if there's an entry in the program index map with the program ID and program index 0
|
// First check if there's an entry in the program index map with the program ID and program index 0
|
||||||
ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, 0);
|
ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, 0);
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
|
||||||
Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/cal/file".ToU8Span()));
|
Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/cal/file".ToU8Span()));
|
||||||
Assert.Equal(DirectoryEntryType.File, type);
|
Assert.Equal(DirectoryEntryType.File, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MountBis_MountSafePartition_OpensCorrectDirectory()
|
public void MountBis_MountSafePartition_OpensCorrectDirectory()
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
|
using LibHac.Time;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
|
namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
|
||||||
|
@ -159,6 +160,90 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
|
||||||
Assert.Equal(userId, info[0].UserId);
|
Assert.Equal(userId, info[0].UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateSaveData_DoesNotExist_HasCorrectOwnerId()
|
||||||
|
{
|
||||||
|
uint ownerId = 1;
|
||||||
|
|
||||||
|
var applicationId = new Ncm.ApplicationId(ownerId);
|
||||||
|
var userId = new UserId(5, 4);
|
||||||
|
|
||||||
|
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
|
||||||
|
|
||||||
|
// Create the save
|
||||||
|
Assert.Success(fs.CreateSaveData(applicationId, userId, ownerId, 0x1000, 0x1000, SaveDataFlags.None));
|
||||||
|
|
||||||
|
// Get the created save data's ID
|
||||||
|
Assert.Success(fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User));
|
||||||
|
|
||||||
|
var info = new SaveDataInfo[2];
|
||||||
|
iterator.ReadSaveDataInfo(out long entriesRead, info);
|
||||||
|
|
||||||
|
Assert.Equal(1, entriesRead);
|
||||||
|
|
||||||
|
// Get the created save data's owner ID
|
||||||
|
Assert.Success(fs.GetSaveDataOwnerId(out ulong actualOwnerId, info[0].SaveDataId));
|
||||||
|
|
||||||
|
Assert.Equal(ownerId, actualOwnerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateSaveData_DoesNotExist_HasCorrectFlags()
|
||||||
|
{
|
||||||
|
SaveDataFlags flags = SaveDataFlags.KeepAfterRefurbishment | SaveDataFlags.NeedsSecureDelete;
|
||||||
|
|
||||||
|
var applicationId = new Ncm.ApplicationId(1);
|
||||||
|
var userId = new UserId(5, 4);
|
||||||
|
|
||||||
|
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
|
||||||
|
|
||||||
|
// Create the save
|
||||||
|
Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, flags));
|
||||||
|
|
||||||
|
// Get the created save data's ID
|
||||||
|
Assert.Success(fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User));
|
||||||
|
|
||||||
|
var info = new SaveDataInfo[2];
|
||||||
|
iterator.ReadSaveDataInfo(out long entriesRead, info);
|
||||||
|
|
||||||
|
Assert.Equal(1, entriesRead);
|
||||||
|
|
||||||
|
// Get the created save data's flags
|
||||||
|
Assert.Success(fs.GetSaveDataFlags(out SaveDataFlags actualFlags, info[0].SaveDataId));
|
||||||
|
|
||||||
|
Assert.Equal(flags, actualFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateSaveData_DoesNotExist_HasCorrectSizes()
|
||||||
|
{
|
||||||
|
long availableSize = 0x220000;
|
||||||
|
long journalSize = 0x120000;
|
||||||
|
|
||||||
|
var applicationId = new Ncm.ApplicationId(1);
|
||||||
|
var userId = new UserId(5, 4);
|
||||||
|
|
||||||
|
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
|
||||||
|
|
||||||
|
// Create the save
|
||||||
|
Assert.Success(fs.CreateSaveData(applicationId, userId, 0, availableSize, journalSize, SaveDataFlags.None));
|
||||||
|
|
||||||
|
// Get the created save data's ID
|
||||||
|
Assert.Success(fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User));
|
||||||
|
|
||||||
|
var info = new SaveDataInfo[2];
|
||||||
|
iterator.ReadSaveDataInfo(out long entriesRead, info);
|
||||||
|
|
||||||
|
Assert.Equal(1, entriesRead);
|
||||||
|
|
||||||
|
// Get the created save data's sizes
|
||||||
|
Assert.Success(fs.GetSaveDataAvailableSize(out long actualAvailableSize, info[0].SaveDataId));
|
||||||
|
Assert.Success(fs.GetSaveDataJournalSize(out long actualJournalSize, info[0].SaveDataId));
|
||||||
|
|
||||||
|
Assert.Equal(availableSize, actualAvailableSize);
|
||||||
|
Assert.Equal(journalSize, actualJournalSize);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DeleteSaveData_DoesNotExist_ReturnsTargetNotFound()
|
public void DeleteSaveData_DoesNotExist_ReturnsTargetNotFound()
|
||||||
{
|
{
|
||||||
|
@ -287,6 +372,64 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSaveDataCommitId_AfterSetSaveDataCommitIdIsCalled_ReturnsSetCommitId()
|
||||||
|
{
|
||||||
|
long commitId = 46506854;
|
||||||
|
|
||||||
|
var applicationId = new Ncm.ApplicationId(1);
|
||||||
|
var userId = new UserId(5, 4);
|
||||||
|
|
||||||
|
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
|
||||||
|
|
||||||
|
// Create the save
|
||||||
|
Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None));
|
||||||
|
|
||||||
|
// Get the created save data's ID
|
||||||
|
Assert.Success(fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User));
|
||||||
|
|
||||||
|
var info = new SaveDataInfo[2];
|
||||||
|
iterator.ReadSaveDataInfo(out long entriesRead, info);
|
||||||
|
|
||||||
|
Assert.Equal(1, entriesRead);
|
||||||
|
|
||||||
|
// Set the new commit ID
|
||||||
|
Assert.Success(fs.SetSaveDataCommitId(info[0].SpaceId, info[0].SaveDataId, commitId));
|
||||||
|
|
||||||
|
Assert.Success(fs.GetSaveDataCommitId(out long actualCommitId, info[0].SpaceId, info[0].SaveDataId));
|
||||||
|
|
||||||
|
Assert.Equal(commitId, actualCommitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSaveDataTimeStamp_AfterSetSaveDataTimeStampIsCalled_ReturnsSetTimeStamp()
|
||||||
|
{
|
||||||
|
var timeStamp = new PosixTime(12345678);
|
||||||
|
|
||||||
|
var applicationId = new Ncm.ApplicationId(1);
|
||||||
|
var userId = new UserId(5, 4);
|
||||||
|
|
||||||
|
FileSystemClient fs = FileSystemServerFactory.CreateClient(true);
|
||||||
|
|
||||||
|
// Create the save
|
||||||
|
Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None));
|
||||||
|
|
||||||
|
// Get the created save data's ID
|
||||||
|
Assert.Success(fs.OpenSaveDataIterator(out SaveDataIterator iterator, SaveDataSpaceId.User));
|
||||||
|
|
||||||
|
var info = new SaveDataInfo[2];
|
||||||
|
iterator.ReadSaveDataInfo(out long entriesRead, info);
|
||||||
|
|
||||||
|
Assert.Equal(1, entriesRead);
|
||||||
|
|
||||||
|
// Set the new timestamp
|
||||||
|
Assert.Success(fs.SetSaveDataTimeStamp(info[0].SpaceId, info[0].SaveDataId, timeStamp));
|
||||||
|
|
||||||
|
Assert.Success(fs.GetSaveDataTimeStamp(out PosixTime actualTimeStamp, info[0].SpaceId, info[0].SaveDataId));
|
||||||
|
|
||||||
|
Assert.Equal(timeStamp, actualTimeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
Loading…
Reference in a new issue