From dea3b3a8b00204c06449b480160cd99526788ab7 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 24 Jan 2022 13:39:39 -0700 Subject: [PATCH] Add some save data transfer client classes - Fs.ISaveDataChunkIterator - Fs.ISaveDataChunkExporter - Fs.ISaveDataChunkImporter - Fs.ISaveDataDivisionExporter - Fs.ISaveDataDivisionImporter - Fs.SaveDataTransferManagerVersion2 - Fs.SaveDataTransferProhibiterForCloudBackUp - Fs.Impl.SaveDataChunkIterator - Fs.Impl.SaveDataChunkExporter - Fs.Impl.SaveDataChunkImporter - Fs.Impl.SaveDataExporterVersion2 - Fs.Impl.SaveDataImporterVersion2 - Fs.Shim.SaveDataTransferVersion2Shim --- src/LibHac/Common/FixedArrays/Array16384.cs | 31 ++ src/LibHac/Common/FixedArrays/Array4096.cs | 31 ++ src/LibHac/Common/FixedArrays/Array8192.cs | 31 ++ src/LibHac/Fs/Impl/SaveDataTransferTypes.cs | 9 + src/LibHac/Fs/SaveData.cs | 2 +- src/LibHac/Fs/SaveDataStructs.cs | 8 +- src/LibHac/Fs/SaveDataTransferInterfaces.cs | 69 +++ src/LibHac/Fs/SaveDataTransferTypes.cs | 5 + src/LibHac/Fs/Shim/SaveDataTransferAdapter.cs | 469 ++++++++++++++++++ .../Fs/Shim/SaveDataTransferVersion2.cs | 374 ++++++++++++++ .../FsSrv/Sf/ISaveDataDivisionExporter.cs | 2 +- tests/LibHac.Tests/Fs/TypeLayoutTests.cs | 54 ++ 12 files changed, 1082 insertions(+), 3 deletions(-) create mode 100644 src/LibHac/Common/FixedArrays/Array16384.cs create mode 100644 src/LibHac/Common/FixedArrays/Array4096.cs create mode 100644 src/LibHac/Common/FixedArrays/Array8192.cs create mode 100644 src/LibHac/Fs/SaveDataTransferInterfaces.cs create mode 100644 src/LibHac/Fs/Shim/SaveDataTransferAdapter.cs create mode 100644 src/LibHac/Fs/Shim/SaveDataTransferVersion2.cs diff --git a/src/LibHac/Common/FixedArrays/Array16384.cs b/src/LibHac/Common/FixedArrays/Array16384.cs new file mode 100644 index 00000000..0fccaec0 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array16384.cs @@ -0,0 +1,31 @@ +#pragma warning disable CS0169, CS0649, IDE0051 // Field is never used, Field is never assigned to, Remove unused private members +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +public struct Array16384 +{ + public const int Length = 16384; + + private Array8192 _0; + private Array8192 _8192; + + public ref T this[int i] => ref Items[i]; + + public Span Items + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); + } + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array16384 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array4096.cs b/src/LibHac/Common/FixedArrays/Array4096.cs new file mode 100644 index 00000000..5f2020cc --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array4096.cs @@ -0,0 +1,31 @@ +#pragma warning disable CS0169, CS0649, IDE0051 // Field is never used, Field is never assigned to, Remove unused private members +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +public struct Array4096 +{ + public const int Length = 4096; + + private Array2048 _0; + private Array2048 _2048; + + public ref T this[int i] => ref Items[i]; + + public Span Items + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); + } + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array4096 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array8192.cs b/src/LibHac/Common/FixedArrays/Array8192.cs new file mode 100644 index 00000000..92059b10 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array8192.cs @@ -0,0 +1,31 @@ +#pragma warning disable CS0169, CS0649, IDE0051 // Field is never used, Field is never assigned to, Remove unused private members +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +public struct Array8192 +{ + public const int Length = 8192; + + private Array4096 _0; + private Array4096 _4096; + + public ref T this[int i] => ref Items[i]; + + public Span Items + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); + } + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array8192 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs b/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs index 4c7090a3..1d4e6750 100644 --- a/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs +++ b/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs @@ -17,6 +17,15 @@ public struct InitialDataMac public Array16 Value; } +public struct ExportReportInfo +{ + public byte DiffChunkCount; + public byte DoubleDivisionDiffChunkCount; + public byte HalfDivisionDiffChunkCount; + public byte CompressionRate; + public Array28 Reserved; +} + public struct ImportReportInfo { public byte DiffChunkCount; diff --git a/src/LibHac/Fs/SaveData.cs b/src/LibHac/Fs/SaveData.cs index 2f7cce99..6e4bfc54 100644 --- a/src/LibHac/Fs/SaveData.cs +++ b/src/LibHac/Fs/SaveData.cs @@ -8,5 +8,5 @@ public static class SaveData public static ProgramId InvalidProgramId => ProgramId.InvalidId; public static ProgramId AutoResolveCallerProgramId => new ProgramId(ulong.MaxValue - 1); public static UserId InvalidUserId => UserId.InvalidId; - public static readonly ulong InvalidSystemSaveDataId = 0; + public static ulong InvalidSystemSaveDataId => 0; } diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index 6aa64c51..1678098b 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -204,10 +204,16 @@ public struct SaveDataFilter return Result.Success; } + public static SaveDataFilter Make(Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index) + { + return Make(programId, saveType, userId, saveDataId, index, SaveDataRank.Primary); + } + public static SaveDataFilter Make(Optional programId, Optional saveType, Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) { - var filter = new SaveDataFilter(); + SaveDataFilter filter = default; if (programId.HasValue) { diff --git a/src/LibHac/Fs/SaveDataTransferInterfaces.cs b/src/LibHac/Fs/SaveDataTransferInterfaces.cs new file mode 100644 index 00000000..89b0b0c0 --- /dev/null +++ b/src/LibHac/Fs/SaveDataTransferInterfaces.cs @@ -0,0 +1,69 @@ +using System; +using LibHac.Common; +using LibHac.Common.FixedArrays; +using LibHac.Fs.Impl; +using LibHac.Time; + +namespace LibHac.Fs; + +public interface ISaveDataChunkIterator : IDisposable +{ + ushort GetId(); + void Next(); + bool IsEnd(); +} + +public interface ISaveDataChunkExporter : IDisposable +{ + Result Pull(out ulong outPulledSize, Span destination); + long GetRestRawDataSize(); +} + +public interface ISaveDataChunkImporter : IDisposable +{ + Result Push(ReadOnlySpan source); +} + +public interface ISaveDataDivisionExporter : IDisposable +{ + Result SetDivisionCount(int divisionCount); + Result OpenSaveDataDiffChunkIterator(ref UniqueRef outIterator); + Result OpenSaveDataChunkExporter(ref UniqueRef outExporter, ushort chunkId); + Result GetKeySeed(out KeySeed outKeySeed); + Result GetInitialDataMac(out InitialDataMac outInitialDataMac); + Result GetInitialDataMacKeyGeneration(out int outKeyGeneration); + Result FinalizeExport(); + Result CancelExport(); + Result SuspendExport(out ExportContext outContext); + Result GetImportInitialDataAad(out InitialDataAad outInitialDataAad); + Result SetExportInitialDataAad(in InitialDataAad initialDataAad); + Result GetSaveDataCommitId(out long outCommitId); + Result GetSaveDataTimeStamp(out PosixTime outTimeStamp); + Result GetReportInfo(out ExportReportInfo outReportInfo); + + public struct ExportContext + { + public Array16384 Value; + } +} + +public interface ISaveDataDivisionImporter : IDisposable +{ + Result OpenSaveDataDiffChunkIterator(ref UniqueRef outIterator); + Result InitializeImport(out long remaining, long sizeToProcess); + Result FinalizeImport(); + Result FinalizeImportWithoutSwap(); + Result CancelImport(); + Result GetImportContext(out ImportContext outContext); + Result SuspendImport(); + Result OpenSaveDataChunkImporter(ref UniqueRef outImporter, ushort chunkId); + Result GetImportInitialDataAad(out InitialDataAad outInitialDataAad); + Result GetSaveDataCommitId(out long outCommitId); + Result GetSaveDataTimeStamp(out PosixTime outTimeStamp); + Result GetReportInfo(out ImportReportInfo outReportInfo); + + public struct ImportContext + { + public Array16384 Value; + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/SaveDataTransferTypes.cs b/src/LibHac/Fs/SaveDataTransferTypes.cs index d3777e0a..f65ff69f 100644 --- a/src/LibHac/Fs/SaveDataTransferTypes.cs +++ b/src/LibHac/Fs/SaveDataTransferTypes.cs @@ -10,4 +10,9 @@ public struct RsaEncryptedKey public struct AesKey { public Array16 Value; +} + +public struct InitialDataVersion2 +{ + public Array8192 Value; } \ No newline at end of file diff --git a/src/LibHac/Fs/Shim/SaveDataTransferAdapter.cs b/src/LibHac/Fs/Shim/SaveDataTransferAdapter.cs new file mode 100644 index 00000000..3fe197e9 --- /dev/null +++ b/src/LibHac/Fs/Shim/SaveDataTransferAdapter.cs @@ -0,0 +1,469 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Sf; +using LibHac.Time; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs.Impl; + +/// +/// An adapter for interacting with an +/// IPC service object via the non-IPC interface. +/// +/// Based on nnSdk 13.4.0 +public class SaveDataChunkIterator : ISaveDataChunkIterator +{ + private SharedRef _baseInterface; + + // LibHac addition + private FileSystemClient _fsClient; + + public SaveDataChunkIterator(FileSystemClient fs, ref SharedRef baseInterface) + { + _baseInterface = SharedRef.CreateMove(ref baseInterface); + _fsClient = fs; + } + + public void Dispose() + { + _baseInterface.Destroy(); + } + + public ushort GetId() + { + Result rc = _baseInterface.Get.GetId(out uint id); + _fsClient.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return (ushort)id; + } + + public void Next() + { + Result rc = _baseInterface.Get.Next(); + _fsClient.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + } + + public bool IsEnd() + { + Result rc = _baseInterface.Get.IsEnd(out bool isEnd); + _fsClient.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return isEnd; + } +} + +/// +/// An adapter for interacting with an +/// IPC service object via the non-IPC interface. +/// +/// Based on nnSdk 13.4.0 +public class SaveDataChunkExporter : ISaveDataChunkExporter +{ + private SharedRef _baseInterface; + + // LibHac addition + private FileSystemClient _fsClient; + + public SaveDataChunkExporter(FileSystemClient fs, ref SharedRef baseInterface) + { + _baseInterface = SharedRef.CreateMove(ref baseInterface); + _fsClient = fs; + } + + public void Dispose() + { + _baseInterface.Destroy(); + } + + public Result Pull(out ulong outPulledSize, Span destination) + { + UnsafeHelpers.SkipParamInit(out outPulledSize); + + Result rc = _baseInterface.Get.Pull(out ulong pulledSize, new OutBuffer(destination), + (ulong)destination.Length); + + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outPulledSize = pulledSize; + return Result.Success; + } + + public long GetRestRawDataSize() + { + Result rc = _baseInterface.Get.GetRestRawDataSize(out long restSize); + _fsClient.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return restSize; + } +} + +/// +/// An adapter for interacting with an +/// IPC service object via the non-IPC interface. +/// +/// Based on nnSdk 13.4.0 +public class SaveDataChunkImporter : ISaveDataChunkImporter +{ + private SharedRef _baseInterface; + + // LibHac addition + private FileSystemClient _fsClient; + + public SaveDataChunkImporter(FileSystemClient fs, ref SharedRef baseInterface) + { + _baseInterface = SharedRef.CreateMove(ref baseInterface); + _fsClient = fs; + } + + public void Dispose() + { + _baseInterface.Destroy(); + } + + public Result Push(ReadOnlySpan source) + { + Result rc = _baseInterface.Get.Push(new InBuffer(source), (ulong)source.Length); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } +} + +/// +/// An adapter for interacting with an +/// IPC service object via the non-IPC interface. +/// +/// Based on nnSdk 13.4.0 +public class SaveDataExporterVersion2 : ISaveDataDivisionExporter +{ + private SharedRef _baseInterface; + + // LibHac addition + private FileSystemClient _fsClient; + + public SaveDataExporterVersion2(FileSystemClient fs, + ref SharedRef baseInterface) + { + _baseInterface = SharedRef.CreateMove(ref baseInterface); + _fsClient = fs; + } + + public void Dispose() + { + _baseInterface.Destroy(); + } + + public Result SetDivisionCount(int divisionCount) + { + Result rc = _baseInterface.Get.SetDivisionCount(divisionCount); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result OpenSaveDataDiffChunkIterator(ref UniqueRef outIterator) + { + using var iteratorObject = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataDiffChunkIterator(ref iteratorObject.Ref()); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outIterator.Reset(new SaveDataChunkIterator(_fsClient, ref iteratorObject.Ref())); + return Result.Success; + } + + public Result OpenSaveDataChunkExporter(ref UniqueRef outExporter, ushort chunkId) + { + using var exporterObject = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataChunkExporter(ref exporterObject.Ref(), chunkId); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outExporter.Reset(new SaveDataChunkExporter(_fsClient, ref exporterObject.Ref())); + return Result.Success; + } + + public Result GetKeySeed(out KeySeed outKeySeed) + { + UnsafeHelpers.SkipParamInit(out outKeySeed); + + Result rc = _baseInterface.Get.GetKeySeed(out KeySeed keySeed); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outKeySeed = keySeed; + return Result.Success; + } + + public Result GetInitialDataMac(out InitialDataMac outInitialDataMac) + { + UnsafeHelpers.SkipParamInit(out outInitialDataMac); + + Result rc = _baseInterface.Get.GetInitialDataMac(out InitialDataMac initialDataMac); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outInitialDataMac = initialDataMac; + return Result.Success; + } + + public Result GetInitialDataMacKeyGeneration(out int outKeyGeneration) + { + UnsafeHelpers.SkipParamInit(out outKeyGeneration); + + Result rc = _baseInterface.Get.GetInitialDataMacKeyGeneration(out int initialDataMacKeyGeneration); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outKeyGeneration = initialDataMacKeyGeneration; + return Result.Success; + } + + public Result FinalizeExport() + { + Result rc = _baseInterface.Get.FinalizeExport(); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result CancelExport() + { + Result rc = _baseInterface.Get.CancelExport(); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result SuspendExport(out ISaveDataDivisionExporter.ExportContext outContext) + { + UnsafeHelpers.SkipParamInit(out outContext); + + Result rc = _baseInterface.Get.SuspendExport(OutBuffer.FromStruct(ref outContext)); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result GetImportInitialDataAad(out InitialDataAad outInitialDataAad) + { + UnsafeHelpers.SkipParamInit(out outInitialDataAad); + + Result rc = _baseInterface.Get.GetImportInitialDataAad(out InitialDataAad initialDataAad); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outInitialDataAad = initialDataAad; + return Result.Success; + } + + public Result SetExportInitialDataAad(in InitialDataAad initialDataAad) + { + Result rc = _baseInterface.Get.SetExportInitialDataAad(in initialDataAad); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result GetSaveDataCommitId(out long outCommitId) + { + UnsafeHelpers.SkipParamInit(out outCommitId); + Unsafe.SkipInit(out SaveDataExtraData extraData); + + Result rc = _baseInterface.Get.ReadSaveDataExtraData(OutBuffer.FromStruct(ref extraData)); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outCommitId = extraData.CommitId; + return Result.Success; + } + + public Result GetSaveDataTimeStamp(out PosixTime outTimeStamp) + { + UnsafeHelpers.SkipParamInit(out outTimeStamp); + Unsafe.SkipInit(out SaveDataExtraData extraData); + + Result rc = _baseInterface.Get.ReadSaveDataExtraData(OutBuffer.FromStruct(ref extraData)); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outTimeStamp = new PosixTime(extraData.TimeStamp); + return Result.Success; + } + + public Result GetReportInfo(out ExportReportInfo outReportInfo) + { + Result rc = _baseInterface.Get.GetReportInfo(out outReportInfo); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } +} + +/// +/// An adapter for interacting with an +/// IPC service object via the non-IPC interface. +/// +/// Based on nnSdk 13.4.0 +public class SaveDataImporterVersion2 : ISaveDataDivisionImporter +{ + private SharedRef _baseInterface; + + // LibHac addition + private FileSystemClient _fsClient; + + public SaveDataImporterVersion2(FileSystemClient fs, + ref SharedRef baseInterface) + { + _baseInterface = SharedRef.CreateMove(ref baseInterface); + _fsClient = fs; + } + + public void Dispose() + { + _baseInterface.Destroy(); + } + + public Result InitializeImport(out long remaining, long sizeToProcess) + { + Result rc = _baseInterface.Get.InitializeImport(out remaining, sizeToProcess); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result FinalizeImport() + { + Result rc = _baseInterface.Get.FinalizeImport(); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result FinalizeImportWithoutSwap() + { + Result rc = _baseInterface.Get.FinalizeImportWithoutSwap(); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result CancelImport() + { + Result rc = _baseInterface.Get.CancelImport(); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result GetImportContext(out ISaveDataDivisionImporter.ImportContext outContext) + { + UnsafeHelpers.SkipParamInit(out outContext); + + Result rc = _baseInterface.Get.GetImportContext(OutBuffer.FromStruct(ref outContext)); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result SuspendImport() + { + Result rc = _baseInterface.Get.SuspendImport(); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result OpenSaveDataDiffChunkIterator(ref UniqueRef outIterator) + { + using var iteratorObject = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataDiffChunkIterator(ref iteratorObject.Ref()); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outIterator.Reset(new SaveDataChunkIterator(_fsClient, ref iteratorObject.Ref())); + return Result.Success; + } + + public Result OpenSaveDataChunkImporter(ref UniqueRef outImporter, ushort chunkId) + { + using var importerObject = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataChunkImporter(ref importerObject.Ref(), chunkId); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outImporter.Reset(new SaveDataChunkImporter(_fsClient, ref importerObject.Ref())); + return Result.Success; + } + + public Result GetImportInitialDataAad(out InitialDataAad outInitialDataAad) + { + UnsafeHelpers.SkipParamInit(out outInitialDataAad); + + Result rc = _baseInterface.Get.GetImportInitialDataAad(out InitialDataAad initialDataAad); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outInitialDataAad = initialDataAad; + return Result.Success; + } + + public Result GetSaveDataCommitId(out long outCommitId) + { + UnsafeHelpers.SkipParamInit(out outCommitId); + Unsafe.SkipInit(out SaveDataExtraData extraData); + + Result rc = _baseInterface.Get.ReadSaveDataExtraData(OutBuffer.FromStruct(ref extraData)); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outCommitId = extraData.CommitId; + return Result.Success; + } + + public Result GetSaveDataTimeStamp(out PosixTime outTimeStamp) + { + UnsafeHelpers.SkipParamInit(out outTimeStamp); + Unsafe.SkipInit(out SaveDataExtraData extraData); + + Result rc = _baseInterface.Get.ReadSaveDataExtraData(OutBuffer.FromStruct(ref extraData)); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + outTimeStamp = new PosixTime(extraData.TimeStamp); + return Result.Success; + } + + public Result GetReportInfo(out ImportReportInfo outReportInfo) + { + Result rc = _baseInterface.Get.GetReportInfo(out outReportInfo); + _fsClient.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/Shim/SaveDataTransferVersion2.cs b/src/LibHac/Fs/Shim/SaveDataTransferVersion2.cs new file mode 100644 index 00000000..6dc218c5 --- /dev/null +++ b/src/LibHac/Fs/Shim/SaveDataTransferVersion2.cs @@ -0,0 +1,374 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Common.FixedArrays; +using LibHac.Diag; +using LibHac.Fs.Impl; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Sf; +using LibHac.Sf; +using LibHac.Util; + +using static LibHac.Fs.SaveData; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs +{ + public class SaveDataTransferManagerVersion2 : IDisposable + { + private SharedRef _baseInterface; + + // LibHac addition + private FileSystemClient _fsClient; + + public struct Challenge + { + public Array16 Value; + } + + public struct SaveDataTag + { + public Array64 Value; + } + + public struct KeySeedPackage + { + public Array512 Value; + } + + public SaveDataTransferManagerVersion2(FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.OpenSaveDataTransferManagerVersion2(ref _baseInterface); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + _fsClient = fs; + } + + public void Dispose() + { + _baseInterface.Destroy(); + } + + public Result GetChallenge(out Challenge outChallenge) + { + UnsafeHelpers.SkipParamInit(out outChallenge); + + Result rc = _baseInterface.Get.GetChallenge(OutBuffer.FromStruct(ref outChallenge)); + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result SetKeySeedPackage(in KeySeedPackage keySeedPackage) + { + Result rc = _baseInterface.Get.SetKeySeedPackage(InBuffer.FromStruct(in keySeedPackage)); + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result OpenSaveDataFullExporter(ref UniqueRef outExporter, + SaveDataSpaceId spaceId, ulong saveDataId) + { + using var exporterInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataExporter(ref exporterInterface.Ref(), spaceId, saveDataId); + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outExporter.Reset(new SaveDataExporterVersion2(_fsClient, ref exporterInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataDiffExporter(ref UniqueRef outExporter, + in InitialDataVersion2 initialData, SaveDataSpaceId spaceId, ulong saveDataId) + { + using var exporterInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataExporterForDiffExport(ref exporterInterface.Ref(), + InBuffer.FromStruct(in initialData), spaceId, saveDataId); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outExporter.Reset(new SaveDataExporterVersion2(_fsClient, ref exporterInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataExporterByContext(ref UniqueRef outExporter, + ISaveDataDivisionExporter.ExportContext exportContext) + { + using var exporterInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataExporterByContext(ref exporterInterface.Ref(), + InBuffer.FromStruct(in exportContext)); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outExporter.Reset(new SaveDataExporterVersion2(_fsClient, ref exporterInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataFullImporter(ref UniqueRef outImporter, + in InitialDataVersion2 initialData, in UserId userId, SaveDataSpaceId spaceId) + { + using var importerInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataImporterDeprecated(ref importerInterface.Ref(), + InBuffer.FromStruct(in initialData), in userId, spaceId); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outImporter.Reset(new SaveDataImporterVersion2(_fsClient, ref importerInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataDiffImporter(ref UniqueRef outImporter, + in InitialDataVersion2 initialData, SaveDataSpaceId spaceId, ulong saveDataId) + { + using var importerInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataImporterForDiffImport(ref importerInterface.Ref(), + InBuffer.FromStruct(in initialData), spaceId, saveDataId); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outImporter.Reset(new SaveDataImporterVersion2(_fsClient, ref importerInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataDuplicateDiffImporter(ref UniqueRef outImporter, + in InitialDataVersion2 initialData, SaveDataSpaceId spaceId, ulong saveDataId) + { + using var importerInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataImporterForDuplicateDiffImport(ref importerInterface.Ref(), + InBuffer.FromStruct(in initialData), spaceId, saveDataId); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outImporter.Reset(new SaveDataImporterVersion2(_fsClient, ref importerInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataImporterImpl(ref UniqueRef outImporter, + in InitialDataVersion2 initialData, in UserId userId, SaveDataSpaceId spaceId, bool useSwap) + { + using var importerInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataImporter(ref importerInterface.Ref(), + InBuffer.FromStruct(in initialData), in userId, spaceId, useSwap); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outImporter.Reset(new SaveDataImporterVersion2(_fsClient, ref importerInterface.Ref())); + return Result.Success; + } + + public Result OpenSaveDataImporter(ref UniqueRef outImporter, + in InitialDataVersion2 initialData, SaveDataSpaceId spaceId, bool useSwap) + { + return OpenSaveDataImporterImpl(ref outImporter, in initialData, UserId.InvalidId, spaceId, useSwap); + } + + public Result OpenSaveDataImporterByContext(ref UniqueRef outImporter, + in ISaveDataDivisionImporter.ImportContext importContext) + { + using var importerInterface = new SharedRef(); + + Result rc = _baseInterface.Get.OpenSaveDataImporterByContext(ref importerInterface.Ref(), + InBuffer.FromStruct(in importContext)); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + outImporter.Reset(new SaveDataImporterVersion2(_fsClient, ref importerInterface.Ref())); + return Result.Success; + } + + public static SaveDataTag MakeUserAccountSaveDataTag(Ncm.ApplicationId applicationId, in UserId userId) + { + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account, + userId, InvalidSystemSaveDataId, index: 0, SaveDataRank.Primary); + Abort.DoAbortUnless(rc.IsSuccess()); + + return Unsafe.As(ref attribute); + } + + public static SaveDataTag MakeDeviceSaveDataTag(Ncm.ApplicationId applicationId) + { + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device, + InvalidUserId, InvalidSystemSaveDataId, index: 0, SaveDataRank.Primary); + Abort.DoAbortUnless(rc.IsSuccess()); + + return Unsafe.As(ref attribute); + } + + public Result CancelSuspendingImport(in SaveDataTag tag) + { + ref readonly SaveDataAttribute attribute = + ref Unsafe.As(ref Unsafe.AsRef(in tag)); + + Result rc = _baseInterface.Get.CancelSuspendingImportByAttribute(in attribute); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result CancelSuspendingImport(Ncm.ApplicationId applicationId, in UserId userId) + { + Result rc = _baseInterface.Get.CancelSuspendingImport(applicationId, in userId); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result SwapSecondary(in SaveDataTag tag, Optional primaryCommitId) + { + long commitId = primaryCommitId.HasValue ? primaryCommitId.Value : 0; + bool doSwap = primaryCommitId.HasValue; + ref readonly SaveDataAttribute attribute = + ref Unsafe.As(ref Unsafe.AsRef(in tag)); + + Result rc = _baseInterface.Get.SwapSecondary(in attribute, doSwap, commitId); + + _fsClient.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + public class SaveDataTransferProhibiterForCloudBackUp : IDisposable + { + private SharedRef _prohibiter; + + public SaveDataTransferProhibiterForCloudBackUp(ref SharedRef prohibiter) + { + _prohibiter = SharedRef.CreateMove(ref prohibiter); + } + + public void Dispose() + { + _prohibiter.Destroy(); + } + } +} + +namespace LibHac.Fs.Shim +{ + public static class SaveDataTransferVersion2Shim + { + public static Result OpenSaveDataTransferProhibiterForCloudBackUp(this FileSystemClientImpl fs, + ref UniqueRef outProhibiter, Ncm.ApplicationId applicationId) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + using var prohibiter = new SharedRef(); + + // Todo: Uncomment once opening transfer prohibiters is implemented + // Result rc = fileSystemProxy.Get.OpenSaveDataTransferProhibiter(ref prohibiter.Ref(), applicationId); + // if (rc.IsFailure()) return rc.Miss(); + + outProhibiter.Reset(new SaveDataTransferProhibiterForCloudBackUp(ref prohibiter.Ref())); + + return Result.Success; + } + + public static Result OpenSaveDataTransferProhibiterForCloudBackUp(this FileSystemClient fs, + ref UniqueRef outProhibiter, Ncm.ApplicationId applicationId) + { + Result rc = fs.Impl.OpenSaveDataTransferProhibiterForCloudBackUp(ref outProhibiter, applicationId); + fs.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result OpenSaveDataTransferProhibiterForCloudBackUp(this FileSystemClient fs, + Span> outProhibiters, + ReadOnlySpan applicationIds) + { + for (int i = 0; i < applicationIds.Length; i++) + { + Result rc = fs.Impl.OpenSaveDataTransferProhibiterForCloudBackUp(ref outProhibiters[i], + applicationIds[i]); + + fs.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + } + + return Result.Success; + } + + public static Result GetCountOfApplicationAccessibleSaveDataOwnerId(this FileSystemClient fs, out int outCount, + Ncm.ApplicationId applicationId, int programIndex) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + ulong tempAppId = 0; + var programId = new Ncm.ProgramId(applicationId.Value + (uint)programIndex); + + Result rc = fileSystemProxy.Get.ListAccessibleSaveDataOwnerId(out outCount, + OutBuffer.FromStruct(ref tempAppId), programId, startIndex: 0, bufferIdCount: 0); + + fs.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result GetOccupiedWorkSpaceSizeForCloudBackUp(this FileSystemClient fs, out long outSize) + { + UnsafeHelpers.SkipParamInit(out outSize); + + using var iterator = new UniqueRef(); + + // We want to iterate every save with a Secondary rank + Result rc = SaveDataFilter.Make(out SaveDataFilter filter, programId: default, saveType: default, + userId: default, saveDataId: default, index: default, SaveDataRank.Secondary); + + fs.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + rc = fs.Impl.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User, in filter); + fs.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + long workSize = 0; + + while (true) + { + Unsafe.SkipInit(out SaveDataInfo info); + + rc = fs.Impl.ReadSaveDataIteratorSaveDataInfo(out long count, SpanHelpers.AsSpan(ref info), + iterator.Get); + + fs.Impl.LogResultErrorMessage(rc); + if (rc.IsFailure()) return rc.Miss(); + + // Break once we've iterated all saves + if (count == 0) + break; + + if (info.Rank == SaveDataRank.Secondary) + workSize += info.Size; + } + + outSize = workSize; + return Result.Success; + } + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs index aaad50a4..24d49b74 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs @@ -19,5 +19,5 @@ public interface ISaveDataDivisionExporter : IDisposable public Result GetInitialDataMacKeyGeneration(out int keyGeneration); public Result GetImportInitialDataAad(out InitialDataAad initialDataAad); public Result SetExportInitialDataAad(in InitialDataAad initialDataAad); - public Result GetReportInfo(out ImportReportInfo reportInfo); + public Result GetReportInfo(out ExportReportInfo reportInfo); } diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 363850a6..113caed0 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -341,6 +341,20 @@ public class TypeLayoutTests Assert.Equal(0x00, GetOffset(in s, in s.Value)); } + [Fact] + public static void ExportReportInfo_Layout() + { + var s = new ExportReportInfo(); + + Assert.Equal(0x20, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.DiffChunkCount)); + Assert.Equal(1, GetOffset(in s, in s.DoubleDivisionDiffChunkCount)); + Assert.Equal(2, GetOffset(in s, in s.HalfDivisionDiffChunkCount)); + Assert.Equal(3, GetOffset(in s, in s.CompressionRate)); + Assert.Equal(4, GetOffset(in s, in s.Reserved)); + } + [Fact] public static void ImportReportInfo_Layout() { @@ -365,4 +379,44 @@ public class TypeLayoutTests Assert.Equal(0, GetOffset(in s, in s.Index)); Assert.Equal(4, GetOffset(in s, in s.Reserved)); } + + [Fact] + public static void Challenge_Layout() + { + var s = new SaveDataTransferManagerVersion2.Challenge(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void SaveDataTag_Layout() + { + var s = new SaveDataTransferManagerVersion2.SaveDataTag(); + + Assert.Equal(0x40, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void KeySeedPackage_Layout() + { + var s = new SaveDataTransferManagerVersion2.KeySeedPackage(); + + Assert.Equal(0x200, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void InitialDataVersion2_Layout() + { + var s = new InitialDataVersion2(); + + Assert.Equal(0x2000, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Value)); + } } \ No newline at end of file