mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Implement DeliveryCacheFileService and supporting code
This commit is contained in:
parent
939c495db6
commit
ef36568a8d
13 changed files with 727 additions and 52 deletions
|
@ -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,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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' };
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue