Implement DeliveryCacheFileService and supporting code

This commit is contained in:
Alex Barney 2020-04-10 10:54:37 -07:00
parent 939c495db6
commit ef36568a8d
13 changed files with 727 additions and 52 deletions

View file

@ -279,15 +279,20 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
122,1,,InvalidArgument,
122,2,,NotFound,
122,3,,TargetLocked,
122,6,,AlreadyOpen,
122,7,,NotOpen,
122,9,,ServiceOpenLimitReached,
122,10,,SaveDataNotFount,
122,10,,SaveDataNotFound,
122,31,,NetworkServiceAccountNotAvailable,
122,90,,PermissionDenied,
122,80,,PassphrasePathNotFound,
122,204,,InvalidStorageMetaVersion,
122,90,,PermissionDenied,
122,91,,AllocationFailed,
122,204,,InvalidDeliveryCacheStorageFile,
122,205,,StorageOpenLimitReached,
123,0,4999,SslService,

1 Module,DescriptionStart,DescriptionEnd,Name,Summary
279 428,1005,,KipSegmentDecompressionFailed,An error occurred while decompressing a KIP segment.
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

View file

@ -1,6 +1,7 @@
using System.Diagnostics;
using LibHac.Bcat.Detail.Ipc;
using LibHac.Bcat.Detail.Service;
using LibHac.Bcat.Detail.Service.Core;
using LibHac.Common;
using LibHac.Fs;
@ -67,7 +68,7 @@ namespace LibHac.Bcat
return StorageManager;
}
StorageManager = new DeliveryCacheStorageManager();
StorageManager = new DeliveryCacheStorageManager(this);
return StorageManager;
}
}

View file

@ -0,0 +1,103 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.Bcat.Detail.Service.Core
{
internal class DeliveryCacheFileMetaAccessor
{
private const int MaxEntryCount = 100;
private const int MetaFileHeaderValue = 1;
private BcatServer Server { get; }
private object Locker { get; } = new object();
private DeliveryCacheFileMetaEntry[] Entries { get; } = new DeliveryCacheFileMetaEntry[MaxEntryCount];
private int Count { get; set; }
public DeliveryCacheFileMetaAccessor(BcatServer server)
{
Server = server;
}
public Result ReadApplicationFileMeta(ulong applicationId, ref DirectoryName directoryName,
bool allowMissingMetaFile)
{
Span<byte> metaPath = stackalloc byte[0x50];
Server.GetStorageManager().GetFilesMetaPath(metaPath, applicationId, ref directoryName);
return Read(new U8Span(metaPath), allowMissingMetaFile);
}
public Result FindEntry(out DeliveryCacheFileMetaEntry entry, ref FileName fileName)
{
lock (Locker)
{
for (int i = 0; i < Count; i++)
{
if (StringUtils.CompareCaseInsensitive(Entries[i].Name.Bytes, fileName.Bytes) == 0)
{
entry = Entries[i];
return Result.Success;
}
}
entry = default;
return ResultBcat.NotFound.Log();
}
}
private Result Read(U8Span path, bool allowMissingMetaFile)
{
lock (Locker)
{
FileSystemClient fs = Server.GetFsClient();
Result rc = fs.OpenFile(out FileHandle handle, path, OpenMode.Read);
if (rc.IsFailure())
{
if (ResultFs.PathNotFound.Includes(rc))
{
if (allowMissingMetaFile)
{
Count = 0;
return Result.Success;
}
return ResultBcat.NotFound.LogConverted(rc);
}
return rc;
}
try
{
Count = 0;
int header = 0;
// Verify the header value
rc = fs.ReadFile(out long bytesRead, handle, 0, SpanHelpers.AsByteSpan(ref header));
if (rc.IsFailure()) return rc;
if (bytesRead != sizeof(int) || header != MetaFileHeaderValue)
return ResultBcat.InvalidDeliveryCacheStorageFile.Log();
// Read all the file entries
Span<byte> buffer = MemoryMarshal.Cast<DeliveryCacheFileMetaEntry, byte>(Entries);
rc = fs.ReadFile(out bytesRead, handle, 4, buffer);
if (rc.IsFailure()) return rc;
Count = (int)((uint)bytesRead / Unsafe.SizeOf<DeliveryCacheFileMetaEntry>());
return Result.Success;
}
finally
{
fs.CloseFile(handle);
}
}
}
}
}

View file

@ -1,9 +1,9 @@
using System.Runtime.InteropServices;
namespace LibHac.Bcat.Detail.Service
namespace LibHac.Bcat.Detail.Service.Core
{
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
internal struct DeliveryCacheFileEntryMeta
internal struct DeliveryCacheFileMetaEntry
{
[FieldOffset(0x00)] public FileName Name;
[FieldOffset(0x20)] public long Size;

View file

@ -0,0 +1,386 @@
using System;
using System.Diagnostics;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Ncm;
using static LibHac.Fs.StringTraits;
namespace LibHac.Bcat.Detail.Service.Core
{
internal class DeliveryCacheStorageManager
{
private const int MaxEntryCount = 4;
private BcatServer Server { get; }
private readonly object _locker = new object();
private Entry[] Entries { get; } = new Entry[MaxEntryCount];
private bool DisableStorage { get; set; }
private struct Entry
{
public ulong ApplicationId { get; set; }
public long RefCount { get; set; }
}
public DeliveryCacheStorageManager(BcatServer server)
{
Server = server;
DisableStorage = false;
}
public Result Open(ulong applicationId)
{
lock (_locker)
{
// Find an existing storage entry for this application ID or get an empty one
Result rc = FindOrGetUnusedEntry(out int index, applicationId);
if (rc.IsFailure()) return rc;
ref Entry entry = ref Entries[index];
if (entry.RefCount != 0)
{
return ResultBcat.TargetLocked.Log();
}
// Get the mount name
var mountName = new MountName();
new U8StringBuilder(mountName.Name)
.Append(DeliveryCacheMountNamePrefix)
.AppendFormat(index, 'd', 2);
// Mount the save if enabled
if (!DisableStorage)
{
rc = Server.GetFsClient()
.MountBcatSaveData(new U8Span(mountName.Name), new TitleId(applicationId));
if (rc.IsFailure())
{
if (ResultFs.TargetNotFound.Includes(rc))
return ResultBcat.SaveDataNotFound.LogConverted(rc);
return rc;
}
}
// Update the storage entry
entry.ApplicationId = applicationId;
entry.RefCount++;
return Result.Success;
}
}
public void Release(ulong applicationId)
{
lock (_locker)
{
int index = FindEntry(applicationId);
ref Entry entry = ref Entries[index];
entry.RefCount--;
// Free the entry if there are no more references
if (entry.RefCount == 0)
{
var mountName = new MountName();
new U8StringBuilder(mountName.Name)
.Append(DeliveryCacheMountNamePrefix)
.AppendFormat(index, 'd', 2);
// Unmount the entry's savedata
if (!DisableStorage)
{
Server.GetFsClient().Unmount(new U8Span(mountName.Name));
}
// Clear the entry
entry.ApplicationId = 0;
// todo: Call nn::bcat::detail::service::core::PassphraseManager::Remove
}
}
}
public void Commit(ulong applicationId)
{
lock (_locker)
{
int index = FindEntry(applicationId);
var mountName = new MountName();
new U8StringBuilder(mountName.Name)
.Append(DeliveryCacheMountNamePrefix)
.AppendFormat(index, 'd', 2);
if (!DisableStorage)
{
Result rc = Server.GetFsClient().Commit(new U8Span(mountName.Name));
if (rc.IsFailure())
{
throw new HorizonResultException(rc, "Abort");
}
}
}
}
public Result GetFreeSpaceSize(out long size, ulong applicationId)
{
lock (_locker)
{
Span<byte> path = stackalloc byte[0x20];
var sb = new U8StringBuilder(path);
AppendMountName(ref sb, applicationId);
sb.Append(RootPath);
Result rc;
if (DisableStorage)
{
size = 0x4400000;
rc = Result.Success;
}
else
{
rc = Server.GetFsClient().GetFreeSpaceSize(out size, new U8Span(path));
}
return rc;
}
}
public void GetPassphrasePath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(PassphrasePath);
}
}
public void GetDeliveryListPath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(DeliveryListPath);
}
}
public void GetEtagFilePath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(EtagPath);
}
}
public void GetNaRequiredPath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(NaRequiredPath);
}
}
public void GetIndexLockPath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(IndexLockPath);
}
}
public void GetFilePath(Span<byte> pathBuffer, ulong applicationId, ref DirectoryName directoryName,
ref FileName fileName)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(DirectoriesPath);
sb.Append(DirectorySeparator).Append(directoryName.Bytes);
sb.Append(DirectorySeparator).Append(FilesDirectoryName);
sb.Append(DirectorySeparator).Append(fileName.Bytes);
}
}
public void GetFilesMetaPath(Span<byte> pathBuffer, ulong applicationId, ref DirectoryName directoryName)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(DirectoriesPath);
sb.Append(DirectorySeparator).Append(directoryName.Bytes);
sb.Append(DirectorySeparator).Append(FilesMetaFileName);
}
}
public void GetDirectoriesPath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(DirectoriesPath);
}
}
public void GetDirectoryPath(Span<byte> pathBuffer, ulong applicationId, ref DirectoryName directoryName)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(DirectoriesPath);
sb.Append(DirectorySeparator).Append(directoryName.Bytes);
}
}
public void GetDirectoriesMetaPath(Span<byte> pathBuffer, ulong applicationId)
{
lock (_locker)
{
var sb = new U8StringBuilder(pathBuffer);
AppendMountName(ref sb, applicationId);
sb.Append(DirectoriesMetaPath);
}
}
private void AppendMountName(ref U8StringBuilder sb, ulong applicationId)
{
int index = FindEntry(applicationId);
sb.Append(DeliveryCacheMountNamePrefix)
.AppendFormat(index, 'd', 2);
}
private Result FindOrGetUnusedEntry(out int entryIndex, ulong applicationId)
{
// Try to find an existing entry
for (int i = 0; i < Entries.Length; i++)
{
if (Entries[i].ApplicationId == applicationId)
{
entryIndex = i;
return Result.Success;
}
}
// Try to find an unused entry
for (int i = 0; i < Entries.Length; i++)
{
if (Entries[i].ApplicationId == 0)
{
entryIndex = i;
return Result.Success;
}
}
entryIndex = default;
return ResultBcat.StorageOpenLimitReached.Log();
}
private int FindEntry(ulong applicationId)
{
for (int i = 0; i < Entries.Length; i++)
{
if (Entries[i].ApplicationId == applicationId)
{
return i;
}
}
// Nintendo uses 1 as the entry index if it wasn't found
Debug.Assert(false, "Entry not found.");
return 1;
}
private static ReadOnlySpan<byte> DeliveryCacheMountNamePrefix => // bcat-dc-
new[] { (byte)'b', (byte)'c', (byte)'a', (byte)'t', (byte)'-', (byte)'d', (byte)'c', (byte)'-' };
private static ReadOnlySpan<byte> RootPath => // :/
new[] { (byte)':', (byte)'/' };
private static ReadOnlySpan<byte> PassphrasePath => // :/passphrase.bin
new[]
{
(byte) ':', (byte) '/', (byte) 'p', (byte) 'a', (byte) 's', (byte) 's', (byte) 'p', (byte) 'h',
(byte) 'r', (byte) 'a', (byte) 's', (byte) 'e', (byte) '.', (byte) 'b', (byte) 'i', (byte) 'n'
};
private static ReadOnlySpan<byte> DeliveryListPath => // :/list.msgpack
new[]
{
(byte) ':', (byte) '/', (byte) 'l', (byte) 'i', (byte) 's', (byte) 't', (byte) '.', (byte) 'm',
(byte) 's', (byte) 'g', (byte) 'p', (byte) 'a', (byte) 'c', (byte) 'k'
};
private static ReadOnlySpan<byte> EtagPath => // :/etag.bin
new[]
{
(byte) ':', (byte) '/', (byte) 'e', (byte) 't', (byte) 'a', (byte) 'g', (byte) '.', (byte) 'b',
(byte) 'i', (byte) 'n'
};
private static ReadOnlySpan<byte> NaRequiredPath => // :/na_required
new[]
{
(byte) ':', (byte) '/', (byte) 'n', (byte) 'a', (byte) '_', (byte) 'r', (byte) 'e', (byte) 'q',
(byte) 'u', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'd'
};
private static ReadOnlySpan<byte> IndexLockPath => // :/index.lock
new[]
{
(byte) ':', (byte) '/', (byte) 'i', (byte) 'n', (byte) 'd', (byte) 'e', (byte) 'x', (byte) '.',
(byte) 'l', (byte) 'o', (byte) 'c', (byte) 'k'
};
private static ReadOnlySpan<byte> DirectoriesPath => // :/directories
new[]
{
(byte) ':', (byte) '/', (byte) 'd', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'c', (byte) 't',
(byte) 'o', (byte) 'r', (byte) 'i', (byte) 'e', (byte) 's'
};
private static ReadOnlySpan<byte> FilesMetaFileName => // files.meta
new[]
{
(byte) 'f', (byte) 'i', (byte) 'l', (byte) 'e', (byte) 's', (byte) '.', (byte) 'm', (byte) 'e',
(byte) 't', (byte) 'a'
};
private static ReadOnlySpan<byte> DirectoriesMetaPath => // :/directories.meta
new[]
{
(byte) ':', (byte) '/', (byte) 'd', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'c', (byte) 't',
(byte) 'o', (byte) 'r', (byte) 'i', (byte) 'e', (byte) 's', (byte) '.', (byte) 'm', (byte) 'e',
(byte) 't', (byte) 'a'
};
private static ReadOnlySpan<byte> FilesDirectoryName => // files
new[] { (byte)'f', (byte)'i', (byte)'l', (byte)'e', (byte)'s' };
}
}

View file

@ -5,6 +5,7 @@ namespace LibHac.Bcat.Detail.Service
{
internal class DeliveryCacheDirectoryService : IDeliveryCacheDirectoryService
{
private BcatServer Server { get; }
public object Locker { get; } = new object();
private DeliveryCacheStorageService Parent { get; }
private AccessControl Access { get; }
@ -13,9 +14,10 @@ namespace LibHac.Bcat.Detail.Service
private bool IsDirectoryOpen { get; set; }
private int Count { get; set; }
public DeliveryCacheDirectoryService(DeliveryCacheStorageService parent, ulong applicationId,
public DeliveryCacheDirectoryService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId,
AccessControl accessControl)
{
Server = server;
Parent = parent;
ApplicationId = applicationId;
Access = accessControl;

View file

@ -1,22 +1,26 @@
using System;
using LibHac.Bcat.Detail.Ipc;
using LibHac.Bcat.Detail.Service.Core;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.Bcat.Detail.Service
{
internal class DeliveryCacheFileService : IDeliveryCacheFileService
internal class DeliveryCacheFileService : IDeliveryCacheFileService, IDisposable
{
public object Locker { get; } = new object();
private BcatServer Server { get; }
private object Locker { get; } = new object();
private DeliveryCacheStorageService Parent { get; }
private AccessControl Access { get; }
private ulong ApplicationId { get; }
private FileHandle Handle { get; set; }
private DeliveryCacheFileEntryMeta _metaEntry;
private FileHandle _handle;
private DeliveryCacheFileMetaEntry _metaEntry;
private bool IsFileOpen { get; set; }
public DeliveryCacheFileService(DeliveryCacheStorageService parent, ulong applicationId,
public DeliveryCacheFileService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId,
AccessControl accessControl)
{
Server = server;
Parent = parent;
ApplicationId = applicationId;
Access = accessControl;
@ -24,22 +28,91 @@ namespace LibHac.Bcat.Detail.Service
public Result Open(ref DirectoryName directoryName, ref FileName fileName)
{
throw new NotImplementedException();
if (!directoryName.IsValid())
return ResultBcat.InvalidArgument.Log();
if (!fileName.IsValid())
return ResultBcat.InvalidArgument.Log();
lock (Locker)
{
if (IsFileOpen)
return ResultBcat.AlreadyOpen.Log();
var metaReader = new DeliveryCacheFileMetaAccessor(Server);
Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref directoryName, true);
if (rc.IsFailure()) return rc;
rc = metaReader.FindEntry(out DeliveryCacheFileMetaEntry entry, ref fileName);
if (rc.IsFailure()) return rc;
Span<byte> filePath = stackalloc byte[0x80];
Server.GetStorageManager().GetFilePath(filePath, ApplicationId, ref directoryName, ref fileName);
rc = Server.GetFsClient().OpenFile(out _handle, new U8Span(filePath), OpenMode.Read);
if (rc.IsFailure()) return rc;
_metaEntry = entry;
IsFileOpen = true;
return Result.Success;
}
}
public Result Read(out long bytesRead, long offset, Span<byte> destination)
{
throw new NotImplementedException();
lock (Locker)
{
bytesRead = 0;
if (!IsFileOpen)
return ResultBcat.NotOpen.Log();
Result rc = Server.GetFsClient().ReadFile(out long read, _handle, offset, destination);
if (rc.IsFailure()) return rc;
bytesRead = read;
return Result.Success;
}
}
public Result GetSize(out long size)
{
throw new NotImplementedException();
lock (Locker)
{
if (!IsFileOpen)
{
size = default;
return ResultBcat.NotOpen.Log();
}
return Server.GetFsClient().GetFileSize(out size, _handle);
}
}
public Result GetDigest(out Digest digest)
{
throw new NotImplementedException();
lock (Locker)
{
if (!IsFileOpen)
{
digest = default;
return ResultBcat.NotOpen.Log();
}
digest = _metaEntry.Digest;
return Result.Success;
}
}
public void Dispose()
{
if (IsFileOpen)
{
Server.GetFsClient().CloseFile(_handle);
}
Parent.NotifyCloseFile();
}
}
}

View file

@ -1,17 +0,0 @@
namespace LibHac.Bcat.Detail.Service
{
internal class DeliveryCacheStorageManager
{
private const int MaxEntryCount = 4;
private readonly object _locker = new object();
private Entry[] Entries { get; set; } = new Entry[MaxEntryCount];
private bool UseRealStorage { get; set; }
private struct Entry
{
public ulong ApplicationId { get; set; }
public long RefCount { get; set; }
}
}
}

View file

@ -1,35 +1,87 @@
using System;
using System.Diagnostics;
using LibHac.Bcat.Detail.Ipc;
namespace LibHac.Bcat.Detail.Service
{
internal class DeliveryCacheStorageService : IDeliveryCacheStorageService
internal class DeliveryCacheStorageService : IDeliveryCacheStorageService, IDisposable
{
private const int MaxOpenCount = 8;
private BcatServer Server { get; }
public object Locker { get; } = new object();
private AccessControl Access { get; }
private ulong ApplicationId { get; }
private int OpenFileServiceCount { get; set; }
private int OpenDirectoryServiceCount { get; set; }
private int FileServiceOpenCount { get; set; }
private int DirectoryServiceOpenCount { get; set; }
public DeliveryCacheStorageService(ulong applicationId, AccessControl accessControl)
public DeliveryCacheStorageService(BcatServer server, ulong applicationId, AccessControl accessControl)
{
Server = server;
ApplicationId = applicationId;
Access = accessControl;
}
public Result CreateFileService(out IDeliveryCacheFileService fileService)
public Result CreateFileService(out IDeliveryCacheFileService service)
{
throw new NotImplementedException();
}
lock (Locker)
{
service = default;
public Result CreateDirectoryService(out IDeliveryCacheDirectoryService directoryService)
if (FileServiceOpenCount >= MaxOpenCount)
return ResultBcat.ServiceOpenLimitReached.Log();
service = new DeliveryCacheFileService(Server, this, ApplicationId, Access);
FileServiceOpenCount++;
return Result.Success;
}
}
public Result CreateDirectoryService(out IDeliveryCacheDirectoryService service)
{
throw new NotImplementedException();
lock (Locker)
{
service = default;
if (DirectoryServiceOpenCount >= MaxOpenCount)
return ResultBcat.ServiceOpenLimitReached.Log();
service = new DeliveryCacheDirectoryService(Server, this, ApplicationId, Access);
DirectoryServiceOpenCount++;
return Result.Success;
}
}
public Result EnumerateDeliveryCacheDirectory(out int namesRead, Span<DirectoryName> nameBuffer)
{
throw new NotImplementedException();
}
internal void NotifyCloseFile()
{
lock (Locker)
{
FileServiceOpenCount--;
Debug.Assert(FileServiceOpenCount >= 0);
}
}
internal void NotifyCloseDirectory()
{
lock (Locker)
{
DirectoryServiceOpenCount--;
Debug.Assert(DirectoryServiceOpenCount >= 0);
}
}
public void Dispose()
{
Server.GetStorageManager().Release(ApplicationId);
}
}
}

View file

@ -1,18 +1,17 @@
using System;
using LibHac.Bcat.Detail.Ipc;
using LibHac.Bcat.Detail.Ipc;
using LibHac.Ncm;
namespace LibHac.Bcat.Detail.Service
{
internal class ServiceCreator : IServiceCreator
{
private BcatServer Parent { get; }
private BcatServer Server { get; }
private BcatServiceType ServiceType { get; }
private AccessControl AccessControl { get; }
public ServiceCreator(BcatServer parentServer, BcatServiceType type, AccessControl accessControl)
public ServiceCreator(BcatServer server, BcatServiceType type, AccessControl accessControl)
{
Parent = parentServer;
Server = server;
ServiceType = type;
AccessControl = accessControl;
}
@ -31,7 +30,23 @@ namespace LibHac.Bcat.Detail.Service
private Result CreateDeliveryCacheStorageServiceImpl(out IDeliveryCacheStorageService service,
TitleId applicationId)
{
throw new NotImplementedException();
service = default;
Result rc = Server.GetStorageManager().Open(applicationId.Value);
if (rc.IsFailure()) return rc;
try
{
// todo: Check if network account required
service = new DeliveryCacheStorageService(Server, applicationId.Value, AccessControl);
}
finally
{
Server.GetStorageManager().Release(applicationId.Value);
}
return Result.Success;
}
}
}

View file

@ -6,9 +6,11 @@ using LibHac.Common;
namespace LibHac.Bcat
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 32)]
[StructLayout(LayoutKind.Sequential, Size = MaxSize)]
public struct DirectoryName
{
private const int MaxSize = 0x20;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2;
@ -22,6 +24,26 @@ namespace LibHac.Bcat
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
public bool IsValid()
{
Span<byte> name = Bytes;
int i;
for (i = 0; i < name.Length; i++)
{
if (name[i] == 0)
break;
if (!StringUtils.IsDigit(name[i]) && !StringUtils.IsAlpha(name[i]) && name[i] != '_' && name[i] != '-')
return false;
}
if (i == 0 || i == MaxSize)
return false;
return name[i] == 0;
}
public override string ToString()
{
return StringUtils.Utf8ZToString(Bytes);

View file

@ -6,9 +6,11 @@ using LibHac.Common;
namespace LibHac.Bcat
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 32)]
[StructLayout(LayoutKind.Sequential, Size = MaxSize)]
public struct FileName
{
private const int MaxSize = 0x20;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2;
@ -22,6 +24,29 @@ namespace LibHac.Bcat
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
public bool IsValid()
{
Span<byte> name = Bytes;
int i;
for (i = 0; i < name.Length; i++)
{
if (name[i] == 0)
break;
if (!StringUtils.IsDigit(name[i]) && !StringUtils.IsAlpha(name[i]) && name[i] != '_' && name[i] != '.')
return false;
}
if (i == 0 || i == MaxSize)
return false;
if (name[i] != 0)
return false;
return name[i - 1] != '.';
}
public override string ToString()
{
return StringUtils.Utf8ZToString(Bytes);

View file

@ -19,18 +19,26 @@ namespace LibHac.Bcat
public static Result.Base InvalidArgument => new Result.Base(ModuleBcat, 1);
/// <summary>Error code: 2122-0002; Inner value: 0x47a</summary>
public static Result.Base NotFound => new Result.Base(ModuleBcat, 2);
/// <summary>Error code: 2122-0003; Inner value: 0x67a</summary>
public static Result.Base TargetLocked => new Result.Base(ModuleBcat, 3);
/// <summary>Error code: 2122-0006; Inner value: 0xc7a</summary>
public static Result.Base AlreadyOpen => new Result.Base(ModuleBcat, 6);
/// <summary>Error code: 2122-0007; Inner value: 0xe7a</summary>
public static Result.Base NotOpen => new Result.Base(ModuleBcat, 7);
/// <summary>Error code: 2122-0009; Inner value: 0x127a</summary>
public static Result.Base ServiceOpenLimitReached => new Result.Base(ModuleBcat, 9);
/// <summary>Error code: 2122-0010; Inner value: 0x147a</summary>
public static Result.Base SaveDataNotFount => new Result.Base(ModuleBcat, 10);
public static Result.Base SaveDataNotFound => new Result.Base(ModuleBcat, 10);
/// <summary>Error code: 2122-0031; Inner value: 0x3e7a</summary>
public static Result.Base NetworkServiceAccountNotAvailable => new Result.Base(ModuleBcat, 31);
/// <summary>Error code: 2122-0080; Inner value: 0xa07a</summary>
public static Result.Base PassphrasePathNotFound => new Result.Base(ModuleBcat, 80);
/// <summary>Error code: 2122-0090; Inner value: 0xb47a</summary>
public static Result.Base PermissionDenied => new Result.Base(ModuleBcat, 90);
/// <summary>Error code: 2122-0091; Inner value: 0xb67a</summary>
public static Result.Base AllocationFailed => new Result.Base(ModuleBcat, 91);
/// <summary>Error code: 2122-0204; Inner value: 0x1987a</summary>
public static Result.Base InvalidStorageMetaVersion => new Result.Base(ModuleBcat, 204);
public static Result.Base InvalidDeliveryCacheStorageFile => new Result.Base(ModuleBcat, 204);
/// <summary>Error code: 2122-0205; Inner value: 0x19a7a</summary>
public static Result.Base StorageOpenLimitReached => new Result.Base(ModuleBcat, 205);
}