mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add DeviceSaveData mounting functions
This commit is contained in:
parent
c2e4b80bac
commit
e70e23492f
4 changed files with 308 additions and 30 deletions
179
src/LibHac/Fs/Shim/DeviceSaveData.cs
Normal file
179
src/LibHac/Fs/Shim/DeviceSaveData.cs
Normal file
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Impl;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Os;
|
||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
|
||||
|
||||
using static LibHac.Fs.Impl.AccessLogStrings;
|
||||
using static LibHac.Fs.SaveData;
|
||||
|
||||
namespace LibHac.Fs.Shim;
|
||||
|
||||
public static class DeviceSaveData
|
||||
{
|
||||
private class DeviceSaveDataAttributeGetter : ISaveDataAttributeGetter
|
||||
{
|
||||
private ProgramId _programId;
|
||||
|
||||
public DeviceSaveDataAttributeGetter(ProgramId programId)
|
||||
{
|
||||
_programId = programId;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public Result GetSaveDataAttribute(out SaveDataAttribute attribute)
|
||||
{
|
||||
Result rc = SaveDataAttribute.Make(out attribute, _programId, SaveDataType.Device, InvalidUserId,
|
||||
InvalidSystemSaveDataId);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private const SaveDataSpaceId DeviceSaveDataSpaceId = SaveDataSpaceId.User;
|
||||
|
||||
private static Result MountDeviceSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName,
|
||||
in SaveDataAttribute attribute)
|
||||
{
|
||||
Result rc = fs.CheckMountName(mountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using SharedRef<IFileSystemProxy> fileSystemProxy = fs.GetFileSystemProxyServiceObject();
|
||||
using var fileSystem = new SharedRef<IFileSystemSf>();
|
||||
|
||||
rc = fileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref(), DeviceSaveDataSpaceId, in attribute);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
var fileSystemAdapterRaw = new FileSystemServiceObjectAdapter(ref fileSystem.Ref());
|
||||
using var fileSystemAdapter = new UniqueRef<IFileSystem>(fileSystemAdapterRaw);
|
||||
|
||||
if (!fileSystemAdapter.HasValue)
|
||||
return ResultFs.AllocationMemoryFailedInDeviceSaveDataA.Log();
|
||||
|
||||
using var saveDataAttributeGetter =
|
||||
new UniqueRef<ISaveDataAttributeGetter>(new DeviceSaveDataAttributeGetter(attribute.ProgramId));
|
||||
|
||||
using var mountNameGenerator = new UniqueRef<ICommonMountNameGenerator>();
|
||||
|
||||
rc = fs.Fs.Register(mountName, fileSystemAdapterRaw, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref(),
|
||||
ref saveDataAttributeGetter.Ref(), useDataCache: false, storageForPurgeFileDataCache: null,
|
||||
usePathCache: true);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result MountDeviceSaveData(this FileSystemClient fs, U8Span mountName)
|
||||
{
|
||||
Span<byte> logBuffer = stackalloc byte[0x30];
|
||||
|
||||
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, InvalidProgramId, SaveDataType.Device,
|
||||
InvalidUserId, InvalidSystemSaveDataId);
|
||||
|
||||
fs.Impl.AbortIfNeeded(rc);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
{
|
||||
Tick start = fs.Hos.Os.GetSystemTick();
|
||||
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
|
||||
Tick end = fs.Hos.Os.GetSystemTick();
|
||||
|
||||
var sb = new U8StringBuilder(logBuffer, true);
|
||||
sb.Append(LogName).Append(mountName).Append(LogQuote);
|
||||
|
||||
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
|
||||
}
|
||||
|
||||
fs.Impl.AbortIfNeeded(rc);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result MountDeviceSaveData(this FileSystemClient fs, U8Span mountName,
|
||||
Ncm.ApplicationId applicationId)
|
||||
{
|
||||
Span<byte> logBuffer = stackalloc byte[0x50];
|
||||
|
||||
Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device,
|
||||
InvalidUserId, InvalidSystemSaveDataId);
|
||||
|
||||
fs.Impl.AbortIfNeeded(rc);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
{
|
||||
Tick start = fs.Hos.Os.GetSystemTick();
|
||||
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
|
||||
Tick end = fs.Hos.Os.GetSystemTick();
|
||||
|
||||
var sb = new U8StringBuilder(logBuffer, true);
|
||||
sb.Append(LogName).Append(mountName).Append(LogQuote)
|
||||
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X');
|
||||
|
||||
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = MountDeviceSaveDataImpl(fs.Impl, mountName, in attribute);
|
||||
}
|
||||
|
||||
fs.Impl.AbortIfNeeded(rc);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result MountDeviceSaveData(this FileSystemClient fs, U8Span mountName, ApplicationId applicationId)
|
||||
{
|
||||
return MountDeviceSaveData(fs, mountName, new Ncm.ApplicationId(applicationId.Value));
|
||||
}
|
||||
|
||||
public static bool IsDeviceSaveDataExisting(this FileSystemClient fs, ApplicationId applicationId)
|
||||
{
|
||||
Result rc;
|
||||
Span<byte> logBuffer = stackalloc byte[0x30];
|
||||
|
||||
bool exists;
|
||||
var appId = new Ncm.ApplicationId(applicationId.Value);
|
||||
|
||||
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null))
|
||||
{
|
||||
Tick start = fs.Hos.Os.GetSystemTick();
|
||||
rc = fs.Impl.IsSaveDataExisting(out exists, appId, SaveDataType.Device, InvalidUserId);
|
||||
Tick end = fs.Hos.Os.GetSystemTick();
|
||||
|
||||
var sb = new U8StringBuilder(logBuffer, true);
|
||||
sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X');
|
||||
|
||||
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fs.Impl.IsSaveDataExisting(out exists, appId, SaveDataType.Device, InvalidUserId);
|
||||
}
|
||||
|
||||
fs.Impl.LogResultErrorMessage(rc);
|
||||
Abort.DoAbortUnless(rc.IsSuccess());
|
||||
|
||||
return exists;
|
||||
}
|
||||
}
|
|
@ -147,6 +147,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
|
|||
}
|
||||
else if (attribute.Type == SaveDataType.Account && attribute.UserId == InvalidUserId)
|
||||
{
|
||||
// Trying to create a program's debug save.
|
||||
bool canAccess =
|
||||
accessControl.CanCall(OperationType.CreateSaveData) ||
|
||||
accessControl.CanCall(OperationType.DebugSaveData);
|
||||
|
@ -193,6 +194,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
|
|||
}
|
||||
else if (attribute.Type == SaveDataType.Account)
|
||||
{
|
||||
// We need debug save data permissions to open a debug save.
|
||||
bool canAccess = attribute.UserId != InvalidUserId ||
|
||||
accessControl.CanCall(OperationType.DebugSaveData);
|
||||
|
||||
|
@ -303,11 +305,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
|
|||
{
|
||||
canAccess |= accessControl.CanCall(OperationType.ExtendOwnSaveData);
|
||||
|
||||
bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData)
|
||||
bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData)
|
||||
&& attribute.Type == SaveDataType.Account
|
||||
&& attribute.UserId == UserId.InvalidId;
|
||||
|
||||
canAccess |= hasDebugAccess;
|
||||
canAccess |= canAccessDebugSave;
|
||||
|
||||
if (!canAccess)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
@ -351,11 +353,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
|
|||
{
|
||||
canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData);
|
||||
|
||||
bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData)
|
||||
bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData)
|
||||
&& attribute.Type == SaveDataType.Account
|
||||
&& attribute.UserId == UserId.InvalidId;
|
||||
|
||||
canAccess |= hasDebugAccess;
|
||||
canAccess |= canAccessDebugSave;
|
||||
}
|
||||
|
||||
if (!canAccess)
|
||||
|
@ -442,11 +444,11 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
|
|||
AccessControl accessControl = programInfo.AccessControl;
|
||||
canAccess = accessControl.CanCall(OperationType.FindOwnSaveDataWithFilter);
|
||||
|
||||
bool hasDebugAccess = accessControl.CanCall(OperationType.DebugSaveData)
|
||||
bool canAccessDebugSave = accessControl.CanCall(OperationType.DebugSaveData)
|
||||
&& filter.Attribute.Type == SaveDataType.Account
|
||||
&& filter.Attribute.UserId == UserId.InvalidId;
|
||||
|
||||
canAccess |= hasDebugAccess;
|
||||
canAccess |= canAccessDebugSave;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -2,27 +2,45 @@
|
|||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.FsSrv.Impl;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.Fs;
|
||||
|
||||
namespace LibHac.Tests.Fs.FileSystemClientTests;
|
||||
|
||||
public class HorizonServerSet
|
||||
{
|
||||
public Horizon Server { get; set; }
|
||||
public HorizonClient FsProcessClient { get; set; }
|
||||
public HorizonClient InitialProcessClient { get; set; }
|
||||
public HorizonClient Client { get; set; }
|
||||
public FileSystemServer FsServer { get; set; }
|
||||
public IFileSystem RootFileSystem { get; set; }
|
||||
}
|
||||
|
||||
public static class FileSystemServerFactory
|
||||
{
|
||||
private static Horizon CreateHorizonImpl(bool sdCardInserted, out IFileSystem rootFs)
|
||||
private static HorizonServerSet CreateHorizonImpl(bool sdCardInserted = true,
|
||||
AccessControlBits.Bits fsAcBits = AccessControlBits.Bits.Debug, ProgramLocation programLocation = default)
|
||||
{
|
||||
rootFs = new InMemoryFileSystem();
|
||||
var hos = new HorizonServerSet();
|
||||
hos.RootFileSystem = new InMemoryFileSystem();
|
||||
var keySet = new KeySet();
|
||||
|
||||
var horizon = new Horizon(new HorizonConfiguration());
|
||||
hos.Server = new Horizon(new HorizonConfiguration());
|
||||
|
||||
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||
var fsServer = new FileSystemServer(fsServerClient);
|
||||
hos.FsProcessClient = hos.Server.CreatePrivilegedHorizonClient();
|
||||
hos.FsServer = new FileSystemServer(hos.FsProcessClient);
|
||||
|
||||
hos.InitialProcessClient = hos.Server.CreatePrivilegedHorizonClient();
|
||||
|
||||
var random = new Random(12345);
|
||||
RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer);
|
||||
|
||||
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer, randomGenerator);
|
||||
var defaultObjects =
|
||||
DefaultFsServerObjects.GetDefaultEmulatedCreators(hos.RootFileSystem, keySet, hos.FsServer,
|
||||
randomGenerator);
|
||||
|
||||
defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted);
|
||||
|
||||
|
@ -32,31 +50,45 @@ public static class FileSystemServerFactory
|
|||
config.ExternalKeySet = new ExternalKeySet();
|
||||
config.RandomGenerator = randomGenerator;
|
||||
|
||||
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
|
||||
return horizon;
|
||||
FileSystemServerInitializer.InitializeWithConfig(hos.FsProcessClient, hos.FsServer, config);
|
||||
hos.FsServer.SetDebugFlagEnabled(true);
|
||||
|
||||
if (programLocation.ProgramId == ProgramId.InvalidId)
|
||||
{
|
||||
hos.Client = hos.Server.CreateHorizonClient();
|
||||
}
|
||||
else
|
||||
{
|
||||
hos.Client = hos.Server.CreateHorizonClient(programLocation, fsAcBits);
|
||||
}
|
||||
|
||||
private static FileSystemClient CreateClientImpl(bool sdCardInserted, out IFileSystem rootFs)
|
||||
{
|
||||
Horizon horizon = CreateHorizonImpl(sdCardInserted, out rootFs);
|
||||
|
||||
HorizonClient horizonClient = horizon.CreatePrivilegedHorizonClient();
|
||||
|
||||
return horizonClient.Fs;
|
||||
return hos;
|
||||
}
|
||||
|
||||
public static FileSystemClient CreateClient(bool sdCardInserted)
|
||||
{
|
||||
return CreateClientImpl(sdCardInserted, out _);
|
||||
HorizonServerSet hos = CreateHorizonImpl(sdCardInserted: sdCardInserted);
|
||||
|
||||
return hos.InitialProcessClient.Fs;
|
||||
}
|
||||
|
||||
public static FileSystemClient CreateClient(out IFileSystem rootFs)
|
||||
{
|
||||
return CreateClientImpl(false, out rootFs);
|
||||
HorizonServerSet hos = CreateHorizonImpl(sdCardInserted: true);
|
||||
rootFs = hos.RootFileSystem;
|
||||
|
||||
return hos.InitialProcessClient.Fs;
|
||||
}
|
||||
|
||||
public static Horizon CreateHorizonServer()
|
||||
{
|
||||
return CreateHorizonImpl(true, out _);
|
||||
return CreateHorizonImpl(sdCardInserted: true).Server;
|
||||
}
|
||||
|
||||
public static HorizonServerSet CreateHorizon(ProgramId programId = default, bool sdCardInserted = true,
|
||||
AccessControlBits.Bits fsAcBits = AccessControlBits.Bits.Debug)
|
||||
{
|
||||
var programLocation = new ProgramLocation(programId, StorageId.BuiltInUser);
|
||||
return CreateHorizonImpl(sdCardInserted, fsAcBits, programLocation);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSrv.Impl;
|
||||
using Xunit;
|
||||
|
||||
namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests;
|
||||
|
||||
public class DeviceSaveData
|
||||
{
|
||||
[Fact]
|
||||
public static void MountDeviceSaveData_SaveDoesNotExist_ReturnsTargetNotFound()
|
||||
{
|
||||
var applicationId = new Ncm.ApplicationId(1234);
|
||||
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(applicationId, fsAcBits: AccessControlBits.Bits.FullPermission);
|
||||
|
||||
Assert.Result(ResultFs.TargetNotFound, hos.Client.Fs.MountDeviceSaveData("device".ToU8Span(), applicationId));
|
||||
Assert.Result(ResultFs.TargetNotFound, hos.Client.Fs.MountDeviceSaveData("device2".ToU8Span()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void MountDeviceSaveData_OwnDeviceSaveExists_ReturnsSuccess()
|
||||
{
|
||||
var applicationId = new Ncm.ApplicationId(1234);
|
||||
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(applicationId, fsAcBits: AccessControlBits.Bits.FullPermission);
|
||||
|
||||
Assert.Success(hos.Client.Fs.CreateDeviceSaveData(applicationId, applicationId.Value, 0, 0, SaveDataFlags.None));
|
||||
Assert.Success(hos.Client.Fs.MountDeviceSaveData("device".ToU8Span()));
|
||||
Assert.Success(hos.Client.Fs.MountDeviceSaveData("device2".ToU8Span(), applicationId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void MountDeviceSaveData_OtherDeviceSaveExists_ReturnsSuccess()
|
||||
{
|
||||
var ownApplicationId = new Ncm.ApplicationId(1234);
|
||||
var otherApplicationId = new Ncm.ApplicationId(12345);
|
||||
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(ownApplicationId, fsAcBits: AccessControlBits.Bits.FullPermission);
|
||||
|
||||
Assert.Success(hos.Client.Fs.CreateDeviceSaveData(otherApplicationId, otherApplicationId.Value, 0, 0, SaveDataFlags.None));
|
||||
Assert.Success(hos.Client.Fs.MountDeviceSaveData("device".ToU8Span(), otherApplicationId));
|
||||
|
||||
// Try to open missing own device save data
|
||||
Assert.Result(ResultFs.TargetNotFound, hos.Client.Fs.MountDeviceSaveData("device2".ToU8Span()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void IsDeviceSaveDataExisting_ReturnsCorrectState()
|
||||
{
|
||||
var applicationId = new ApplicationId(1234);
|
||||
var ncmApplicationId = new Ncm.ApplicationId(applicationId.Value);
|
||||
HorizonServerSet hos = FileSystemServerFactory.CreateHorizon(ncmApplicationId);
|
||||
FileSystemClient fs = hos.InitialProcessClient.Fs;
|
||||
|
||||
// Should return false when there aren't any saves.
|
||||
Assert.False(fs.IsDeviceSaveDataExisting(applicationId));
|
||||
|
||||
// Should return true after creating the save.
|
||||
Assert.Success(fs.CreateDeviceSaveData(ncmApplicationId, applicationId.Value, 0, 0, SaveDataFlags.None));
|
||||
Assert.True(fs.IsDeviceSaveDataExisting(applicationId));
|
||||
|
||||
// Should return false after deleting the save.
|
||||
Assert.Success(fs.DeleteDeviceSaveData(applicationId));
|
||||
Assert.False(fs.IsDeviceSaveDataExisting(applicationId));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue