Implement the rest of SaveDataFileSystemServiceImpl and update it to FS 17

This commit is contained in:
Alex Barney 2024-02-18 19:45:15 -07:00
parent 257cf57d0b
commit 164382f998
7 changed files with 571 additions and 100 deletions

View file

@ -2,13 +2,15 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
namespace LibHac.FsSrv.FsCreator;
public interface ISaveDataFileSystemCreator
{
Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode);
Result CreateRaw(ref SharedRef<IFile> outFile, in SharedRef<IFileSystem> fileSystem, ulong saveDataId, OpenMode openMode);
Result Create(ref SharedRef<ISaveDataFileSystem> outFileSystem, ref SharedRef<IFileSystem> baseFileSystem,
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool isDeviceUniqueMac,
@ -16,10 +18,37 @@ public interface ISaveDataFileSystemCreator
ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible);
Result CreateExtraDataAccessor(ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
ref SharedRef<IFileSystem> baseFileSystem);
in SharedRef<IStorage> baseStorage, bool isDeviceUniqueMac, bool isIntegritySaveData, bool isReconstructible);
Result IsDataEncrypted(out bool isEncrypted, ref SharedRef<IFileSystem> baseFileSystem, ulong saveDataId,
IBufferManager bufferManager, bool isDeviceUniqueMac, bool isReconstructible);
Result CreateInternalStorage(ref SharedRef<IFileSystem> outFileSystem, in SharedRef<IFileSystem> baseFileSystem,
SaveDataSpaceId spaceId, ulong saveDataId, bool isDeviceUniqueMac, bool useUniqueKey1,
ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible);
Result RecoverMasterHeader(in SharedRef<IFileSystem> baseFileSystem, ulong saveDataId, IBufferManager bufferManager,
bool isDeviceUniqueMac, bool isReconstructible);
Result UpdateMac(in SharedRef<IFileSystem> baseFileSystem, ulong saveDataId, bool isDeviceUniqueMac,
bool isReconstructible);
Result Format(in ValueSubStorage saveImageStorage, long blockSize, int countExpandMax, uint blockCount,
uint journalBlockCount, IBufferManager bufferManager, bool isDeviceUniqueMac, in HashSalt hashSalt,
RandomDataGenerator encryptionKeyGenerator, bool isReconstructible, uint version);
Result FormatAsIntegritySaveData(in ValueSubStorage saveImageStorage, long blockSize, uint blockCount,
IBufferManager bufferManager, bool isDeviceUniqueMac, RandomDataGenerator encryptionKeyGenerator,
bool isReconstructible, uint version);
Result ExtractSaveDataParameters(out JournalIntegritySaveDataParameters outParams, IStorage saveFileStorage,
bool isDeviceUniqueMac, bool isReconstructible);
Result ExtendSaveData(SaveDataExtender extender, in ValueSubStorage baseStorage, in ValueSubStorage logStorage,
bool isDeviceUniqueMac, bool isReconstructible);
void SetMacGenerationSeed(ReadOnlySpan<byte> seed);
Result IsProvisionallyCommittedSaveData(out bool outIsProvisionallyCommitted,
in SharedRef<IFileSystem> baseFileSystem, in SaveDataInfo info, bool isDeviceUniqueMac,
ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible);
IMacGenerator GetMacGenerator(bool isDeviceUniqueMac, bool isTemporaryTransferSave);
}

View file

