Add U8String class and some FileSystemClient functions

This commit is contained in:
Alex Barney 2019-09-16 16:42:32 -05:00
parent 2f49021405
commit 4bbfa057d2
23 changed files with 455 additions and 31 deletions

View file

@ -65,21 +65,19 @@ namespace LibHac.Common
return iDest;
}
public static string FromUtf8Z(this Span<byte> value) => FromUtf8Z((ReadOnlySpan<byte>)value);
public static string FromUtf8Z(this ReadOnlySpan<byte> value)
public static string Utf8ToString(ReadOnlySpan<byte> value)
{
int i;
for (i = 0; i < value.Length && value[i] != 0; i++) { }
value = value.Slice(0, i);
#if STRING_SPAN
return Encoding.UTF8.GetString(value);
#else
return Encoding.UTF8.GetString(value.ToArray());
#endif
}
public static string Utf8ZToString(ReadOnlySpan<byte> value)
{
return Utf8ToString(value.Slice(0, GetLength(value)));
}
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Diagnostics;
using System.Text;
namespace LibHac.Common
{
[DebuggerDisplay("{ToString()}")]
public ref struct U8Span
{
private readonly ReadOnlySpan<byte> _buffer;
public ReadOnlySpan<byte> Value => _buffer;
public int Length => _buffer.Length;
public byte this[int i]
{
get => _buffer[i];
}
public U8Span(ReadOnlySpan<byte> value)
{
_buffer = value;
}
public U8Span(string value)
{
_buffer = Encoding.UTF8.GetBytes(value);
}
public static implicit operator ReadOnlySpan<byte>(U8Span value) => value.Value;
public static explicit operator string(U8Span value) => value.ToString();
public static explicit operator U8Span(string value) => new U8Span(value);
public override string ToString()
{
return StringUtils.Utf8ToString(_buffer);
}
public bool IsNull() => _buffer == default;
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Diagnostics;
using System.Text;
namespace LibHac.Common
{
[DebuggerDisplay("{ToString()}")]
public ref struct U8SpanMutable
{
private readonly Span<byte> _buffer;
public Span<byte> Value => _buffer;
public int Length => _buffer.Length;
public byte this[int i]
{
get => _buffer[i];
set => _buffer[i] = value;
}
public U8SpanMutable(Span<byte> value)
{
_buffer = value;
}
public U8SpanMutable(string value)
{
_buffer = Encoding.UTF8.GetBytes(value);
}
public static implicit operator U8Span(U8SpanMutable value) => new U8Span(value._buffer);
public static implicit operator ReadOnlySpan<byte>(U8SpanMutable value) => value.Value;
public static implicit operator Span<byte>(U8SpanMutable value) => value.Value;
public static explicit operator string(U8SpanMutable value) => value.ToString();
public static explicit operator U8SpanMutable(string value) => new U8SpanMutable(value);
public override string ToString()
{
return StringUtils.Utf8ZToString(_buffer);
}
public bool IsNull() => _buffer == default;
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Diagnostics;
using System.Text;
namespace LibHac.Common
{
[DebuggerDisplay("{ToString()}")]
public struct U8String
{
private readonly byte[] _buffer;
public ReadOnlySpan<byte> Value => _buffer;
public int Length => _buffer.Length;
public byte this[int i] => _buffer[i];
public U8String(byte[] value)
{
_buffer = value;
}
public U8String(string value)
{
_buffer = Encoding.UTF8.GetBytes(value);
}
public static implicit operator U8Span(U8String value) => new U8Span(value._buffer);
public static implicit operator ReadOnlySpan<byte>(U8String value) => value.Value;
public static explicit operator string(U8String value) => value.ToString();
public static explicit operator U8String(string value) => new U8String(value);
public override string ToString()
{
return StringUtils.Utf8ToString(_buffer);
}
public bool IsNull() => _buffer == null;
}
}

View file

@ -0,0 +1,15 @@
namespace LibHac.Common
{
public static class U8StringHelpers
{
public static U8String AsU8String(this string value)
{
return new U8String(value);
}
public static U8Span AsU8Span(this string value)
{
return new U8Span(value);
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Diagnostics;
using System.Text;
namespace LibHac.Common
{
[DebuggerDisplay("{ToString()}")]
public struct U8StringMutable
{
private readonly byte[] _buffer;
public Span<byte> Value => _buffer;
public int Length => _buffer.Length;
public byte this[int i]
{
get => _buffer[i];
set => _buffer[i] = value;
}
public U8StringMutable(byte[] value)
{
_buffer = value;
}
public U8StringMutable(string value)
{
_buffer = Encoding.UTF8.GetBytes(value);
}
public static implicit operator U8String(U8StringMutable value) => new U8String(value._buffer);
public static implicit operator U8SpanMutable(U8StringMutable value) => new U8SpanMutable(value._buffer);
public static implicit operator U8Span(U8StringMutable value) => new U8Span(value._buffer);
public static implicit operator ReadOnlySpan<byte>(U8StringMutable value) => value.Value;
public static implicit operator Span<byte>(U8StringMutable value) => value.Value;
public static explicit operator string(U8StringMutable value) => value.ToString();
public static explicit operator U8StringMutable(string value) => new U8StringMutable(value);
public override string ToString()
{
return StringUtils.Utf8ZToString(_buffer);
}
public bool IsNull() => _buffer == null;
}
}

View file

@ -39,7 +39,7 @@ namespace LibHac.Fs
try
{
Result rc = sourceFs.OpenFile(out srcFile, sourcePath.FromUtf8Z(), OpenMode.Read);
Result rc = sourceFs.OpenFile(out srcFile, StringUtils.Utf8ZToString(sourcePath), OpenMode.Read);
if (rc.IsFailure()) return rc;
FsPath dstPath = default;
@ -51,7 +51,7 @@ namespace LibHac.Fs
throw new ArgumentException();
}
string dstPathStr = dstPath.Str.FromUtf8Z();
string dstPathStr = StringUtils.Utf8ZToString(dstPath.Str);
rc = destFs.CreateFile(dstPathStr, dirEntry.Size, CreateFileOptions.None);
if (rc.IsFailure()) return rc;

View file

@ -82,7 +82,7 @@ namespace LibHac.Fs
fileSystem.OpenDirectory(out IDirectory directory, path, OpenDirectoryMode.All).ThrowIfFailure();
while(true)
while (true)
{
directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)).ThrowIfFailure();
if (entriesRead == 0) break;
@ -109,7 +109,7 @@ namespace LibHac.Fs
internal static DirectoryEntryEx GetDirectoryEntryEx(ref DirectoryEntry entry, string parentPath)
{
string name = entry.Name.FromUtf8Z();
string name = StringUtils.Utf8ZToString(entry.Name);
string path = PathTools.Combine(parentPath, name);
var entryEx = new DirectoryEntryEx(name, path, entry.Type, entry.Size);

View file

@ -53,4 +53,10 @@
ProperSystem = 100,
Safe = 101
}
public enum CustomStorageId
{
User = 0,
SdCard = 1
}
}

View file

@ -11,6 +11,7 @@ namespace LibHac.FsClient.Accessors
private IFileSystem FileSystem { get; }
internal FileSystemManager FsManager { get; }
private ICommonMountNameGenerator MountNameGenerator { get; }
private HashSet<FileAccessor> OpenFiles { get; } = new HashSet<FileAccessor>();
private HashSet<DirectoryAccessor> OpenDirectories { get; } = new HashSet<DirectoryAccessor>();
@ -19,11 +20,12 @@ namespace LibHac.FsClient.Accessors
internal bool IsAccessLogEnabled { get; set; }
public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemManager fsManager)
public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemManager fsManager, ICommonMountNameGenerator nameGenerator)
{
Name = name;
FileSystem = baseFileSystem;
FsManager = fsManager;
MountNameGenerator = nameGenerator;
}
public Result CreateDirectory(string path)
@ -147,6 +149,13 @@ namespace LibHac.FsClient.Accessors
return FileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
}
public Result GetCommonMountName(Span<byte> nameBuffer)
{
if (MountNameGenerator == null) return ResultFs.PreconditionViolation;
return MountNameGenerator.Generate(nameBuffer);
}
internal void NotifyCloseFile(FileAccessor file)
{
lock (_locker)

View file

@ -0,0 +1,70 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsService;
namespace LibHac.FsClient
{
public static class ContentStorage
{
private static readonly U8String ContentStorageMountNameSystem = new U8String("@SystemContent");
private static readonly U8String ContentStorageMountNameUser = new U8String("@UserContent");
private static readonly U8String ContentStorageMountNameSdCard = new U8String("@SdCardContent");
public static Result MountContentStorage(this FileSystemClient fs, ContentStorageId storageId)
{
return MountContentStorage(fs, GetContentStorageMountName(storageId), storageId);
}
public static Result MountContentStorage(this FileSystemClient fs, U8Span mountName, ContentStorageId storageId)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
FileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId);
if (rc.IsFailure()) return rc;
var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId);
return fs.Register(mountName, contentFs, mountNameGenerator);
}
public static U8String GetContentStorageMountName(ContentStorageId storageId)
{
switch (storageId)
{
case ContentStorageId.System:
return ContentStorageMountNameSystem;
case ContentStorageId.User:
return ContentStorageMountNameUser;
case ContentStorageId.SdCard:
return ContentStorageMountNameSdCard;
default:
throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null);
}
}
private class ContentStorageCommonMountNameGenerator : ICommonMountNameGenerator
{
private ContentStorageId StorageId { get; }
public ContentStorageCommonMountNameGenerator(ContentStorageId storageId)
{
StorageId = storageId;
}
public Result Generate(Span<byte> nameBuffer)
{
U8String mountName = GetContentStorageMountName(StorageId);
int length = StringUtils.Copy(nameBuffer, mountName);
nameBuffer[length] = (byte)':';
nameBuffer[length + 1] = 0;
return Result.Success;
}
}
}
}

View file

@ -0,0 +1,22 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsService;
namespace LibHac.FsClient
{
public partial class FileSystemClient
{
public Result MountCustomStorage(U8Span mountName, CustomStorageId storageId)
{
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
FileSystemProxy fsProxy = GetFileSystemProxyServiceObject();
rc = fsProxy.OpenCustomStorageFileSystem(out IFileSystem customFs, storageId);
if (rc.IsFailure()) return rc;
return FsManager.Register(mountName, customFs);
}
}
}

View file

@ -1,4 +1,6 @@
using LibHac.FsService;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsService;
namespace LibHac.FsClient
{
@ -16,7 +18,7 @@ namespace LibHac.FsClient
FsManager = new FileSystemManager(timer);
}
private FileSystemProxy GetFileSystemProxyServiceObject()
public FileSystemProxy GetFileSystemProxyServiceObject()
{
if (FsProxy != null) return FsProxy;
@ -29,5 +31,15 @@ namespace LibHac.FsClient
return FsProxy;
}
}
public Result Register(U8Span mountName, IFileSystem fileSystem)
{
return FsManager.Register(mountName, fileSystem);
}
public Result Register(U8Span mountName, IFileSystem fileSystem, ICommonMountNameGenerator nameGenerator)
{
return FsManager.Register(mountName, fileSystem, nameGenerator);
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsClient.Accessors;
@ -32,13 +33,20 @@ namespace LibHac.FsClient
Time = timer;
}
public void Register(string mountName, IFileSystem fileSystem)
public Result Register(U8Span mountName, IFileSystem fileSystem)
{
var accessor = new FileSystemAccessor(mountName, fileSystem, this);
return Register(mountName, fileSystem, null);
}
MountTable.Mount(accessor).ThrowIfFailure();
public Result Register(U8Span mountName, IFileSystem fileSystem, ICommonMountNameGenerator nameGenerator)
{
var accessor = new FileSystemAccessor(mountName.ToString(), fileSystem, this, nameGenerator);
Result rc = MountTable.Mount(accessor);
if (rc.IsFailure()) return rc;
accessor.IsAccessLogEnabled = IsEnabledAccessLog();
return Result.Success;
}
public void Unmount(string mountName)

View file

@ -131,12 +131,10 @@ namespace LibHac.FsClient
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
IEnumerable<DirectoryEntryEx> subEntries =
fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern,
searchOptions);
fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern, searchOptions);
foreach (DirectoryEntryEx subEntry in subEntries)
{
subEntry.FullPath = PathTools.Combine(path, subEntry.Name);
yield return subEntry;
}
}

View file

@ -0,0 +1,9 @@
using System;
namespace LibHac.FsClient
{
public interface ICommonMountNameGenerator
{
Result Generate(Span<byte> nameBuffer);
}
}

View file

@ -0,0 +1,33 @@
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.FsClient
{
internal static class MountHelpers
{
public static Result CheckMountName(U8Span name)
{
if (name.IsNull()) return ResultFs.NullArgument.Log();
if (name.Length > 0 && name[0] == '@') return ResultFs.InvalidMountName.Log();
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();
return Result.Success;
}
public static Result CheckMountNameAcceptingReservedMountName(U8Span name)
{
if (name.IsNull()) return ResultFs.NullArgument.Log();
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();
return Result.Success;
}
private static bool CheckMountNameImpl(U8Span name)
{
// Todo
return true;
}
}
}

View file

@ -95,6 +95,13 @@ namespace LibHac.FsService
return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId);
}
public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId)
{
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId);
}
public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId,
SaveDataAttribute attribute)
{

View file

@ -75,6 +75,50 @@ namespace LibHac.FsService
EncryptedFsKeyId.Content, SdEncryptionSeed);
}
public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId)
{
fileSystem = default;
switch (storageId)
{
case CustomStorageId.SdCard:
{
Result rc = FsCreators.SdFileSystemCreator.Create(out IFileSystem sdFs);
if (rc.IsFailure()) return rc;
string customStorageDir = Util.GetCustomStorageDirectoryName(CustomStorageId.SdCard);
string subDirName = $"/{NintendoDirectoryName}/{customStorageDir}";
rc = Util.CreateSubFileSystem(out IFileSystem subFs, sdFs, subDirName, true);
if (rc.IsFailure()) return rc;
rc = FsCreators.EncryptedFileSystemCreator.Create(out IFileSystem encryptedFs, subFs,
EncryptedFsKeyId.CustomStorage, SdEncryptionSeed);
if (rc.IsFailure()) return rc;
fileSystem = encryptedFs;
return Result.Success;
}
case CustomStorageId.User:
{
Result rc = FsCreators.BuiltInStorageFileSystemCreator.Create(out IFileSystem userFs, string.Empty,
BisPartitionId.User);
if (rc.IsFailure()) return rc;
string customStorageDir = Util.GetCustomStorageDirectoryName(CustomStorageId.User);
string subDirName = $"/{customStorageDir}";
rc = Util.CreateSubFileSystem(out IFileSystem subFs, userFs, subDirName, true);
if (rc.IsFailure()) return rc;
fileSystem = subFs;
return Result.Success;
}
default:
return ResultFs.InvalidArgument.Log();
}
}
public Result SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed)
{
seed.CopyTo(SdEncryptionSeed);

View file

@ -12,6 +12,7 @@ namespace LibHac.FsService
Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, SaveDataAttribute attribute);
Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, SaveDataAttribute attribute);
Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId);
Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId);
Result SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed);
Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize);
Result SetSaveDataRootPath(string path);

