mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add fs_Host shims
This commit is contained in:
parent
a006816a2e
commit
7b4df4671c
8 changed files with 373 additions and 3 deletions
|
@ -12,6 +12,7 @@ namespace LibHac.Common
|
||||||
private int _length;
|
private int _length;
|
||||||
|
|
||||||
public bool Overflowed { get; private set; }
|
public bool Overflowed { get; private set; }
|
||||||
|
public int Length => _length;
|
||||||
public int Capacity => _buffer.Length - NullTerminatorLength;
|
public int Capacity => _buffer.Length - NullTerminatorLength;
|
||||||
|
|
||||||
public U8StringBuilder(Span<byte> buffer)
|
public U8StringBuilder(Span<byte> buffer)
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
internal static class CommonMountNames
|
internal static class CommonMountNames
|
||||||
{
|
{
|
||||||
|
public const char ReservedMountNamePrefixCharacter = '@';
|
||||||
|
|
||||||
public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc");
|
public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc");
|
||||||
public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent");
|
public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent");
|
||||||
public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent");
|
public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent");
|
||||||
|
|
|
@ -150,6 +150,30 @@ namespace LibHac.Fs
|
||||||
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (result.IsFailure())
|
||||||
|
{
|
||||||
|
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (result.IsFailure())
|
||||||
|
{
|
||||||
|
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (result.IsFailure())
|
||||||
|
{
|
||||||
|
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
|
internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
|
||||||
string message, [CallerMemberName] string caller = "")
|
string message, [CallerMemberName] string caller = "")
|
||||||
{
|
{
|
||||||
|
@ -208,6 +232,27 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Result RunOperationWithAccessLogOnFailure(AccessLogTarget logTarget, Func<Result> operation,
|
||||||
|
Func<string> textGenerator, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
Result rc;
|
||||||
|
|
||||||
|
if (IsEnabledAccessLog(logTarget))
|
||||||
|
{
|
||||||
|
TimeSpan startTime = Time.GetCurrent();
|
||||||
|
rc = operation();
|
||||||
|
TimeSpan endTime = Time.GetCurrent();
|
||||||
|
|
||||||
|
OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, textGenerator(), caller);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rc = operation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
|
|
|
@ -187,4 +187,10 @@ namespace LibHac.Fs
|
||||||
Nand = 1,
|
Nand = 1,
|
||||||
SdCard = 2
|
SdCard = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MountHostOption
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
PseudoCaseSensitive = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
|
using static LibHac.Fs.CommonMountNames;
|
||||||
|
|
||||||
namespace LibHac.Fs
|
namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
|
@ -23,6 +24,11 @@ namespace LibHac.Fs
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsReservedMountName(U8Span name)
|
||||||
|
{
|
||||||
|
return (uint)name.Length > 0 && name[0] == ReservedMountNamePrefixCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
// ReSharper disable once UnusedParameter.Local
|
// ReSharper disable once UnusedParameter.Local
|
||||||
private static bool CheckMountNameImpl(U8Span name)
|
private static bool CheckMountNameImpl(U8Span name)
|
||||||
{
|
{
|
||||||
|
|
304
src/LibHac/Fs/Shim/Host.cs
Normal file
304
src/LibHac/Fs/Shim/Host.cs
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.FsService;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using static LibHac.Fs.CommonMountNames;
|
||||||
|
|
||||||
|
namespace LibHac.Fs.Shim
|
||||||
|
{
|
||||||
|
public static class Host
|
||||||
|
{
|
||||||
|
private static ReadOnlySpan<byte> HostRootFileSystemPath => new[]
|
||||||
|
{(byte) '@', (byte) 'H', (byte) 'o', (byte) 's', (byte) 't', (byte) ':', (byte) '/'};
|
||||||
|
|
||||||
|
private const int HostRootFileSystemPathLength = 8;
|
||||||
|
|
||||||
|
private class HostCommonMountNameGenerator : ICommonMountNameGenerator
|
||||||
|
{
|
||||||
|
private FsPath _path;
|
||||||
|
|
||||||
|
public HostCommonMountNameGenerator(U8Span path)
|
||||||
|
{
|
||||||
|
StringUtils.Copy(_path.Str, path);
|
||||||
|
|
||||||
|
int pathLength = StringUtils.GetLength(_path.Str);
|
||||||
|
if (pathLength != 0 && _path.Str[pathLength - 1] == StringTraits.DirectorySeparator)
|
||||||
|
{
|
||||||
|
_path.Str[pathLength - 1] = StringTraits.NullTerminator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||||
|
{
|
||||||
|
int requiredNameBufferSize = StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength;
|
||||||
|
|
||||||
|
if (nameBuffer.Length < requiredNameBufferSize)
|
||||||
|
return ResultFs.TooLongPath.Log();
|
||||||
|
|
||||||
|
int size = new U8StringBuilder(nameBuffer).Append(HostRootFileSystemPath).Append(_path.Str).Length;
|
||||||
|
Debug.Assert(size == requiredNameBufferSize - 1);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator
|
||||||
|
{
|
||||||
|
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||||
|
{
|
||||||
|
const int requiredNameBufferSize = HostRootFileSystemPathLength;
|
||||||
|
|
||||||
|
Debug.Assert(nameBuffer.Length >= requiredNameBufferSize);
|
||||||
|
|
||||||
|
int size = StringUtils.Copy(nameBuffer, HostRootFileSystemPath);
|
||||||
|
Debug.Assert(size == requiredNameBufferSize - 1);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result MountHostRoot(this FileSystemClient fs)
|
||||||
|
{
|
||||||
|
IFileSystem hostFileSystem = default;
|
||||||
|
var path = new FsPath();
|
||||||
|
path.Str[0] = 0;
|
||||||
|
|
||||||
|
static string LogMessageGenerator() => $", name: \"{HostRootFileSystemMountName.ToString()}\"";
|
||||||
|
|
||||||
|
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, MountHostOption.None);
|
||||||
|
|
||||||
|
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
|
||||||
|
new HostRootCommonMountNameGenerator());
|
||||||
|
|
||||||
|
// Open the host file system
|
||||||
|
Result result =
|
||||||
|
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
|
||||||
|
if (result.IsFailure()) return result;
|
||||||
|
|
||||||
|
// Mount the host file system
|
||||||
|
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
|
||||||
|
if (result.IsFailure()) return result;
|
||||||
|
|
||||||
|
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||||
|
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option)
|
||||||
|
{
|
||||||
|
IFileSystem hostFileSystem = default;
|
||||||
|
var path = new FsPath();
|
||||||
|
path.Str[0] = 0;
|
||||||
|
|
||||||
|
string LogMessageGenerator() =>
|
||||||
|
$", name: \"{HostRootFileSystemMountName.ToString()}, mount_host_option: {option}\"";
|
||||||
|
|
||||||
|
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, option);
|
||||||
|
|
||||||
|
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
|
||||||
|
new HostRootCommonMountNameGenerator());
|
||||||
|
|
||||||
|
// Open the host file system
|
||||||
|
Result result =
|
||||||
|
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
|
||||||
|
if (result.IsFailure()) return result;
|
||||||
|
|
||||||
|
// Mount the host file system
|
||||||
|
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
|
||||||
|
if (result.IsFailure()) return result;
|
||||||
|
|
||||||
|
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||||
|
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnmountHostRoot(this FileSystemClient fs)
|
||||||
|
{
|
||||||
|
fs.Unmount(HostRootFileSystemMountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path)
|
||||||
|
{
|
||||||
|
return MountHostImpl(fs, mountName, path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option)
|
||||||
|
{
|
||||||
|
return MountHostImpl(fs, mountName, path, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result MountHostImpl(this FileSystemClient fs, U8Span mountName, U8Span path,
|
||||||
|
MountHostOption? optionalOption, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
Result rc;
|
||||||
|
ICommonMountNameGenerator nameGenerator;
|
||||||
|
|
||||||
|
string logMessage = null;
|
||||||
|
var option = MountHostOption.None;
|
||||||
|
|
||||||
|
// Set the mount option if it was specified
|
||||||
|
if (optionalOption.HasValue)
|
||||||
|
{
|
||||||
|
option = optionalOption.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||||
|
{
|
||||||
|
if (optionalOption.HasValue)
|
||||||
|
{
|
||||||
|
logMessage = $", name: \"{mountName.ToString()}\", mount_host_option: {option}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logMessage = $", name: \"{mountName.ToString()}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan startTime = fs.Time.GetCurrent();
|
||||||
|
rc = PreMountHost(out nameGenerator, mountName, path);
|
||||||
|
TimeSpan endTime = fs.Time.GetCurrent();
|
||||||
|
|
||||||
|
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rc = PreMountHost(out nameGenerator, mountName, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
IFileSystem hostFileSystem;
|
||||||
|
|
||||||
|
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||||
|
{
|
||||||
|
TimeSpan startTime = fs.Time.GetCurrent();
|
||||||
|
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
|
||||||
|
TimeSpan endTime = fs.Time.GetCurrent();
|
||||||
|
|
||||||
|
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||||
|
{
|
||||||
|
TimeSpan startTime = fs.Time.GetCurrent();
|
||||||
|
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
|
||||||
|
TimeSpan endTime = fs.Time.GetCurrent();
|
||||||
|
|
||||||
|
fs.OutputAccessLog(rc, startTime, endTime, logMessage, caller);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||||
|
{
|
||||||
|
fs.EnableFileSystemAccessorAccessLog(mountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result PreMountHost(out ICommonMountNameGenerator nameGenerator, U8Span mountName, U8Span path)
|
||||||
|
{
|
||||||
|
nameGenerator = default;
|
||||||
|
|
||||||
|
Result rc = MountHelpers.CheckMountName(mountName);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
|
if (path.IsNull())
|
||||||
|
return ResultFs.NullptrArgument.Log();
|
||||||
|
|
||||||
|
nameGenerator = new HostCommonMountNameGenerator(path);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result OpenHostFileSystem(FileSystemClient fs, out IFileSystem fileSystem, U8Span mountName,
|
||||||
|
U8Span path, MountHostOption option)
|
||||||
|
{
|
||||||
|
fileSystem = default;
|
||||||
|
|
||||||
|
if (mountName.IsNull())
|
||||||
|
return ResultFs.NullptrArgument.Log();
|
||||||
|
|
||||||
|
if (path.IsNull())
|
||||||
|
return ResultFs.NullptrArgument.Log();
|
||||||
|
|
||||||
|
if (PathUtility.IsWindowsDrive(mountName))
|
||||||
|
return ResultFs.InvalidMountName.Log();
|
||||||
|
|
||||||
|
if (MountHelpers.IsReservedMountName(mountName))
|
||||||
|
return ResultFs.InvalidMountName.Log();
|
||||||
|
|
||||||
|
bool needsTrailingSeparator = false;
|
||||||
|
int pathLength = StringUtils.GetLength(path, PathTools.MaxPathLength + 1);
|
||||||
|
|
||||||
|
if (pathLength != 0 && PathTool.IsSeparator(path[pathLength - 1]))
|
||||||
|
{
|
||||||
|
needsTrailingSeparator = true;
|
||||||
|
pathLength++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathLength + 1 > PathTools.MaxPathLength)
|
||||||
|
return ResultFs.TooLongPath.Log();
|
||||||
|
|
||||||
|
FsPath fullPath;
|
||||||
|
unsafe { _ = &fullPath; } // workaround for CS0165
|
||||||
|
|
||||||
|
var sb = new U8StringBuilder(fullPath.Str);
|
||||||
|
sb.Append(StringTraits.DirectorySeparator).Append(path);
|
||||||
|
|
||||||
|
if (needsTrailingSeparator)
|
||||||
|
{
|
||||||
|
sb.Append(StringTraits.DirectorySeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.Overflowed)
|
||||||
|
return ResultFs.TooLongPath.Log();
|
||||||
|
|
||||||
|
// If the input path begins with "//", change any leading '/' characters to '\'
|
||||||
|
if (PathTool.IsSeparator(fullPath.Str[1]) && PathTool.IsSeparator(fullPath.Str[2]))
|
||||||
|
{
|
||||||
|
for (int i = 1; PathTool.IsSeparator(fullPath.Str[i]); i++)
|
||||||
|
{
|
||||||
|
fullPath.Str[i] = StringTraits.AltDirectorySeparator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OpenHostFileSystemImpl(fs, out fileSystem, ref fullPath, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, ref FsPath path, MountHostOption option)
|
||||||
|
{
|
||||||
|
fileSystem = default;
|
||||||
|
|
||||||
|
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||||
|
IFileSystem hostFs;
|
||||||
|
|
||||||
|
if (option == MountHostOption.None)
|
||||||
|
{
|
||||||
|
Result rc = fsProxy.OpenHostFileSystem(out hostFs, ref path);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Result rc = fsProxy.OpenHostFileSystemWithOption(out hostFs, ref path, option);
|
||||||
|
if (rc.IsFailure()) return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSystem = hostFs;
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ namespace LibHac.FsService
|
||||||
|
|
||||||
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
|
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
|
||||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||||
|
|
||||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||||
return FsProxyCore.OpenFileSystem(out fileSystem, normalizer.Path, type, canMountSystemDataPrivate, titleId);
|
return FsProxyCore.OpenFileSystem(out fileSystem, normalizer.Path, type, canMountSystemDataPrivate, titleId);
|
||||||
}
|
}
|
||||||
|
@ -682,7 +682,12 @@ namespace LibHac.FsService
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath)
|
public Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ namespace LibHac.FsService
|
||||||
Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId);
|
Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId);
|
||||||
Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId);
|
Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId);
|
||||||
Result InvalidateBisCache();
|
Result InvalidateBisCache();
|
||||||
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath);
|
Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option);
|
||||||
|
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path);
|
||||||
Result OpenSdCardFileSystem(out IFileSystem fileSystem);
|
Result OpenSdCardFileSystem(out IFileSystem fileSystem);
|
||||||
Result FormatSdCardFileSystem();
|
Result FormatSdCardFileSystem();
|
||||||
Result DeleteSaveDataFileSystem(ulong saveDataId);
|
Result DeleteSaveDataFileSystem(ulong saveDataId);
|
||||||
|
|
Loading…
Reference in a new issue