@ -5,7 +5,9 @@ using LibHac.Common.FixedArrays;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Util;
using OpenType = LibHac.FsSrv.SaveDataOpenTypeSetFileStorage.OpenType;
@ -34,7 +36,38 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
_keySet = keySet;
}
public Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode)
public Result Format(in ValueSubStorage saveImageStorage, long blockSize, int countExpandMax, uint blockCount,
uint journalBlockCount, IBufferManager bufferManager, bool isDeviceUniqueMac, in HashSalt hashSalt,
RandomDataGenerator encryptionKeyGenerator, bool isReconstructible, uint version)
{
throw new NotImplementedException();
}
public Result FormatAsIntegritySaveData(in ValueSubStorage saveImageStorage, long blockSize, uint blockCount,
IBufferManager bufferManager, bool isDeviceUniqueMac, RandomDataGenerator encryptionKeyGenerator,
bool isReconstructible, uint version)
{
throw new NotImplementedException();
}
public Result ExtractSaveDataParameters(out JournalIntegritySaveDataParameters outParams, IStorage saveFileStorage,
bool isDeviceUniqueMac, bool isReconstructible)
{
throw new NotImplementedException();
}
public Result ExtendSaveData(SaveDataExtender extender, in ValueSubStorage baseStorage,
in ValueSubStorage logStorage, bool isDeviceUniqueMac, bool isReconstructible)
{
throw new NotImplementedException();
}
public void SetMacGenerationSeed(ReadOnlySpan<byte> seed)
{
throw new NotImplementedException();
}
public Result CreateRaw(ref SharedRef<IFile> outFile, in SharedRef<IFileSystem> fileSystem, ulong saveDataId, OpenMode openMode)
{
throw new NotImplementedException();
}
@ -114,18 +147,38 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
}
public Result CreateExtraDataAccessor(ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
ref SharedRef<IFileSystem> baseFileSystem)
in SharedRef<IStorage> baseStorage, bool isDeviceUniqueMac, bool isIntegritySaveData, bool isReconstructible)
{
throw new NotImplementedException();
}
public Result IsDataEncrypted(out bool isEncrypted, ref SharedRef<IFileSystem> baseFileSystem, ulong saveDataId,
public Result CreateInternalStorage(ref SharedRef<IFileSystem> outFileSystem,
in SharedRef<IFileSystem> baseFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool isDeviceUniqueMac,
bool useUniqueKey1, ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible)
{
throw new NotImplementedException();
}
public Result RecoverMasterHeader(in SharedRef<IFileSystem> baseFileSystem, ulong saveDataId,
IBufferManager bufferManager, bool isDeviceUniqueMac, bool isReconstructible)
{
throw new NotImplementedException();
}
public void SetMacGenerationSeed(ReadOnlySpan<byte> seed)
public Result UpdateMac(in SharedRef<IFileSystem> baseFileSystem, ulong saveDataId, bool isDeviceUniqueMac,
bool isReconstructible)
{
throw new NotImplementedException();
}
public Result IsProvisionallyCommittedSaveData(out bool outIsProvisionallyCommitted,
in SharedRef<IFileSystem> baseFileSystem, in SaveDataInfo info, bool isDeviceUniqueMac,
ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible)
{
throw new NotImplementedException();
}
public IMacGenerator GetMacGenerator(bool isDeviceUniqueMac, bool isTemporaryTransferSave)
{
throw new NotImplementedException();
}

View file

@ -67,7 +67,9 @@ public class SaveDataPorterManager
public SaveDataPorterManager()
{
throw new NotImplementedException();
_prohibiteeList = new LinkedList<Prohibitee>();
_porterProhibiterList = new LinkedList<SaveDataPorterProhibiter>();
_mutex = new SdkMutex();
}
public bool IsProhibited(ref UniqueLock<SdkMutex> refLock, ApplicationId applicationId)

View file

@ -2,13 +2,14 @@
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
using LibHac.Os;
using LibHac.Util;
@ -28,14 +29,60 @@ file static class Anonymous
spaceId == SaveDataSpaceId.SafeMode;
}
public static Result WipeData(IFileSystem fileSystem, ref readonly Path filePath, RandomDataGenerator random)
public static Result WipeData(IFileSystem fileSystem, ref readonly Path filePath, RandomDataGenerator generateKey)
{
throw new NotImplementedException();
}
const int workBufferSize = 0x400000;
const int minimumWorkBufferSize = 0x1000;
public static Result WipeMasterHeader(IFileSystem fileSystem, ref readonly Path filePath, RandomDataGenerator random)
{
throw new NotImplementedException();
Result lastResult = Result.Success;
using (var file = new UniqueRef<IFile>())
{
Result res = fileSystem.OpenFile(ref file.Ref, in filePath, OpenMode.ReadWrite);
if (res.IsFailure()) return res.Miss();
try
{
res = file.Get.GetSize(out long remainingSize);
if (res.IsFailure()) return res.Miss();
Span<byte> key = stackalloc byte[Aes.KeySize128];
Span<byte> counter = stackalloc byte[Aes.BlockSize];
generateKey(key);
using var workBuffer = new PooledBuffer();
workBuffer.AllocateParticularlyLarge(workBufferSize, minimumWorkBufferSize);
Span<byte> buffer = workBuffer.GetBuffer();
buffer.Clear();
counter.Clear();
long offset = 0;
while (remainingSize > 0)
{
int writeSize = (int)Math.Min(remainingSize, buffer.Length);
int encryptSize = Alignment.AlignUp(writeSize, Aes.BlockSize);
Aes.EncryptCtr128(buffer.Slice(0, encryptSize), buffer.Slice(0, writeSize), key, counter);
Result result = file.Get.Write(offset, buffer.Slice(0, writeSize), WriteOption.None);
if (lastResult.IsSuccess() && result.IsFailure())
lastResult = result;
FsSystem.Utility.AddCounter(counter, (ulong)BitUtil.DivideUp(writeSize, Aes.BlockSize));
offset += writeSize;
remainingSize -= writeSize;
}
}
finally
{
file.Get.Flush().IgnoreResult();
}
}
if (lastResult.IsSuccess()) return lastResult.Miss();
return Result.Success;
}
}
@ -43,7 +90,7 @@ file static class Anonymous
/// Handles the lower-level operations on save data.
/// <see cref="SaveDataFileSystemService"/> uses this class to provide save data APIs at a higher level of abstraction.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class SaveDataFileSystemServiceImpl : IDisposable
{
private static readonly bool UseTargetManager = true;
@ -110,11 +157,19 @@ public class SaveDataFileSystemServiceImpl : IDisposable
_config = configuration;
_saveFileSystemCacheManager = new SaveDataFileSystemCacheManager();
_saveExtraDataCacheManager = new SaveDataExtraDataAccessorCacheManager();
_saveDataPorterManager = new SaveDataPorterManager();
_isSdCardAccessible = false;
_integritySaveDataVersion = new Optional<uint>();
_journalIntegritySaveDataVersion = new Optional<uint>();
_timeStampGetter = new TimeStampGetter(this);
Result res = _saveFileSystemCacheManager.Initialize(_config.SaveDataFileSystemCacheCount);
Abort.DoAbortUnless(res.IsSuccess());
IntegritySaveDataFileSystem.SetVersionSupported(_config.FsServer, _config.IntegritySupportedVersionMin, _config.IntegritySupportedVersionMax);
JournalIntegritySaveDataFileSystem.SetVersionSupported(_config.FsServer, _config.JournalIntegritySupportedVersionMin, _config.JournalIntegritySupportedVersionMax);
}
public void Dispose()
@ -159,7 +214,6 @@ public class SaveDataFileSystemServiceImpl : IDisposable
UnsafeHelpers.SkipParamInit(out exists);
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
@ -190,20 +244,31 @@ public class SaveDataFileSystemServiceImpl : IDisposable
private Result GetSaveDataCommitTimeStamp(out long timeStamp)
{
return _config.TimeService.GetCurrentPosixTime(out timeStamp);
return _config.TimeService.GetCurrentPosixTime(out timeStamp).Ret();
}
public Result OpenSaveDataFile(ref SharedRef<IFile> outFile, SaveDataSpaceId spaceId, ulong saveDataId,
OpenMode openMode)
{
throw new NotImplementedException();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
using var saveDataFile = new SharedRef<IFile>();
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
res = _config.SaveFsCreator.CreateRaw(ref saveDataFile.Ref, in fileSystem, saveDataId, openMode);
if (res.IsFailure()) return res.Miss();
outFile.SetByMove(ref saveDataFile.Ref);
return Result.Success;
}
public Result OpenSaveDataFileSystem(ref SharedRef<IFileSystem> outFileSystem, SaveDataSpaceId spaceId,
ulong saveDataId, ref readonly Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData)
{
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, true);
if (res.IsFailure()) return res.Miss();
@ -288,46 +353,243 @@ public class SaveDataFileSystemServiceImpl : IDisposable
SaveDataSpaceId spaceId, ulong saveDataId, ref readonly Path saveDataRootPath, bool useSecondMacKey,
bool isReconstructible)
{
throw new NotImplementedException();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, allowEmulatedSave: true);
if (res.IsFailure()) return res.Miss();
using UniqueLockRef<SdkRecursiveMutexType> scopedLockFsCache = _saveFileSystemCacheManager.GetScopedLock();
using UniqueLockRef<SdkRecursiveMutexType> scopedLockExtraDataCache = _saveExtraDataCacheManager.GetScopedLock();
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
using var internalStorage = new SharedRef<IFileSystem>();
res = _config.SaveFsCreator.CreateInternalStorage(ref internalStorage.Ref, in fileSystem, spaceId, saveDataId,
IsDeviceUniqueMac(spaceId), useSecondMacKey, _timeStampGetter, isReconstructible);
if (res.IsFailure()) return res.Miss();
outFileSystem.SetByMove(ref internalStorage.Ref);
return Result.Success;
}
private Result OpenSaveDataImageFile(ref UniqueRef<IFile> outFile, SaveDataSpaceId spaceId, ulong saveDataId,
ref readonly Path saveDataRootPath)
{
throw new NotImplementedException();
Unsafe.SkipInit(out Array18<byte> saveImageNameBuffer);
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, allowEmulatedSave: true);
if (res.IsFailure()) return res.Miss();
using scoped var saveImageName = new Path();
res = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId);
if (res.IsFailure()) return res.Miss();
res = fileSystem.Get.GetEntryType(out DirectoryEntryType type, in saveImageName);
if (res.IsFailure())
{
if (ResultFs.PathNotFound.Includes(res))
return ResultFs.TargetNotFound.LogConverted(res);
return res.Miss();
}
if (type == DirectoryEntryType.File)
{
res = fileSystem.Get.OpenFile(ref outFile.Ref, in saveImageName, OpenMode.ReadWrite);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
return ResultFs.IncompatiblePath.Log();
}
private Result OpenSaveDataExtensionContextFile(ref UniqueRef<IFile> outFile, IFile saveDataFile, ulong saveDataId,
SaveDataSpaceId spaceId, long dataSize, long journalSize, bool createIfMissing, bool isReconstructible)
{
throw new NotImplementedException();
Result result = OpenSaveDataMeta(ref outFile.Ref, saveDataId, spaceId, SaveDataMetaType.ExtensionContext);
if (result.IsFailure())
{
if (ResultFs.PathNotFound.Includes(result))
{
// Create the extension context file if it's missing and we were asked to create it
if (!createIfMissing)
return result.Rethrow();
using var saveDataStorage = new SharedRef<IStorage>(new FileStorage(saveDataFile));
// Check that the new save data sizes aren't smaller than the current sizes
using var extraDataAccessor = new SharedRef<ISaveDataExtraDataAccessor>();
Result res = _config.SaveFsCreator.CreateExtraDataAccessor(ref extraDataAccessor.Ref,
in saveDataStorage, IsDeviceUniqueMac(spaceId), isIntegritySaveData: true, isReconstructible);
if (res.IsFailure()) return res.Miss();
res = extraDataAccessor.Get.ReadExtraData(out SaveDataExtraData extraData);
if (res.IsFailure()) return res.Miss();
if (dataSize < extraData.DataSize || journalSize < extraData.JournalSize)
return ResultFs.InvalidSize.Log();
// Calculate the size needed for the context file, and create it
var extender = new SaveDataExtender();
res = _config.SaveFsCreator.ExtractSaveDataParameters(out JournalIntegritySaveDataParameters parameters,
saveDataStorage.Get, IsDeviceUniqueMac(spaceId), isReconstructible);
if (res.IsFailure()) return res.Miss();
res = extender.InitializeContext(in parameters, dataSize, journalSize);
if (res.IsFailure()) return res.Miss();
long contextFileSize = SaveDataExtender.QueryContextSize() + extender.GetLogSize();
res = CreateSaveDataMeta(saveDataId, spaceId, SaveDataMetaType.ExtensionContext, contextFileSize);
if (res.IsFailure()) return res.Miss();
// Once the context file is created, write the initial extension context to it
res = OpenSaveDataMeta(ref outFile.Ref, saveDataId, spaceId, SaveDataMetaType.ExtensionContext);
if (res.IsFailure()) return res.Miss();
using var contextStorage = new FileStorage(outFile.Get);
extender.WriteContext(contextStorage).IgnoreResult();
}
else
{
return result.Miss();
}
}
return Result.Success;
}
public Result StartExtendSaveDataFileSystem(out long extendedTotalSize, ulong saveDataId, SaveDataSpaceId spaceId,
SaveDataType type, long dataSize, long journalSize, ref readonly Path saveDataRootPath)
{
return ExtendSaveDataFileSystemCore(out extendedTotalSize, saveDataId, spaceId, type, dataSize, journalSize,
in saveDataRootPath, isExtensionStart: true);
in saveDataRootPath, isExtensionStart: true).Ret();
}
public Result ResumeExtendSaveDataFileSystem(out long extendedTotalSize, ulong saveDataId, SaveDataSpaceId spaceId,
SaveDataType type, ref readonly Path saveDataRootPath)
{
return ExtendSaveDataFileSystemCore(out extendedTotalSize, saveDataId, spaceId, type, dataSize: 0,
journalSize: 0, in saveDataRootPath, isExtensionStart: false);
journalSize: 0, in saveDataRootPath, isExtensionStart: false).Ret();
}
private Result ExtendSaveDataFileSystemCore(out long extendedTotalSize, ulong saveDataId, SaveDataSpaceId spaceId,
SaveDataType type, long dataSize, long journalSize, ref readonly Path saveDataRootPath, bool isExtensionStart)
{
throw new NotImplementedException();
UnsafeHelpers.SkipParamInit(out extendedTotalSize);
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
using var saveDataFile = new UniqueRef<IFile>();
using var contextFile = new UniqueRef<IFile>();
Result res;
Result result = OpenSaveDataImageFile(ref saveDataFile.Ref, spaceId, saveDataId, in saveDataRootPath);
if (result.IsFailure())
{
// OpenSaveDataImageFile returns ResultIncompatiblePath when the target is a directory
if (ResultFs.IncompatiblePath.Includes(result))
{
// All we need to do for directory save data is update the sizes in its extra data
res = ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, saveDataId, type, in saveDataRootPath);
if (res.IsFailure()) return res.Miss();
extraData.DataSize = dataSize;
extraData.JournalSize = journalSize;
res = WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath, type, updateTimeStamp: true);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
return result.Miss();
}
res = OpenSaveDataExtensionContextFile(ref contextFile.Ref, saveDataFile.Get, saveDataId, spaceId, dataSize,
journalSize, isExtensionStart, SaveDataProperties.IsReconstructible(type, spaceId));
if (res.IsFailure()) return res.Miss();
using var saveDataStorage = new SharedRef<IStorage>(new FileStorage(saveDataFile.Get));
var contextStorage = new FileStorage(contextFile.Get);
var extender = new SaveDataExtender();
res = extender.ReadContext(contextStorage);
if (res.IsFailure()) return res.Miss();
if (isExtensionStart)
{
if (extender.GetAvailableSize() != dataSize)
return ResultFs.DifferentSaveDataExtensionContextParameter.Log();
if (extender.GetJournalSize() != journalSize)
return ResultFs.DifferentSaveDataExtensionContextParameter.Log();
}
extendedTotalSize = extender.GetExtendedSaveDataSize();
res = saveDataStorage.Get.GetSize(out long currentSize);
if (res.IsFailure()) return res.Miss();
if (currentSize < extendedTotalSize)
{
res = saveDataStorage.Get.SetSize(extendedTotalSize);
if (res.IsFailure()) return res.Miss();
}
using var saveDataSubStorage = new ValueSubStorage(saveDataStorage.Get, 0, extendedTotalSize);
using var logSubStorage = new ValueSubStorage(contextStorage, SaveDataExtender.QueryContextSize(), extender.GetLogSize());
res = _config.SaveFsCreator.ExtendSaveData(extender, in saveDataSubStorage, in logSubStorage,
IsDeviceUniqueMac(spaceId), SaveDataProperties.IsReconstructible(type, spaceId));
if (res.IsFailure()) return res.Miss();
using (var extraDataAccessor = new SharedRef<ISaveDataExtraDataAccessor>())
{
res = _config.SaveFsCreator.CreateExtraDataAccessor(ref extraDataAccessor.Ref, in saveDataStorage,
IsDeviceUniqueMac(spaceId), isIntegritySaveData: true, SaveDataProperties.IsReconstructible(type, spaceId));
if (res.IsFailure()) return res.Miss();
res = extraDataAccessor.Get.ReadExtraData(out SaveDataExtraData extraData);
if (res.IsFailure()) return res.Miss();
extraData.DataSize = extender.GetAvailableSize();
extraData.JournalSize = extender.GetJournalSize();
_config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId));
if (GetSaveDataCommitTimeStamp(out long timeStamp).IsSuccess())
extraData.TimeStamp = timeStamp;
res = extraDataAccessor.Get.WriteExtraData(in extraData);
if (res.IsFailure()) return res.Miss();
res = extraDataAccessor.Get.CommitExtraData(updateTimeStamp: true);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
}
public Result FinishExtendSaveDataFileSystem(ulong saveDataId, SaveDataSpaceId spaceId)
{
Result res = DeleteSaveDataMeta(saveDataId, spaceId, SaveDataMetaType.ExtensionContext);
if (res.IsFailure() && !ResultFs.PathNotFound.Includes(res))
return res.Miss();
if (res.IsFailure())
{
if (ResultFs.PathNotFound.Includes(res))
{
res.Catch().Handle();
}
else
{
return res.Miss();
}
}
return Result.Success;
}
@ -350,7 +612,6 @@ public class SaveDataFileSystemServiceImpl : IDisposable
long metaFileSize)
{
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
@ -370,7 +631,6 @@ public class SaveDataFileSystemServiceImpl : IDisposable
public Result DeleteSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType)
{
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
@ -403,8 +663,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
if (res.IsFailure()) return res.Miss();
using scoped var saveDataIdDirectoryName = new Path();
PathFunctions.SetUpFixedPathSaveId(ref saveDataIdDirectoryName.Ref(), saveDataIdDirectoryNameBuffer,
saveDataId);
PathFunctions.SetUpFixedPathSaveId(ref saveDataIdDirectoryName.Ref(), saveDataIdDirectoryNameBuffer, saveDataId);
if (res.IsFailure()) return res.Miss();
// Delete the save data's meta directory, ignoring the error if the directory is already gone
@ -413,7 +672,9 @@ public class SaveDataFileSystemServiceImpl : IDisposable
if (res.IsFailure())
{
if (ResultFs.PathNotFound.Includes(res))
return Result.Success;
{
res.Catch().Handle();
}
return res.Miss();
}
@ -425,7 +686,6 @@ public class SaveDataFileSystemServiceImpl : IDisposable
SaveDataMetaType metaType)
{
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
@ -444,8 +704,14 @@ public class SaveDataFileSystemServiceImpl : IDisposable
public Result QuerySaveDataTotalSize(out long totalSize, long blockSize, long dataSize, long journalSize)
{
// Todo: Implement
totalSize = 0;
JournalIntegritySaveDataParameters saveParams =
JournalIntegritySaveDataFileSystemDriver.SetUpSaveDataParameters(blockSize, dataSize, journalSize);
Result res = JournalIntegritySaveDataFileSystemDriver.QueryTotalSize(out totalSize, saveParams.BlockSize,
saveParams.CountDataBlock, saveParams.CountJournalBlock, saveParams.CountExpandMax,
GetJournalIntegritySaveDataVersion());
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
@ -461,16 +727,16 @@ public class SaveDataFileSystemServiceImpl : IDisposable
SaveDataFlags flags = creationInfo.Flags;
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, creationInfo.SpaceId, saveDataId,
in saveDataRootPath, allowEmulatedSave: false);
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, allowEmulatedSave: false);
if (res.IsFailure()) return res.Miss();
using scoped var saveImageName = new Path();
res = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId);
if (res.IsFailure()) return res.Miss();
// Check if we need to create a pseudo save data, i.e. directory save data
bool isPseudoSaveFs = _config.IsPseudoSaveData();
bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(creationInfo.FormatType);
bool isCreationSuccessful = false;
try
@ -482,7 +748,84 @@ public class SaveDataFileSystemServiceImpl : IDisposable
}
else
{
throw new NotImplementedException();
long totalSize;
const long blockSize = 0x4000;
var integrityParams = new Optional<IntegritySaveDataParameters>();
var journalIntegrityParams = new Optional<JournalIntegritySaveDataParameters>();
if (!isJournalingSupported)
{
IntegritySaveDataParameters param = IntegritySaveDataFileSystemDriver.SetUpSaveDataParameters(blockSize, dataSize);
res = IntegritySaveDataFileSystemDriver.QueryTotalSize(out totalSize, param.BlockSize,
param.BlockCount, GetIntegritySaveDataVersion());
if (res.IsFailure()) return res.Miss();
integrityParams.Set(in param);
}
else
{
JournalIntegritySaveDataParameters param =
JournalIntegritySaveDataFileSystemDriver.SetUpSaveDataParameters(blockSize, dataSize, journalSize);
res = JournalIntegritySaveDataFileSystemDriver.QueryTotalSize(out totalSize, param.BlockSize,
param.CountDataBlock, param.CountJournalBlock, param.CountExpandMax,
GetJournalIntegritySaveDataVersion());
if (res.IsFailure()) return res.Miss();
journalIntegrityParams.Set(in param);
}
res = fileSystem.Get.CreateFile(in saveImageName, totalSize);
if (res.IsFailure()) return res.Miss();
if (skipFormat)
return Result.Success;
using var fileStorage = new SharedRef<FileStorageBasedFileSystem>(new FileStorageBasedFileSystem());
if (!fileStorage.HasValue)
return ResultFs.AllocationMemoryFailedInSaveDataFileSystemServiceImplA.Log();
res = fileStorage.Get.Initialize(ref fileSystem.Ref, in saveImageName, OpenMode.ReadWrite);
if (res.IsFailure()) return res.Miss();
if (!isJournalingSupported)
{
Assert.SdkAssert(integrityParams.HasValue);
IntegritySaveDataParameters param = integrityParams.ValueRo;
using var subStorage = new ValueSubStorage(fileStorage.Get, 0, totalSize);
res = _config.SaveFsCreator.FormatAsIntegritySaveData(in subStorage, param.BlockSize,
param.BlockCount, _config.BufferManager, IsDeviceUniqueMac(spaceId), _config.GenerateRandomData,
SaveDataProperties.IsReconstructible(creationInfo.Attribute.Type, spaceId),
GetIntegritySaveDataVersion());
if (res.IsFailure()) return res.Miss();
}
else
{
var hashSaltRandom = new Optional<HashSalt>();
if (!creationInfo.IsHashSaltEnabled)
{
hashSaltRandom.Set();
_config.GenerateRandomData(SpanHelpers.AsByteSpan(ref hashSaltRandom.Value));
}
Assert.SdkAssert(journalIntegrityParams.HasValue);
JournalIntegritySaveDataParameters param = journalIntegrityParams.ValueRo;
using var subStorage = new ValueSubStorage(fileStorage.Get, 0, totalSize);
ref readonly HashSalt hashSalt = ref creationInfo.IsHashSaltEnabled ? ref creationInfo.HashSalt : ref hashSaltRandom.ValueRo;
res = _config.SaveFsCreator.Format(in subStorage, param.BlockSize, param.CountExpandMax,
param.CountDataBlock, param.CountJournalBlock, _config.BufferManager,
IsDeviceUniqueMac(spaceId), in hashSalt, _config.GenerateRandomData,
SaveDataProperties.IsReconstructible(creationInfo.Attribute.Type, spaceId),
GetJournalIntegritySaveDataVersion());
if (res.IsFailure()) return res.Miss();
}
}
SaveDataExtraData extraData = default;
@ -530,12 +873,11 @@ public class SaveDataFileSystemServiceImpl : IDisposable
{
Unsafe.SkipInit(out Array18<byte> saveImageNameBuffer);
using var fileSystem = new SharedRef<IFileSystem>();
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
// Open the directory containing the save data
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, false);
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, allowEmulatedSave: false);
if (res.IsFailure()) return res.Miss();
using scoped var saveImageName = new Path();
@ -556,27 +898,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
{
if (wipeSaveFile)
{
// If we need to wipe the save file, check if the save is encrypted. If it is, we only wipe the master
// header because it contains the key data needed to decrypt the save.
bool isDataEncrypted = false;
if (GetDebugConfigurationService().Get(DebugOptionKey.SaveDataEncryption, 0) != 0)
{
using SharedRef<IFileSystem> tempFileSystem = SharedRef<IFileSystem>.CreateCopy(in fileSystem);
res = _config.SaveFsCreator.IsDataEncrypted(out isDataEncrypted, ref tempFileSystem.Ref,
saveDataId, _config.BufferManager, IsDeviceUniqueMac(spaceId), isReconstructible: false);
if (res.IsFailure())
isDataEncrypted = false;
}
if (isDataEncrypted)
{
WipeMasterHeader(fileSystem.Get, in saveImageName, _config.GenerateRandomData).IgnoreResult();
}
else
{
WipeData(fileSystem.Get, in saveImageName, _config.GenerateRandomData).IgnoreResult();
}
WipeData(fileSystem.Get, in saveImageName, _config.GenerateRandomData).IgnoreResult();
}
res = fileSystem.Get.DeleteFile(in saveImageName);
@ -630,8 +952,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
}
public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId,
in SaveDataExtraData extraData, ref readonly Path saveDataRootPath, SaveDataType type,
bool updateTimeStamp)
in SaveDataExtraData extraData, ref readonly Path saveDataRootPath, SaveDataType type, bool updateTimeStamp)
{
// Emulated save data on a host device doesn't have extra data.
if (IsAllowedDirectorySaveData(spaceId, in saveDataRootPath))
@ -677,17 +998,76 @@ public class SaveDataFileSystemServiceImpl : IDisposable
public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset,
ref readonly Path saveDataRootPath)
{
throw new NotImplementedException();
Unsafe.SkipInit(out Array18<byte> saveImageNameBuffer);
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
using var fileSystem = new SharedRef<IFileSystem>();
// Open the directory containing the save data
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId, in saveDataRootPath, allowEmulatedSave: false);
if (res.IsFailure()) return res.Miss();
using scoped var saveImageName = new Path();
res = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId);
if (res.IsFailure()) return res.Miss();
// Check if the save data is a file or a directory
res = fileSystem.Get.GetEntryType(out DirectoryEntryType entryType, in saveImageName);
if (res.IsFailure()) return res.Miss();
if (entryType != DirectoryEntryType.File)
return ResultFs.PreconditionViolation.Log();
using var file = new UniqueRef<IFile>();
fileSystem.Get.OpenFile(ref file.Ref, in saveImageName, OpenMode.Write);
if (res.IsFailure()) return res.Miss();
const int bufferSize = 0x200;
const int corruptWriteSize = 0x4000;
Span<byte> buffer = stackalloc byte[bufferSize];
buffer.Fill(0xAA);
for (int i = 0; i < corruptWriteSize; i += bufferSize)
{
res = file.Get.Write(offset + i, buffer, WriteOption.None);
if (res.IsFailure()) return res.Miss();
}
res = file.Get.Flush();
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
public Result RecoverSaveDataFileSystemMasterHeader(SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
res = _config.SaveFsCreator.RecoverMasterHeader(in fileSystem, saveDataId, _config.BufferManager,
IsDeviceUniqueMac(spaceId), isReconstructible: false);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
public Result UpdateSaveDataFileSystemMac(SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, spaceId, saveDataId);
if (res.IsFailure()) return res.Miss();
_saveFileSystemCacheManager.Unregister(spaceId, saveDataId);
res = _config.SaveFsCreator.UpdateMac(in fileSystem, saveDataId, IsDeviceUniqueMac(spaceId), isReconstructible: false);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
private bool IsSaveEmulated(ref readonly Path saveDataRootPath)
@ -700,7 +1080,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
{
using var rootPath = new Path();
return OpenSaveDataDirectoryFileSystem(ref outFileSystem, spaceId, saveDataId, in rootPath, allowEmulatedSave: true);
return OpenSaveDataDirectoryFileSystem(ref outFileSystem, spaceId, saveDataId, in rootPath, allowEmulatedSave: true).Ret();
}
public Result OpenSaveDataDirectoryFileSystem(ref SharedRef<IFileSystem> outFileSystem,
@ -743,16 +1123,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
}
using scoped var saveDataAreaDirectoryName = new Path();
ReadOnlySpan<byte> saveDirName;
if (spaceId == SaveDataSpaceId.Temporary)
{
saveDirName = "/temp"u8;
}
else
{
saveDirName = "/save"u8;
}
ReadOnlySpan<byte> saveDirName = spaceId == SaveDataSpaceId.Temporary ? "/temp"u8 : "/save"u8;
res = PathFunctions.SetUpFixedPath(ref saveDataAreaDirectoryName.Ref(), saveDirName);
if (res.IsFailure()) return res.Miss();
@ -863,12 +1234,23 @@ public class SaveDataFileSystemServiceImpl : IDisposable
private Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef<IFileSystem> outFileSystem, SaveDataSpaceId spaceId,
ulong saveDataId, ref readonly Path directoryPath)
{
return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, saveDataId, in directoryPath, createIfMissing: true);
return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, saveDataId, in directoryPath, createIfMissing: true).Ret();
}
public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo)
{
throw new NotImplementedException();
UnsafeHelpers.SkipParamInit(out isProvisionallyCommitted);
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref, saveInfo.SpaceId, saveInfo.SaveDataId);
if (res.IsFailure()) return res.Miss();
res = _config.SaveFsCreator.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in fileSystem,
in saveInfo, IsDeviceUniqueMac(saveInfo.SpaceId), _timeStampGetter,
SaveDataProperties.IsReconstructible(saveInfo.Type, saveInfo.SpaceId));
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
/// <summary>
@ -910,20 +1292,12 @@ public class SaveDataFileSystemServiceImpl : IDisposable
/// <returns>The program ID of the save data.</returns>
public ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId)
{
// First check if the program ID is part of a multi-program application that contains a program with index 0.
ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, programIndex: 0);
if (mainProgramId != ProgramId.InvalidId)
{
return mainProgramId;
}
// Get the current program's map info and return the main program ID.
Optional<ProgramIndexMapInfo> mapInfo = _config.ProgramRegistryService.GetProgramIndexMapInfo(programId);
Optional<ProgramIndexMapInfo> programIndexMapInfo = _config.ProgramRegistryService.GetProgramIndexMapInfo(programId);
if (mapInfo.HasValue)
if (programIndexMapInfo.HasValue)
{
return mapInfo.Value.MainProgramId;
return programIndexMapInfo.Value.MainProgramId;
}
// The program ID isn't in the program index map. Probably running a single-program application
@ -932,7 +1306,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
public SaveDataTransferCryptoConfiguration GetSaveDataTransferCryptoConfiguration()
{
throw new NotImplementedException();
return _config.SaveTransferCryptoConfig;
}
public SaveDataPorterManager GetSaveDataPorterManager()

View file

@ -5,6 +5,7 @@ using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Save;
using LibHac.FsSrv;
using LibHac.Os;
namespace LibHac.FsSystem.Save;
@ -166,13 +167,14 @@ public class IntegritySaveDataFileSystem : IFileSystem
throw new NotImplementedException();
}
public static void SetGenerateRandomFunction(FileSystemClient fs, RandomDataGenerator randomFunction)
public static void SetGenerateRandomFunction(FileSystemServer fsServer, RandomDataGenerator randomFunction)
{
throw new NotImplementedException();
}
public static void SetVersionSupported(FileSystemClient fs, uint versionMin, uint versionMax)
public static void SetVersionSupported(FileSystemServer fsServer, uint versionMin, uint versionMax)
{
return;
throw new NotImplementedException();
}

View file

@ -5,6 +5,7 @@ using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Save;
using LibHac.FsSrv;
using LibHac.Os;
using LibHac.Util;
@ -559,8 +560,9 @@ public class JournalIntegritySaveDataFileSystem : IFileSystem
throw new NotImplementedException();
}
public static void SetVersionSupported(FileSystemClient fs, uint versionMin, uint versionMax)
public static void SetVersionSupported(FileSystemServer fsServer, uint versionMin, uint versionMax)
{
return;
throw new NotImplementedException();
}

View file

@ -34,7 +34,9 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
public static Result QueryTotalSize(out long outSizeTotal, long blockSize, uint countAvailableBlock,
uint countJournalBlock, int countExpandMax, uint version)
{
throw new NotImplementedException();
// Todo: Implement
outSizeTotal = 0;
return Result.Success;
}
public static Result Format(
@ -137,7 +139,14 @@ public class JournalIntegritySaveDataFileSystemDriver : ProxyFileSystemWithRetry
public static JournalIntegritySaveDataParameters SetUpSaveDataParameters(long blockSize, long dataSize, long journalSize)
{
throw new NotImplementedException();
return new JournalIntegritySaveDataParameters()
{
// Align the data sizes up to a multiple of the block size
CountDataBlock = (uint)((dataSize + blockSize - 1) / blockSize),
CountJournalBlock = (uint)((journalSize + blockSize - 1) / blockSize),
BlockSize = blockSize,
CountExpandMax = 1
};
}
public Result AcceptVisitor(IInternalStorageFileSystemVisitor visitor)