diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 16abb8a9..13eab837 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -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, diff --git a/src/LibHac/Bcat/BcatServer.cs b/src/LibHac/Bcat/BcatServer.cs index 27c2c478..2618967d 100644 --- a/src/LibHac/Bcat/BcatServer.cs +++ b/src/LibHac/Bcat/BcatServer.cs @@ -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; } } diff --git a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs new file mode 100644 index 00000000..78007e7b --- /dev/null +++ b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs @@ -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 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 buffer = MemoryMarshal.Cast(Entries); + rc = fs.ReadFile(out bytesRead, handle, 4, buffer); + if (rc.IsFailure()) return rc; + + Count = (int)((uint)bytesRead / Unsafe.SizeOf()); + + return Result.Success; + } + finally + { + fs.CloseFile(handle); + } + } + } + } +} diff --git a/src/LibHac/Bcat/Detail/Service/DeliveryCacheFileEntryMeta.cs b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs similarity index 74% rename from src/LibHac/Bcat/Detail/Service/DeliveryCacheFileEntryMeta.cs rename to src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs index f3223e3c..53d2392f 100644 --- a/src/LibHac/Bcat/Detail/Service/DeliveryCacheFileEntryMeta.cs +++ b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs @@ -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; diff --git a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs new file mode 100644 index 00000000..ecd76e0c --- /dev/null +++ b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs @@ -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 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 pathBuffer, ulong applicationId) + { + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(PassphrasePath); + } + } + + public void GetDeliveryListPath(Span pathBuffer, ulong applicationId) + { + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(DeliveryListPath); + } + } + + public void GetEtagFilePath(Span pathBuffer, ulong applicationId) + { + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(EtagPath); + } + } + + public void GetNaRequiredPath(Span pathBuffer, ulong applicationId) + { + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(NaRequiredPath); + } + } + + public void GetIndexLockPath(Span pathBuffer, ulong applicationId) + { + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(IndexLockPath); + } + } + + public void GetFilePath(Span 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 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 pathBuffer, ulong applicationId) + { + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(DirectoriesPath); + } + } + + public void GetDirectoryPath(Span 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 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 DeliveryCacheMountNamePrefix => // bcat-dc- + new[] { (byte)'b', (byte)'c', (byte)'a', (byte)'t', (byte)'-', (byte)'d', (byte)'c', (byte)'-' }; + + private static ReadOnlySpan RootPath => // :/ + new[] { (byte)':', (byte)'/' }; + + private static ReadOnlySpan 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 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 EtagPath => // :/etag.bin + new[] + { + (byte) ':', (byte) '/', (byte) 'e', (byte) 't', (byte) 'a', (byte) 'g', (byte) '.', (byte) 'b', + (byte) 'i', (byte) 'n' + }; + + private static ReadOnlySpan 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 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 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 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 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 FilesDirectoryName => // files + new[] { (byte)'f', (byte)'i', (byte)'l', (byte)'e', (byte)'s' }; + } +} diff --git a/src/LibHac/Bcat/Detail/Service/DeliveryCacheDirectoryService.cs b/src/LibHac/Bcat/Detail/Service/DeliveryCacheDirectoryService.cs index e84169fe..fc091065 100644 --- a/src/LibHac/Bcat/Detail/Service/DeliveryCacheDirectoryService.cs +++ b/src/LibHac/Bcat/Detail/Service/DeliveryCacheDirectoryService.cs @@ -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; diff --git a/src/LibHac/Bcat/Detail/Service/DeliveryCacheFileService.cs b/src/LibHac/Bcat/Detail/Service/DeliveryCacheFileService.cs index 9d886bbb..b39cd62f 100644 --- a/src/LibHac/Bcat/Detail/Service/DeliveryCacheFileService.cs +++ b/src/LibHac/Bcat/Detail/Service/DeliveryCacheFileService.cs @@ -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 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 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(); } } } diff --git a/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageManager.cs b/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageManager.cs deleted file mode 100644 index d7227053..00000000 --- a/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageManager.cs +++ /dev/null @@ -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; } - } - } -} diff --git a/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs b/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs index fe5424bb..2c13bd4b 100644 --- a/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs +++ b/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs @@ -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 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); + } } } diff --git a/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs b/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs index c5b2c240..0905d706 100644 --- a/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs +++ b/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs @@ -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; } } } diff --git a/src/LibHac/Bcat/DirectoryName.cs b/src/LibHac/Bcat/DirectoryName.cs index 75c675b3..009f276e 100644 --- a/src/LibHac/Bcat/DirectoryName.cs +++ b/src/LibHac/Bcat/DirectoryName.cs @@ -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 Bytes => SpanHelpers.AsByteSpan(ref this); + public bool IsValid() + { + Span 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); diff --git a/src/LibHac/Bcat/FileName.cs b/src/LibHac/Bcat/FileName.cs index db546cbe..6911a7eb 100644 --- a/src/LibHac/Bcat/FileName.cs +++ b/src/LibHac/Bcat/FileName.cs @@ -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 Bytes => SpanHelpers.AsByteSpan(ref this); + public bool IsValid() + { + Span 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); diff --git a/src/LibHac/Bcat/ResultBcat.cs b/src/LibHac/Bcat/ResultBcat.cs index cb20cc6f..9a5fd6b1 100644 --- a/src/LibHac/Bcat/ResultBcat.cs +++ b/src/LibHac/Bcat/ResultBcat.cs @@ -19,18 +19,26 @@ namespace LibHac.Bcat public static Result.Base InvalidArgument => new Result.Base(ModuleBcat, 1); /// Error code: 2122-0002; Inner value: 0x47a public static Result.Base NotFound => new Result.Base(ModuleBcat, 2); + /// Error code: 2122-0003; Inner value: 0x67a + public static Result.Base TargetLocked => new Result.Base(ModuleBcat, 3); + /// Error code: 2122-0006; Inner value: 0xc7a + public static Result.Base AlreadyOpen => new Result.Base(ModuleBcat, 6); /// Error code: 2122-0007; Inner value: 0xe7a public static Result.Base NotOpen => new Result.Base(ModuleBcat, 7); /// Error code: 2122-0009; Inner value: 0x127a public static Result.Base ServiceOpenLimitReached => new Result.Base(ModuleBcat, 9); /// Error code: 2122-0010; Inner value: 0x147a - public static Result.Base SaveDataNotFount => new Result.Base(ModuleBcat, 10); + public static Result.Base SaveDataNotFound => new Result.Base(ModuleBcat, 10); /// Error code: 2122-0031; Inner value: 0x3e7a public static Result.Base NetworkServiceAccountNotAvailable => new Result.Base(ModuleBcat, 31); + /// Error code: 2122-0080; Inner value: 0xa07a + public static Result.Base PassphrasePathNotFound => new Result.Base(ModuleBcat, 80); /// Error code: 2122-0090; Inner value: 0xb47a public static Result.Base PermissionDenied => new Result.Base(ModuleBcat, 90); + /// Error code: 2122-0091; Inner value: 0xb67a + public static Result.Base AllocationFailed => new Result.Base(ModuleBcat, 91); /// Error code: 2122-0204; Inner value: 0x1987a - public static Result.Base InvalidStorageMetaVersion => new Result.Base(ModuleBcat, 204); + public static Result.Base InvalidDeliveryCacheStorageFile => new Result.Base(ModuleBcat, 204); /// Error code: 2122-0205; Inner value: 0x19a7a public static Result.Base StorageOpenLimitReached => new Result.Base(ModuleBcat, 205); }