View file

@ -1,4 +1,5 @@
using LibHac.Fs;
using System;
using LibHac.Fs;
namespace LibHac.FsService
{
@ -48,5 +49,17 @@ namespace LibHac.FsService
spaceId == SaveDataSpaceId.ProperSystem ||
spaceId == SaveDataSpaceId.Safe;
}
public static string GetCustomStorageDirectoryName(CustomStorageId storageId)
{
switch (storageId)
{
case CustomStorageId.User:
case CustomStorageId.SdCard:
return "CustomStorage0";
default:
throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null);
}
}
}
}

View file

@ -1,6 +1,7 @@
using System.IO;
using System.Text;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.NcaUtils;
using LibHac.FsClient;
@ -47,8 +48,8 @@ namespace hactoolnet
string mountName = $"section{i}";
fs.Register(mountName, OpenFileSystem(i));
fs.Register("output", new LocalFileSystem(ctx.Options.SectionOutDir[i]));
fs.Register(mountName.AsU8Span(), OpenFileSystem(i));
fs.Register("output".AsU8Span(), new LocalFileSystem(ctx.Options.SectionOutDir[i]));
FsUtils.CopyDirectoryWithProgress(fs, mountName + ":/", "output:/", logger: ctx.Logger);
@ -96,8 +97,8 @@ namespace hactoolnet
{
FileSystemManager fs = ctx.Horizon.Fs;
fs.Register("rom", OpenFileSystemByType(NcaSectionType.Data));
fs.Register("output", new LocalFileSystem(ctx.Options.RomfsOutDir));
fs.Register("rom".AsU8Span(), OpenFileSystemByType(NcaSectionType.Data));
fs.Register("output".AsU8Span(), new LocalFileSystem(ctx.Options.RomfsOutDir));
FsUtils.CopyDirectoryWithProgress(fs, "rom:/", "output:/", logger: ctx.Logger);
@ -153,8 +154,8 @@ namespace hactoolnet
{
FileSystemManager fs = ctx.Horizon.Fs;
fs.Register("code", OpenFileSystemByType(NcaSectionType.Code));
fs.Register("output", new LocalFileSystem(ctx.Options.ExefsOutDir));
fs.Register("code".AsU8Span(), OpenFileSystemByType(NcaSectionType.Code));
fs.Register("output".AsU8Span(), new LocalFileSystem(ctx.Options.ExefsOutDir));
FsUtils.CopyDirectoryWithProgress(fs, "code:/", "output:/", logger: ctx.Logger);

View file

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Save;
using LibHac.FsClient;
@ -30,7 +31,7 @@ namespace hactoolnet
var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true);
FileSystemManager fs = ctx.Horizon.Fs;
fs.Register("save", save);
fs.Register("save".AsU8Span(), save);
if (ctx.Options.Validate)
{
@ -39,7 +40,7 @@ namespace hactoolnet
if (ctx.Options.OutDir != null)
{
fs.Register("output", new LocalFileSystem(ctx.Options.OutDir));
fs.Register("output".AsU8Span(), new LocalFileSystem(ctx.Options.OutDir));
FsUtils.CopyDirectoryWithProgress(fs, "save:/", "output:/", logger: ctx.Logger);
@ -85,7 +86,7 @@ namespace hactoolnet
if (ctx.Options.RepackSource != null)
{
fs.Register("input", new LocalFileSystem(ctx.Options.RepackSource));
fs.Register("input".AsU8Span(), new LocalFileSystem(ctx.Options.RepackSource));
fs.CleanDirectoryRecursively("save:/");
fs.Commit("save");