mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Merge pull request #129 from Thealexbarney/bcat-storage
Implement BCAT delivery cache access - Add BCAT delivery cache reading services - Add methods to `U8StringBuilder` for writing formatted values - Add a `HorizonClient` class which mimics nnsdk from Horizon - Add a simple service manager that tracks registered objects
This commit is contained in:
commit
fdf11b2621
@ -1,6 +1,9 @@
|
||||
Name,Index
|
||||
Fs,2
|
||||
Loader,9
|
||||
Sf,10
|
||||
Kvdb,20
|
||||
Sm,21
|
||||
Sdmmc,24
|
||||
Bcat,122
|
||||
LibHac,428
|
|
@ -1,6 +1,9 @@
|
||||
Name,Namespace,Path
|
||||
Fs,LibHac.Fs,LibHac/Fs/ResultFs.cs
|
||||
Loader,LibHac.Loader,LibHac/Loader/ResultLoader.cs
|
||||
Sf,LibHac.Sf,LibHac/Sf/ResultSf.cs
|
||||
Kvdb,LibHac.Kvdb,LibHac/Kvdb/ResultKvdb.cs
|
||||
Sm,LibHac.Sm,LibHac/Sm/ResultSm.cs
|
||||
Sdmmc,LibHac.FsService,LibHac/FsService/ResultSdmmc.cs
|
||||
Bcat,LibHac.Bcat,LibHac/Bcat/ResultBcat.cs
|
||||
LibHac,LibHac.Common,LibHac/Common/ResultLibHac.cs
|
|
@ -268,15 +268,65 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
||||
|
||||
9,200,,InternalError,
|
||||
|
||||
10,1,,NotSupported,
|
||||
10,3,,PreconditionViolation,
|
||||
|
||||
# These should be in the sf::cmif namespace
|
||||
10,202,,InvalidHeaderSize,
|
||||
10,211,,InvalidInHeader,
|
||||
10,221,,UnknownCommandId,
|
||||
10,232,,InvalidOutRawSize,
|
||||
10,235,,InvalidNumInObjects,
|
||||
10,236,,InvalidNumOutObjects,
|
||||
10,239,,InvalidInObject,
|
||||
10,261,,TargetNotFound,
|
||||
10,301,,OutOfDomainEntries,
|
||||
|
||||
# These should be in the sf::impl namespace
|
||||
10,800,899,RequestContextChanged,
|
||||
10,801,809,RequestInvalidated,
|
||||
10,802,,RequestInvalidatedByUser,
|
||||
|
||||
10,811,819,RequestDeferred,
|
||||
10,812,,RequestDeferredByUser,
|
||||
|
||||
20,1,,TooLargeKeyOrDbFull,
|
||||
20,2,,KeyNotFound,
|
||||
20,4,,AllocationFailed,
|
||||
20,5,,InvalidKeyValue,
|
||||
20,6,,BufferInsufficient,
|
||||
|
||||
21,1,,OutOfProcesses,
|
||||
21,2,,InvalidClient,
|
||||
21,3,,OutOfSessions,
|
||||
21,4,,AlreadyRegistered,
|
||||
21,5,,OutOfServices,
|
||||
21,6,,InvalidServiceName,
|
||||
21,7,,NotRegistered,
|
||||
21,8,,NotAllowed,
|
||||
21,9,,TooLargeAccessControl,
|
||||
|
||||
24,1,,DeviceNotFound,
|
||||
24,4,,DeviceAsleep,
|
||||
|
||||
122,1,,InvalidArgument,
|
||||
122,2,,NotFound,
|
||||
122,3,,TargetLocked,
|
||||
122,6,,AlreadyOpen,
|
||||
122,7,,NotOpen,
|
||||
122,9,,ServiceOpenLimitReached,
|
||||
122,10,,SaveDataNotFound,
|
||||
|
||||
122,31,,NetworkServiceAccountNotAvailable,
|
||||
|
||||
122,80,,PassphrasePathNotFound,
|
||||
|
||||
122,90,,PermissionDenied,
|
||||
122,91,,AllocationFailed,
|
||||
|
||||
122,204,,InvalidDeliveryCacheStorageFile,
|
||||
122,205,,StorageOpenLimitReached,
|
||||
|
||||
123,0,4999,SslService,
|
||||
|
||||
124,0,,Cancelled,
|
||||
@ -307,6 +357,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
||||
428,3,,ArgumentOutOfRange,
|
||||
428,4,,BufferTooSmall,
|
||||
|
||||
428,51,,ServiceNotInitialized,
|
||||
|
||||
428,1000,1999,InvalidData,
|
||||
428,1001,1019,InvalidKip,
|
||||
428,1002,,InvalidKipFileSize,The size of the KIP file was smaller than expected.
|
||||
|
|
20
src/LibHac/ApplicationId.cs
Normal file
20
src/LibHac/ApplicationId.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
public readonly struct ApplicationId : IEquatable<ApplicationId>
|
||||
{
|
||||
public static ApplicationId InvalidId => default;
|
||||
|
||||
public readonly ulong Value;
|
||||
|
||||
public ApplicationId(ulong value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is ApplicationId id && Equals(id);
|
||||
public bool Equals(ApplicationId other) => Value == other.Value;
|
||||
public override int GetHashCode() => HashCode.Combine(Value);
|
||||
}
|
||||
}
|
13
src/LibHac/Arp/ApplicationLaunchProperty.cs
Normal file
13
src/LibHac/Arp/ApplicationLaunchProperty.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Arp
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
||||
public struct ApplicationLaunchProperty
|
||||
{
|
||||
[FieldOffset(0x0)] public ApplicationId ApplicationId;
|
||||
[FieldOffset(0x8)] public uint Version;
|
||||
[FieldOffset(0xC)] public Ncm.StorageId BaseStorageId;
|
||||
[FieldOffset(0xD)] public Ncm.StorageId UpdateStorageId;
|
||||
}
|
||||
}
|
67
src/LibHac/Arp/ArpClient.cs
Normal file
67
src/LibHac/Arp/ArpClient.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using LibHac.Arp.Impl;
|
||||
using LibHac.Ns;
|
||||
|
||||
namespace LibHac.Arp
|
||||
{
|
||||
public class ArpClient
|
||||
{
|
||||
private HorizonClient HosClient { get; }
|
||||
private IReader Reader { get; set; }
|
||||
|
||||
private readonly object _readerInitLocker = new object();
|
||||
|
||||
internal ArpClient(HorizonClient horizonClient)
|
||||
{
|
||||
HosClient = horizonClient;
|
||||
}
|
||||
|
||||
public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ulong processId)
|
||||
{
|
||||
EnsureReaderInitialized();
|
||||
|
||||
return Reader.GetApplicationLaunchProperty(out launchProperty, processId);
|
||||
}
|
||||
|
||||
public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ApplicationId applicationId)
|
||||
{
|
||||
EnsureReaderInitialized();
|
||||
|
||||
return Reader.GetApplicationLaunchPropertyWithApplicationId(out launchProperty, applicationId);
|
||||
}
|
||||
|
||||
public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId)
|
||||
{
|
||||
EnsureReaderInitialized();
|
||||
|
||||
return Reader.GetApplicationControlProperty(out controlProperty, processId);
|
||||
}
|
||||
|
||||
public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ApplicationId applicationId)
|
||||
{
|
||||
EnsureReaderInitialized();
|
||||
|
||||
return Reader.GetApplicationControlPropertyWithApplicationId(out controlProperty, applicationId);
|
||||
}
|
||||
|
||||
private void EnsureReaderInitialized()
|
||||
{
|
||||
if (Reader != null)
|
||||
return;
|
||||
|
||||
lock (_readerInitLocker)
|
||||
{
|
||||
if (Reader != null)
|
||||
return;
|
||||
|
||||
Result rc = HosClient.Sm.GetService(out IReader reader, "arp:r");
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
throw new HorizonResultException(rc, "Failed to initialize arp reader.");
|
||||
}
|
||||
|
||||
Reader = reader;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
src/LibHac/Arp/Impl/IReader.cs
Normal file
12
src/LibHac/Arp/Impl/IReader.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using LibHac.Ns;
|
||||
|
||||
namespace LibHac.Arp.Impl
|
||||
{
|
||||
public interface IReader
|
||||
{
|
||||
Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ulong processId);
|
||||
Result GetApplicationLaunchPropertyWithApplicationId(out ApplicationLaunchProperty launchProperty, ApplicationId applicationId);
|
||||
Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId);
|
||||
Result GetApplicationControlPropertyWithApplicationId(out ApplicationControlProperty controlProperty, ApplicationId applicationId);
|
||||
}
|
||||
}
|
85
src/LibHac/Bcat/BcatServer.cs
Normal file
85
src/LibHac/Bcat/BcatServer.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System.Diagnostics;
|
||||
using LibHac.Bcat.Detail.Ipc;
|
||||
using LibHac.Bcat.Detail.Service;
|
||||
using LibHac.Bcat.Detail.Service.Core;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
public class BcatServer
|
||||
{
|
||||
private const int ServiceTypeCount = 4;
|
||||
|
||||
internal HorizonClient Hos { get; }
|
||||
private ServiceCreator[] ServiceCreators { get; } = new ServiceCreator[ServiceTypeCount];
|
||||
|
||||
private readonly object _bcatServiceInitLocker = new object();
|
||||
private readonly object _storageManagerInitLocker = new object();
|
||||
|
||||
private DeliveryCacheStorageManager StorageManager { get; set; }
|
||||
|
||||
public BcatServer(HorizonClient horizonClient)
|
||||
{
|
||||
Hos = horizonClient;
|
||||
|
||||
InitBcatService(BcatServiceType.BcatU, "bcat:u", AccessControl.MountOwnDeliveryCacheStorage);
|
||||
InitBcatService(BcatServiceType.BcatS, "bcat:s", AccessControl.MountOthersDeliveryCacheStorage);
|
||||
InitBcatService(BcatServiceType.BcatM, "bcat:m", AccessControl.MountOthersDeliveryCacheStorage | AccessControl.DeliveryTaskManagement);
|
||||
InitBcatService(BcatServiceType.BcatA, "bcat:a", AccessControl.All);
|
||||
}
|
||||
|
||||
private void InitBcatService(BcatServiceType type, string name, AccessControl accessControl)
|
||||
{
|
||||
InitServiceCreator(type, name, accessControl);
|
||||
|
||||
IServiceCreator service = GetServiceCreator(type);
|
||||
|
||||
Result rc = Hos.Sm.RegisterService(service, name);
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
throw new HorizonResultException(rc, "Abort");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitServiceCreator(BcatServiceType type, string name, AccessControl accessControl)
|
||||
{
|
||||
lock (_bcatServiceInitLocker)
|
||||
{
|
||||
Debug.Assert((uint)type < ServiceTypeCount);
|
||||
|
||||
ServiceCreators[(int)type] = new ServiceCreator(this, name, accessControl);
|
||||
}
|
||||
}
|
||||
|
||||
private IServiceCreator GetServiceCreator(BcatServiceType type)
|
||||
{
|
||||
Debug.Assert((uint)type < ServiceTypeCount);
|
||||
|
||||
return ServiceCreators[(int)type];
|
||||
}
|
||||
|
||||
internal DeliveryCacheStorageManager GetStorageManager()
|
||||
{
|
||||
return StorageManager ?? InitStorageManager();
|
||||
}
|
||||
|
||||
internal FileSystemClient GetFsClient()
|
||||
{
|
||||
return Hos.Fs;
|
||||
}
|
||||
|
||||
private DeliveryCacheStorageManager InitStorageManager()
|
||||
{
|
||||
lock (_storageManagerInitLocker)
|
||||
{
|
||||
if (StorageManager != null)
|
||||
{
|
||||
return StorageManager;
|
||||
}
|
||||
|
||||
StorageManager = new DeliveryCacheStorageManager(this);
|
||||
return StorageManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/LibHac/Bcat/BcatServiceType.cs
Normal file
10
src/LibHac/Bcat/BcatServiceType.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
public enum BcatServiceType
|
||||
{
|
||||
BcatU,
|
||||
BcatS,
|
||||
BcatM,
|
||||
BcatA
|
||||
}
|
||||
}
|
19
src/LibHac/Bcat/DeliveryCacheDirectoryEntry.cs
Normal file
19
src/LibHac/Bcat/DeliveryCacheDirectoryEntry.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x38)]
|
||||
public struct DeliveryCacheDirectoryEntry
|
||||
{
|
||||
[FieldOffset(0x00)] public FileName Name;
|
||||
[FieldOffset(0x20)] public long Size;
|
||||
[FieldOffset(0x28)] public Digest Digest;
|
||||
|
||||
public DeliveryCacheDirectoryEntry(ref FileName name, long size, ref Digest digest)
|
||||
{
|
||||
Name = name;
|
||||
Size = size;
|
||||
Digest = digest;
|
||||
}
|
||||
}
|
||||
}
|
11
src/LibHac/Bcat/Detail/Ipc/IDeliveryCacheDirectoryService.cs
Normal file
11
src/LibHac/Bcat/Detail/Ipc/IDeliveryCacheDirectoryService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Ipc
|
||||
{
|
||||
public interface IDeliveryCacheDirectoryService : IDisposable
|
||||
{
|
||||
Result Open(ref DirectoryName name);
|
||||
Result Read(out int entriesRead, Span<DeliveryCacheDirectoryEntry> entryBuffer);
|
||||
Result GetCount(out int count);
|
||||
}
|
||||
}
|
12
src/LibHac/Bcat/Detail/Ipc/IDeliveryCacheFileService.cs
Normal file
12
src/LibHac/Bcat/Detail/Ipc/IDeliveryCacheFileService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Ipc
|
||||
{
|
||||
public interface IDeliveryCacheFileService : IDisposable
|
||||
{
|
||||
Result Open(ref DirectoryName directoryName, ref FileName fileName);
|
||||
Result Read(out long bytesRead, long offset, Span<byte> destination);
|
||||
Result GetSize(out long size);
|
||||
Result GetDigest(out Digest digest);
|
||||
}
|
||||
}
|
11
src/LibHac/Bcat/Detail/Ipc/IDeliveryCacheStorageService.cs
Normal file
11
src/LibHac/Bcat/Detail/Ipc/IDeliveryCacheStorageService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Ipc
|
||||
{
|
||||
public interface IDeliveryCacheStorageService : IDisposable
|
||||
{
|
||||
Result CreateFileService(out IDeliveryCacheFileService fileService);
|
||||
Result CreateDirectoryService(out IDeliveryCacheDirectoryService directoryService);
|
||||
Result EnumerateDeliveryCacheDirectory(out int namesRead, Span<DirectoryName> nameBuffer);
|
||||
}
|
||||
}
|
13
src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs
Normal file
13
src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using LibHac.Ncm;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Ipc
|
||||
{
|
||||
public interface IServiceCreator
|
||||
{
|
||||
Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service,
|
||||
ulong processId);
|
||||
|
||||
Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service,
|
||||
ApplicationId applicationId);
|
||||
}
|
||||
}
|
15
src/LibHac/Bcat/Detail/Service/AccessControl.cs
Normal file
15
src/LibHac/Bcat/Detail/Service/AccessControl.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Service
|
||||
{
|
||||
[Flags]
|
||||
internal enum AccessControl
|
||||
{
|
||||
None = 0,
|
||||
MountOwnDeliveryCacheStorage = 1 << 1,
|
||||
MountOthersDeliveryCacheStorage = 1 << 2,
|
||||
DeliveryTaskManagement = 1 << 3,
|
||||
Debug = 1 << 4,
|
||||
All = ~0
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
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];
|
||||
public int Count { get; private 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 GetEntry(out DeliveryCacheFileMetaEntry entry, int index)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (index >= Count)
|
||||
{
|
||||
entry = default;
|
||||
return ResultBcat.NotFound.Log();
|
||||
}
|
||||
|
||||
entry = Entries[index];
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Service.Core
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
|
||||
internal struct DeliveryCacheFileMetaEntry
|
||||
{
|
||||
[FieldOffset(0x00)] public FileName Name;
|
||||
[FieldOffset(0x20)] public long Size;
|
||||
[FieldOffset(0x30)] public Digest Digest;
|
||||
}
|
||||
}
|
@ -0,0 +1,398 @@
|
||||
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();
|
||||
|
||||
var sb = new U8StringBuilder(mountName.Name);
|
||||
sb.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();
|
||||
|
||||
var sb = new U8StringBuilder(mountName.Name);
|
||||
sb.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();
|
||||
|
||||
var sb = new U8StringBuilder(mountName.Name);
|
||||
sb.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)
|
||||
{
|
||||
// returns "mount:/passphrase.bin"
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
sb.Append(PassphrasePath);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetDeliveryListPath(Span<byte> pathBuffer, ulong applicationId)
|
||||
{
|
||||
// returns "mount:/list.msgpack"
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
sb.Append(DeliveryListPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetEtagFilePath(Span<byte> pathBuffer, ulong applicationId)
|
||||
{
|
||||
// returns "mount:/etag.bin"
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
sb.Append(EtagPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetNaRequiredPath(Span<byte> pathBuffer, ulong applicationId)
|
||||
{
|
||||
// returns "mount:/na_required"
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
sb.Append(NaRequiredPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetIndexLockPath(Span<byte> pathBuffer, ulong applicationId)
|
||||
{
|
||||
// returns "mount:/index.lock"
|
||||
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)
|
||||
{
|
||||
// returns "mount:/directories/%s/files/%s", directoryName, fileName
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
|
||||
sb.Append(DirectoriesPath)
|
||||
.Append(DirectorySeparator).Append(directoryName.Bytes)
|
||||
.Append(DirectorySeparator).Append(FilesDirectoryName)
|
||||
.Append(DirectorySeparator).Append(fileName.Bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetFilesMetaPath(Span<byte> pathBuffer, ulong applicationId, ref DirectoryName directoryName)
|
||||
{
|
||||
// returns "mount:/directories/%s/files.meta", directoryName
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
|
||||
sb.Append(DirectoriesPath)
|
||||
.Append(DirectorySeparator).Append(directoryName.Bytes)
|
||||
.Append(DirectorySeparator).Append(FilesMetaFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetDirectoriesPath(Span<byte> pathBuffer, ulong applicationId)
|
||||
{
|
||||
// returns "mount:/directories"
|
||||
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)
|
||||
{
|
||||
// returns "mount:/directories/%s", directoryName
|
||||
lock (_locker)
|
||||
{
|
||||
var sb = new U8StringBuilder(pathBuffer);
|
||||
AppendMountName(ref sb, applicationId);
|
||||
|
||||
sb.Append(DirectoriesPath)
|
||||
.Append(DirectorySeparator).Append(directoryName.Bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetDirectoriesMetaPath(Span<byte> pathBuffer, ulong applicationId)
|
||||
{
|
||||
// returns "mount:/directories.meta"
|
||||
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)
|
||||
{
|
||||
Entry[] entries = Entries;
|
||||
|
||||
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' };
|
||||
}
|
||||
}
|
105
src/LibHac/Bcat/Detail/Service/DeliveryCacheDirectoryService.cs
Normal file
105
src/LibHac/Bcat/Detail/Service/DeliveryCacheDirectoryService.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using LibHac.Bcat.Detail.Ipc;
|
||||
using LibHac.Bcat.Detail.Service.Core;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Service
|
||||
{
|
||||
internal class DeliveryCacheDirectoryService : IDeliveryCacheDirectoryService
|
||||
{
|
||||
private BcatServer Server { get; }
|
||||
private object Locker { get; } = new object();
|
||||
private DeliveryCacheStorageService Parent { get; }
|
||||
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
private AccessControl Access { get; }
|
||||
private ulong ApplicationId { get; }
|
||||
private DirectoryName _name;
|
||||
private bool IsDirectoryOpen { get; set; }
|
||||
private int Count { get; set; }
|
||||
|
||||
public DeliveryCacheDirectoryService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId,
|
||||
AccessControl accessControl)
|
||||
{
|
||||
Server = server;
|
||||
Parent = parent;
|
||||
ApplicationId = applicationId;
|
||||
Access = accessControl;
|
||||
}
|
||||
|
||||
public Result Open(ref DirectoryName name)
|
||||
{
|
||||
if (!name.IsValid())
|
||||
return ResultBcat.InvalidArgument.Log();
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
if (IsDirectoryOpen)
|
||||
return ResultBcat.AlreadyOpen.Log();
|
||||
|
||||
var metaReader = new DeliveryCacheFileMetaAccessor(Server);
|
||||
Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref name, false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Count = metaReader.Count;
|
||||
_name = name;
|
||||
IsDirectoryOpen = true;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Read(out int entriesRead, Span<DeliveryCacheDirectoryEntry> entryBuffer)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
entriesRead = default;
|
||||
|
||||
if (!IsDirectoryOpen)
|
||||
return ResultBcat.NotOpen.Log();
|
||||
|
||||
var metaReader = new DeliveryCacheFileMetaAccessor(Server);
|
||||
Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref _name, true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < entryBuffer.Length; i++)
|
||||
{
|
||||
rc = metaReader.GetEntry(out DeliveryCacheFileMetaEntry entry, i);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (!ResultBcat.NotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
entryBuffer[i] = new DeliveryCacheDirectoryEntry(ref entry.Name, entry.Size, ref entry.Digest);
|
||||
}
|
||||
|
||||
entriesRead = i;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Result GetCount(out int count)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (!IsDirectoryOpen)
|
||||
{
|
||||
count = default;
|
||||
return ResultBcat.NotOpen.Log();
|
||||
}
|
||||
|
||||
count = Count;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Parent.NotifyCloseDirectory();
|
||||
}
|
||||
}
|
||||
}
|
120
src/LibHac/Bcat/Detail/Service/DeliveryCacheFileService.cs
Normal file
120
src/LibHac/Bcat/Detail/Service/DeliveryCacheFileService.cs
Normal file
@ -0,0 +1,120 @@
|
||||
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
|
||||
{
|
||||
private BcatServer Server { get; }
|
||||
private object Locker { get; } = new object();
|
||||
private DeliveryCacheStorageService Parent { get; }
|
||||
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
private AccessControl Access { get; }
|
||||
private ulong ApplicationId { get; }
|
||||
private FileHandle _handle;
|
||||
private DeliveryCacheFileMetaEntry _metaEntry;
|
||||
private bool IsFileOpen { get; set; }
|
||||
|
||||
public DeliveryCacheFileService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId,
|
||||
AccessControl accessControl)
|
||||
{
|
||||
Server = server;
|
||||
Parent = parent;
|
||||
ApplicationId = applicationId;
|
||||
Access = accessControl;
|
||||
}
|
||||
|
||||
public Result Open(ref DirectoryName directoryName, ref FileName fileName)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (!IsFileOpen)
|
||||
{
|
||||
size = default;
|
||||
return ResultBcat.NotOpen.Log();
|
||||
}
|
||||
|
||||
return Server.GetFsClient().GetFileSize(out size, _handle);
|
||||
}
|
||||
}
|
||||
|
||||
public Result GetDigest(out Digest digest)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using LibHac.Bcat.Detail.Ipc;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Service
|
||||
{
|
||||
internal class DeliveryCacheStorageService : IDeliveryCacheStorageService
|
||||
{
|
||||
private const int MaxOpenCount = 8;
|
||||
private BcatServer Server { get; }
|
||||
|
||||
private object Locker { get; } = new object();
|
||||
private AccessControl Access { get; }
|
||||
private ulong ApplicationId { get; }
|
||||
private int FileServiceOpenCount { get; set; }
|
||||
private int DirectoryServiceOpenCount { get; set; }
|
||||
|
||||
public DeliveryCacheStorageService(BcatServer server, ulong applicationId, AccessControl accessControl)
|
||||
{
|
||||
Server = server;
|
||||
ApplicationId = applicationId;
|
||||
Access = accessControl;
|
||||
}
|
||||
|
||||
public Result CreateFileService(out IDeliveryCacheFileService service)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
service = default;
|
||||
|
||||
if (FileServiceOpenCount >= MaxOpenCount)
|
||||
return ResultBcat.ServiceOpenLimitReached.Log();
|
||||
|
||||
service = new DeliveryCacheFileService(Server, this, ApplicationId, Access);
|
||||
|
||||
FileServiceOpenCount++;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Result CreateDirectoryService(out IDeliveryCacheDirectoryService service)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
62
src/LibHac/Bcat/Detail/Service/ServiceCreator.cs
Normal file
62
src/LibHac/Bcat/Detail/Service/ServiceCreator.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using LibHac.Arp;
|
||||
using LibHac.Bcat.Detail.Ipc;
|
||||
|
||||
namespace LibHac.Bcat.Detail.Service
|
||||
{
|
||||
internal class ServiceCreator : IServiceCreator
|
||||
{
|
||||
private BcatServer Server { get; }
|
||||
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
private string ServiceName { get; }
|
||||
private AccessControl AccessControl { get; }
|
||||
|
||||
public ServiceCreator(BcatServer server, string serviceName, AccessControl accessControl)
|
||||
{
|
||||
Server = server;
|
||||
ServiceName = serviceName;
|
||||
AccessControl = accessControl;
|
||||
}
|
||||
|
||||
public Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, ulong processId)
|
||||
{
|
||||
Result rc = Server.Hos.Arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty,
|
||||
processId);
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
service = default;
|
||||
return ResultBcat.NotFound.LogConverted(rc);
|
||||
}
|
||||
|
||||
return CreateDeliveryCacheStorageServiceImpl(out service, launchProperty.ApplicationId);
|
||||
}
|
||||
|
||||
public Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service,
|
||||
ApplicationId applicationId)
|
||||
{
|
||||
if (!AccessControl.HasFlag(AccessControl.MountOthersDeliveryCacheStorage))
|
||||
{
|
||||
service = default;
|
||||
return ResultBcat.PermissionDenied.Log();
|
||||
}
|
||||
|
||||
return CreateDeliveryCacheStorageServiceImpl(out service, applicationId);
|
||||
}
|
||||
|
||||
private Result CreateDeliveryCacheStorageServiceImpl(out IDeliveryCacheStorageService service,
|
||||
ApplicationId applicationId)
|
||||
{
|
||||
service = default;
|
||||
|
||||
Result rc = Server.GetStorageManager().Open(applicationId.Value);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// todo: Check if network account required
|
||||
|
||||
service = new DeliveryCacheStorageService(Server, applicationId.Value, AccessControl);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
28
src/LibHac/Bcat/Digest.cs
Normal file
28
src/LibHac/Bcat/Digest.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
public struct Digest
|
||||
{
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => Bytes[i];
|
||||
set => Bytes[i] = value;
|
||||
}
|
||||
|
||||
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Bytes.ToHexString();
|
||||
}
|
||||
}
|
||||
}
|
52
src/LibHac/Bcat/DirectoryName.cs
Normal file
52
src/LibHac/Bcat/DirectoryName.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
[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;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => Bytes[i];
|
||||
set => Bytes[i] = value;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
55
src/LibHac/Bcat/FileName.cs
Normal file
55
src/LibHac/Bcat/FileName.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
[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;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => Bytes[i];
|
||||
set => Bytes[i] = value;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
45
src/LibHac/Bcat/ResultBcat.cs
Normal file
45
src/LibHac/Bcat/ResultBcat.cs
Normal file
@ -0,0 +1,45 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// This file was automatically generated.
|
||||
// Changes to this file will be lost when the file is regenerated.
|
||||
//
|
||||
// To change this file, modify /build/CodeGen/results.csv at the root of this
|
||||
// repo and run the build script.
|
||||
//
|
||||
// The script can be run with the "codegen" option to run only the
|
||||
// code generation portion of the build.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
public static class ResultBcat
|
||||
{
|
||||
public const int ModuleBcat = 122;
|
||||
|
||||
/// <summary>Error code: 2122-0001; Inner value: 0x27a</summary>
|
||||
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 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 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);
|
||||
}
|
||||
}
|
@ -26,6 +26,9 @@ namespace LibHac.Common
|
||||
/// <summary>Error code: 2428-0004; Inner value: 0x9ac</summary>
|
||||
public static Result.Base BufferTooSmall => new Result.Base(ModuleLibHac, 4);
|
||||
|
||||
/// <summary>Error code: 2428-0051; Inner value: 0x67ac</summary>
|
||||
public static Result.Base ServiceNotInitialized => new Result.Base(ModuleLibHac, 51);
|
||||
|
||||
/// <summary>Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac</summary>
|
||||
public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); }
|
||||
/// <summary>Error code: 2428-1001; Range: 1001-1019; Inner value: 0x7d3ac</summary>
|
||||
|
@ -168,5 +168,15 @@ namespace LibHac.Common
|
||||
{
|
||||
return Utf8ToString(value.Slice(0, GetLength(value)));
|
||||
}
|
||||
|
||||
public static bool IsAlpha(byte c)
|
||||
{
|
||||
return (c | 0x20u) - (byte)'a' <= 'z' - 'a';
|
||||
}
|
||||
|
||||
public static bool IsDigit(byte c)
|
||||
{
|
||||
return (uint)(c - (byte)'0') <= 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
@ -8,18 +11,20 @@ namespace LibHac.Common
|
||||
{
|
||||
private const int NullTerminatorLength = 1;
|
||||
|
||||
private readonly Span<byte> _buffer;
|
||||
private int _length;
|
||||
|
||||
public Span<byte> Buffer { get; }
|
||||
public int Length { get; private set; }
|
||||
public bool Overflowed { get; private set; }
|
||||
public readonly int Length => _length;
|
||||
public readonly int Capacity => _buffer.Length - NullTerminatorLength;
|
||||
public readonly Span<byte> Buffer => _buffer;
|
||||
|
||||
public readonly int Capacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Buffer.Length - NullTerminatorLength;
|
||||
}
|
||||
|
||||
public U8StringBuilder(Span<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_length = 0;
|
||||
Buffer = buffer;
|
||||
Length = 0;
|
||||
Overflowed = false;
|
||||
|
||||
ThrowIfBufferLengthIsZero();
|
||||
@ -27,62 +32,287 @@ namespace LibHac.Common
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
public U8StringBuilder Append(ReadOnlySpan<byte> value)
|
||||
// These functions are internal so they can be called by the extension methods
|
||||
// in U8StringBuilderExtensions. It's not an ideal setup, but it allows append
|
||||
// calls to be chained without accidentally creating a copy of the U8StringBuilder.
|
||||
internal void AppendInternal(ReadOnlySpan<byte> value)
|
||||
{
|
||||
if (Overflowed) return this;
|
||||
if (Overflowed) return;
|
||||
|
||||
int valueLength = StringUtils.GetLength(value);
|
||||
|
||||
if (!HasAdditionalCapacity(valueLength))
|
||||
{
|
||||
Overflowed = true;
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
value.Slice(0, valueLength).CopyTo(_buffer.Slice(_length));
|
||||
_length += valueLength;
|
||||
value.Slice(0, valueLength).CopyTo(Buffer.Slice(Length));
|
||||
Length += valueLength;
|
||||
AddNullTerminator();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public U8StringBuilder Append(byte value)
|
||||
internal void AppendInternal(byte value)
|
||||
{
|
||||
if (Overflowed) return this;
|
||||
if (Overflowed) return;
|
||||
|
||||
if (!HasAdditionalCapacity(1))
|
||||
{
|
||||
Overflowed = true;
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer[_length] = value;
|
||||
_length++;
|
||||
Buffer[Length] = value;
|
||||
Length++;
|
||||
AddNullTerminator();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(byte value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatUInt64(value, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(sbyte value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatInt64(value, 0xff, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(ushort value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatUInt64(value, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(short value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatInt64(value, 0xffff, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(uint value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatUInt64(value, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(int value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatInt64(value, 0xffffff, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(ulong value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatUInt64(value, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(long value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatInt64(value, 0xffffffff, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(float value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatFloat(value, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(double value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatDouble(value, format, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly bool HasCapacity(int requiredCapacity)
|
||||
{
|
||||
return requiredCapacity <= Capacity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly bool HasAdditionalCapacity(int requiredAdditionalCapacity)
|
||||
{
|
||||
return HasCapacity(_length + requiredAdditionalCapacity);
|
||||
return HasCapacity(Length + requiredAdditionalCapacity);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddNullTerminator()
|
||||
{
|
||||
_buffer[_length] = 0;
|
||||
Buffer[Length] = 0;
|
||||
}
|
||||
|
||||
private readonly void ThrowIfBufferLengthIsZero()
|
||||
{
|
||||
if (_buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0.");
|
||||
if (Buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0.");
|
||||
}
|
||||
|
||||
public override readonly string ToString() => StringUtils.Utf8ZToString(_buffer);
|
||||
private void AppendFormatInt64(long value, ulong mask, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Remove possible sign extension if needed
|
||||
if (mask == 'x' | mask == 'X')
|
||||
{
|
||||
value &= (long)mask;
|
||||
}
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatUInt64(ulong value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatFloat(float value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatDouble(double value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
public override readonly string ToString() => StringUtils.Utf8ZToString(Buffer);
|
||||
}
|
||||
|
||||
public static class U8StringBuilderExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder Append(this ref U8StringBuilder sb, ReadOnlySpan<byte> value)
|
||||
{
|
||||
sb.AppendInternal(value);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder Append(this ref U8StringBuilder sb, byte value)
|
||||
{
|
||||
sb.AppendInternal(value);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, byte value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, sbyte value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, ushort value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, short value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, uint value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, int value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, ulong value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, long value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, float value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, double value, char format = 'G',
|
||||
byte precision = 255)
|
||||
{
|
||||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,10 @@ namespace LibHac.Fs.Shim
|
||||
|
||||
Debug.Assert(nameBuffer.Length >= requiredNameBufferSize);
|
||||
|
||||
// ReSharper disable once RedundantAssignment
|
||||
int size = new U8StringBuilder(nameBuffer).Append(mountName).Append(StringTraits.DriveSeparator).Length;
|
||||
Debug.Assert(size == requiredNameBufferSize - 1);
|
||||
var sb = new U8StringBuilder(nameBuffer);
|
||||
sb.Append(mountName).Append(StringTraits.DriveSeparator);
|
||||
|
||||
Debug.Assert(sb.Length == requiredNameBufferSize - 1);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@ -144,7 +145,8 @@ namespace LibHac.Fs.Shim
|
||||
? StringTraits.NullTerminator
|
||||
: StringTraits.DirectorySeparator;
|
||||
|
||||
Result rc = new U8StringBuilder(sfPath.Str).Append(rootPath).Append(endingSeparator).ToSfPath();
|
||||
var sb = new U8StringBuilder(sfPath.Str);
|
||||
Result rc = sb.Append(rootPath).Append(endingSeparator).ToSfPath();
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
|
@ -43,9 +43,10 @@ namespace LibHac.Fs.Shim
|
||||
if (nameBuffer.Length < requiredNameBufferSize)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
// ReSharper disable once RedundantAssignment
|
||||
int size = new U8StringBuilder(nameBuffer).Append(HostRootFileSystemPath).Append(_path.Str).Length;
|
||||
Debug.Assert(size == requiredNameBufferSize - 1);
|
||||
var sb = new U8StringBuilder(nameBuffer);
|
||||
sb.Append(HostRootFileSystemPath).Append(_path.Str);
|
||||
|
||||
Debug.Assert(sb.Length == requiredNameBufferSize - 1);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
@ -26,9 +26,10 @@ namespace LibHac.FsSystem
|
||||
{
|
||||
fsPath = new FsPath();
|
||||
|
||||
U8StringBuilder builder = new U8StringBuilder(fsPath.Str).Append(path);
|
||||
var sb = new U8StringBuilder(fsPath.Str);
|
||||
bool overflowed = sb.Append(path).Overflowed;
|
||||
|
||||
return builder.Overflowed ? ResultFs.TooLongPath.Log() : Result.Success;
|
||||
return overflowed ? ResultFs.TooLongPath.Log() : Result.Success;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -1,38 +1,70 @@
|
||||
using LibHac.Fs;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsService;
|
||||
using LibHac.FsService.Creators;
|
||||
using LibHac.Sm;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
public class Horizon
|
||||
{
|
||||
internal ITimeSpanGenerator Time { get; }
|
||||
|
||||
public FileSystemClient Fs { get; }
|
||||
public FileSystemServer FsSrv { get; private set; }
|
||||
private FileSystemServer FileSystemServer { get; set; }
|
||||
internal ServiceManager ServiceManager { get; }
|
||||
|
||||
private readonly object _initLocker = new object();
|
||||
|
||||
public Horizon(ITimeSpanGenerator timer)
|
||||
{
|
||||
Time = timer;
|
||||
Time = timer ?? new StopWatchTimeSpanGenerator();
|
||||
ServiceManager = new ServiceManager(this);
|
||||
}
|
||||
|
||||
Fs = new FileSystemClient(timer);
|
||||
public Horizon(ITimeSpanGenerator timer, FileSystemServer fsServer)
|
||||
{
|
||||
Time = timer ?? new StopWatchTimeSpanGenerator();
|
||||
FileSystemServer = fsServer;
|
||||
ServiceManager = new ServiceManager(this);
|
||||
}
|
||||
|
||||
private Result OpenFileSystemClient(out FileSystemClient client)
|
||||
{
|
||||
if (FileSystemServer is null)
|
||||
{
|
||||
client = default;
|
||||
return ResultLibHac.ServiceNotInitialized.Log();
|
||||
}
|
||||
|
||||
client = FileSystemServer.CreateFileSystemClient();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateHorizonClient(out HorizonClient client)
|
||||
{
|
||||
Result rc = OpenFileSystemClient(out FileSystemClient fsClient);
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
client = default;
|
||||
return rc;
|
||||
}
|
||||
|
||||
client = new HorizonClient(this, fsClient);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void InitializeFileSystemServer(FileSystemCreators fsCreators, IDeviceOperator deviceOperator)
|
||||
{
|
||||
if (FsSrv != null) return;
|
||||
if (FileSystemServer != null) return;
|
||||
|
||||
lock (_initLocker)
|
||||
{
|
||||
if (FsSrv != null) return;
|
||||
if (FileSystemServer != null) return;
|
||||
|
||||
var config = new FileSystemServerConfig();
|
||||
config.FsCreators = fsCreators;
|
||||
config.DeviceOperator = deviceOperator;
|
||||
|
||||
FsSrv = new FileSystemServer(config);
|
||||
FileSystemServer = new FileSystemServer(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
src/LibHac/HorizonClient.cs
Normal file
33
src/LibHac/HorizonClient.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using LibHac.Arp;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Sm;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
public class HorizonClient
|
||||
{
|
||||
private Horizon Horizon { get; }
|
||||
|
||||
private Lazy<ArpClient> ArpLazy { get; }
|
||||
|
||||
public FileSystemClient Fs { get; }
|
||||
public ServiceManagerClient Sm { get; }
|
||||
public ArpClient Arp => ArpLazy.Value;
|
||||
|
||||
internal HorizonClient(Horizon horizon, FileSystemClient fsClient)
|
||||
{
|
||||
Horizon = horizon;
|
||||
|
||||
Fs = fsClient;
|
||||
Sm = new ServiceManagerClient(horizon.ServiceManager);
|
||||
|
||||
ArpLazy = new Lazy<ArpClient>(InitArpClient, true);
|
||||
}
|
||||
|
||||
private ArpClient InitArpClient()
|
||||
{
|
||||
return new ArpClient(this);
|
||||
}
|
||||
}
|
||||
}
|
55
src/LibHac/Sf/ResultSf.cs
Normal file
55
src/LibHac/Sf/ResultSf.cs
Normal file
@ -0,0 +1,55 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// This file was automatically generated.
|
||||
// Changes to this file will be lost when the file is regenerated.
|
||||
//
|
||||
// To change this file, modify /build/CodeGen/results.csv at the root of this
|
||||
// repo and run the build script.
|
||||
//
|
||||
// The script can be run with the "codegen" option to run only the
|
||||
// code generation portion of the build.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Sf
|
||||
{
|
||||
public static class ResultSf
|
||||
{
|
||||
public const int ModuleSf = 10;
|
||||
|
||||
/// <summary>Error code: 2010-0001; Inner value: 0x20a</summary>
|
||||
public static Result.Base NotSupported => new Result.Base(ModuleSf, 1);
|
||||
/// <summary>Error code: 2010-0003; Inner value: 0x60a</summary>
|
||||
public static Result.Base PreconditionViolation => new Result.Base(ModuleSf, 3);
|
||||
/// <summary>Error code: 2010-0202; Inner value: 0x1940a</summary>
|
||||
public static Result.Base InvalidHeaderSize => new Result.Base(ModuleSf, 202);
|
||||
/// <summary>Error code: 2010-0211; Inner value: 0x1a60a</summary>
|
||||
public static Result.Base InvalidInHeader => new Result.Base(ModuleSf, 211);
|
||||
/// <summary>Error code: 2010-0221; Inner value: 0x1ba0a</summary>
|
||||
public static Result.Base UnknownCommandId => new Result.Base(ModuleSf, 221);
|
||||
/// <summary>Error code: 2010-0232; Inner value: 0x1d00a</summary>
|
||||
public static Result.Base InvalidOutRawSize => new Result.Base(ModuleSf, 232);
|
||||
/// <summary>Error code: 2010-0235; Inner value: 0x1d60a</summary>
|
||||
public static Result.Base InvalidNumInObjects => new Result.Base(ModuleSf, 235);
|
||||
/// <summary>Error code: 2010-0236; Inner value: 0x1d80a</summary>
|
||||
public static Result.Base InvalidNumOutObjects => new Result.Base(ModuleSf, 236);
|
||||
/// <summary>Error code: 2010-0239; Inner value: 0x1de0a</summary>
|
||||
public static Result.Base InvalidInObject => new Result.Base(ModuleSf, 239);
|
||||
/// <summary>Error code: 2010-0261; Inner value: 0x20a0a</summary>
|
||||
public static Result.Base TargetNotFound => new Result.Base(ModuleSf, 261);
|
||||
/// <summary>Error code: 2010-0301; Inner value: 0x25a0a</summary>
|
||||
public static Result.Base OutOfDomainEntries => new Result.Base(ModuleSf, 301);
|
||||
|
||||
/// <summary>Error code: 2010-0800; Range: 800-899; Inner value: 0x6400a</summary>
|
||||
public static Result.Base RequestContextChanged { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSf, 800, 899); }
|
||||
/// <summary>Error code: 2010-0801; Range: 801-809; Inner value: 0x6420a</summary>
|
||||
public static Result.Base RequestInvalidated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSf, 801, 809); }
|
||||
/// <summary>Error code: 2010-0802; Inner value: 0x6440a</summary>
|
||||
public static Result.Base RequestInvalidatedByUser => new Result.Base(ModuleSf, 802);
|
||||
|
||||
/// <summary>Error code: 2010-0811; Range: 811-819; Inner value: 0x6560a</summary>
|
||||
public static Result.Base RequestDeferred { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSf, 811, 819); }
|
||||
/// <summary>Error code: 2010-0812; Inner value: 0x6580a</summary>
|
||||
public static Result.Base RequestDeferredByUser => new Result.Base(ModuleSf, 812);
|
||||
}
|
||||
}
|
37
src/LibHac/Sm/ResultSm.cs
Normal file
37
src/LibHac/Sm/ResultSm.cs
Normal file
@ -0,0 +1,37 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// This file was automatically generated.
|
||||
// Changes to this file will be lost when the file is regenerated.
|
||||
//
|
||||
// To change this file, modify /build/CodeGen/results.csv at the root of this
|
||||
// repo and run the build script.
|
||||
//
|
||||
// The script can be run with the "codegen" option to run only the
|
||||
// code generation portion of the build.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace LibHac.Sm
|
||||
{
|
||||
public static class ResultSm
|
||||
{
|
||||
public const int ModuleSm = 21;
|
||||
|
||||
/// <summary>Error code: 2021-0001; Inner value: 0x215</summary>
|
||||
public static Result.Base OutOfProcesses => new Result.Base(ModuleSm, 1);
|
||||
/// <summary>Error code: 2021-0002; Inner value: 0x415</summary>
|
||||
public static Result.Base InvalidClient => new Result.Base(ModuleSm, 2);
|
||||
/// <summary>Error code: 2021-0003; Inner value: 0x615</summary>
|
||||
public static Result.Base OutOfSessions => new Result.Base(ModuleSm, 3);
|
||||
/// <summary>Error code: 2021-0004; Inner value: 0x815</summary>
|
||||
public static Result.Base AlreadyRegistered => new Result.Base(ModuleSm, 4);
|
||||
/// <summary>Error code: 2021-0005; Inner value: 0xa15</summary>
|
||||
public static Result.Base OutOfServices => new Result.Base(ModuleSm, 5);
|
||||
/// <summary>Error code: 2021-0006; Inner value: 0xc15</summary>
|
||||
public static Result.Base InvalidServiceName => new Result.Base(ModuleSm, 6);
|
||||
/// <summary>Error code: 2021-0007; Inner value: 0xe15</summary>
|
||||
public static Result.Base NotRegistered => new Result.Base(ModuleSm, 7);
|
||||
/// <summary>Error code: 2021-0008; Inner value: 0x1015</summary>
|
||||
public static Result.Base NotAllowed => new Result.Base(ModuleSm, 8);
|
||||
/// <summary>Error code: 2021-0009; Inner value: 0x1215</summary>
|
||||
public static Result.Base TooLargeAccessControl => new Result.Base(ModuleSm, 9);
|
||||
}
|
||||
}
|
96
src/LibHac/Sm/ServiceManager.cs
Normal file
96
src/LibHac/Sm/ServiceManager.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Sf;
|
||||
|
||||
namespace LibHac.Sm
|
||||
{
|
||||
// This is basically a makeshift service manager that doesn't do anything
|
||||
// other than keep service objects for now. It's just here so other stuff
|
||||
// isn't blocked waiting for something better.
|
||||
internal class ServiceManager
|
||||
{
|
||||
private Horizon Horizon { get; }
|
||||
private Dictionary<ServiceName, object> Services { get; } = new Dictionary<ServiceName, object>();
|
||||
|
||||
public ServiceManager(Horizon horizon)
|
||||
{
|
||||
Horizon = horizon;
|
||||
}
|
||||
|
||||
internal Result GetService(out object serviceObject, ServiceName serviceName)
|
||||
{
|
||||
serviceObject = default;
|
||||
|
||||
Result rc = ValidateServiceName(serviceName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!Services.TryGetValue(serviceName, out serviceObject))
|
||||
{
|
||||
return ResultSf.RequestDeferredByUser.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
internal Result RegisterService(object serviceObject, ServiceName serviceName)
|
||||
{
|
||||
Result rc = ValidateServiceName(serviceName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!Services.TryAdd(serviceName, serviceObject))
|
||||
{
|
||||
return ResultSm.AlreadyRegistered.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
internal Result UnregisterService(ServiceName serviceName)
|
||||
{
|
||||
Result rc = ValidateServiceName(serviceName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!Services.Remove(serviceName, out object service))
|
||||
{
|
||||
return ResultSm.NotRegistered.Log();
|
||||
}
|
||||
|
||||
if (service is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result ValidateServiceName(ServiceName name)
|
||||
{
|
||||
// Service names must be non-empty.
|
||||
if (name.Name == 0)
|
||||
return ResultSm.InvalidServiceName.Log();
|
||||
|
||||
// Get name length.
|
||||
int nameLen;
|
||||
for (nameLen = 1; nameLen < Unsafe.SizeOf<ServiceName>(); nameLen++)
|
||||
{
|
||||
if (SpanHelpers.AsReadOnlyByteSpan(ref name)[nameLen] == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Names must be all-zero after they end.
|
||||
for (; nameLen < Unsafe.SizeOf<ServiceName>(); nameLen++)
|
||||
{
|
||||
if (SpanHelpers.AsReadOnlyByteSpan(ref name)[nameLen] != 0)
|
||||
{
|
||||
return ResultSm.InvalidServiceName.Log();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
42
src/LibHac/Sm/ServiceManagerClient.cs
Normal file
42
src/LibHac/Sm/ServiceManagerClient.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Sm
|
||||
{
|
||||
public class ServiceManagerClient
|
||||
{
|
||||
private ServiceManager Server { get; }
|
||||
|
||||
internal ServiceManagerClient(ServiceManager server)
|
||||
{
|
||||
Server = server;
|
||||
}
|
||||
|
||||
public Result GetService<T>(out T serviceObject, ReadOnlySpan<char> name)
|
||||
{
|
||||
Result rc = Server.GetService(out object service, ServiceName.Encode(name));
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
serviceObject = default;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (service is T typedService)
|
||||
{
|
||||
serviceObject = typedService;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("The service object is not of the specified type.");
|
||||
}
|
||||
|
||||
public Result RegisterService(object serviceObject, ReadOnlySpan<char> name)
|
||||
{
|
||||
return Server.RegisterService(serviceObject, ServiceName.Encode(name));
|
||||
}
|
||||
|
||||
public Result UnregisterService(ReadOnlySpan<char> name)
|
||||
{
|
||||
return Server.UnregisterService(ServiceName.Encode(name));
|
||||
}
|
||||
}
|
||||
}
|
41
src/LibHac/Sm/ServiceName.cs
Normal file
41
src/LibHac/Sm/ServiceName.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Sm
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public readonly struct ServiceName : IEquatable<ServiceName>
|
||||
{
|
||||
private const int MaxLength = 8;
|
||||
|
||||
public readonly ulong Name;
|
||||
|
||||
public static ServiceName Encode(ReadOnlySpan<char> name)
|
||||
{
|
||||
var outName = new ServiceName();
|
||||
int length = Math.Min(MaxLength, name.Length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
SpanHelpers.AsByteSpan(ref outName)[i] = (byte)name[i];
|
||||
}
|
||||
|
||||
return outName;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is ServiceName name && Equals(name);
|
||||
public bool Equals(ServiceName other) => Name == other.Name;
|
||||
|
||||
public override int GetHashCode() => Name.GetHashCode();
|
||||
|
||||
public static bool operator ==(ServiceName left, ServiceName right) => left.Equals(right);
|
||||
public static bool operator !=(ServiceName left, ServiceName right) => !(left == right);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
ulong name = Name;
|
||||
return StringUtils.Utf8ZToString(SpanHelpers.AsReadOnlyByteSpan(ref name));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace hactoolnet
|
||||
@ -93,6 +94,6 @@ namespace hactoolnet
|
||||
public Options Options;
|
||||
public Keyset Keyset;
|
||||
public ProgressBar Logger;
|
||||
public Horizon Horizon;
|
||||
public FileSystemClient FsClient;
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace hactoolnet
|
||||
|
||||
if (ctx.Options.SectionOutDir[i] != null)
|
||||
{
|
||||
FileSystemClient fs = ctx.Horizon.Fs;
|
||||
FileSystemClient fs = ctx.FsClient;
|
||||
|
||||
string mountName = $"section{i}";
|
||||
|
||||
@ -95,7 +95,7 @@ namespace hactoolnet
|
||||
|
||||
if (ctx.Options.RomfsOutDir != null)
|
||||
{
|
||||
FileSystemClient fs = ctx.Horizon.Fs;
|
||||
FileSystemClient fs = ctx.FsClient;
|
||||
|
||||
fs.Register("rom".ToU8Span(), OpenFileSystemByType(NcaSectionType.Data));
|
||||
fs.Register("output".ToU8Span(), new LocalFileSystem(ctx.Options.RomfsOutDir));
|
||||
@ -152,7 +152,7 @@ namespace hactoolnet
|
||||
|
||||
if (ctx.Options.ExefsOutDir != null)
|
||||
{
|
||||
FileSystemClient fs = ctx.Horizon.Fs;
|
||||
FileSystemClient fs = ctx.FsClient;
|
||||
|
||||
fs.Register("code".ToU8Span(), OpenFileSystemByType(NcaSectionType.Code));
|
||||
fs.Register("output".ToU8Span(), new LocalFileSystem(ctx.Options.ExefsOutDir));
|
||||
@ -232,10 +232,12 @@ namespace hactoolnet
|
||||
PrintItem(sb, colLen, $"NPDM Signature{nca.VerifySignature2().GetValidityString()}:", nca.Header.Signature2.ToArray());
|
||||
PrintItem(sb, colLen, "Content Size:", $"0x{nca.Header.NcaSize:x12}");
|
||||
PrintItem(sb, colLen, "TitleID:", $"{nca.Header.TitleId:X16}");
|
||||
if (nca.CanOpenSection(NcaSectionType.Code)) {
|
||||
if (nca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.None);
|
||||
Result r = fs.OpenFile(out IFile file, "/main.npdm".ToU8String(), OpenMode.Read);
|
||||
if (r.IsSuccess()) {
|
||||
if (r.IsSuccess())
|
||||
{
|
||||
var npdm = new NpdmBinary(file.AsStream(), null);
|
||||
PrintItem(sb, colLen, "Title Name:", npdm.TitleName);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace hactoolnet
|
||||
bool signNeeded = ctx.Options.SignSave;
|
||||
|
||||
var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true);
|
||||
FileSystemClient fs = ctx.Horizon.Fs;
|
||||
FileSystemClient fs = ctx.FsClient;
|
||||
|
||||
fs.Register("save".ToU8Span(), save);
|
||||
|
||||
|
@ -59,17 +59,17 @@ namespace hactoolnet
|
||||
using (var logger = new ProgressBar())
|
||||
{
|
||||
ctx.Logger = logger;
|
||||
ctx.Horizon = new Horizon(new StopWatchTimeSpanGenerator());
|
||||
ctx.FsClient = new FileSystemClient(new StopWatchTimeSpanGenerator());
|
||||
|
||||
if (ctx.Options.AccessLog != null)
|
||||
{
|
||||
logWriter = new StreamWriter(ctx.Options.AccessLog);
|
||||
var accessLog = new TextWriterAccessLog(logWriter);
|
||||
|
||||
ctx.Horizon.Fs.SetAccessLogTarget(AccessLogTarget.All);
|
||||
ctx.Horizon.Fs.SetGlobalAccessLogMode(GlobalAccessLogMode.Log);
|
||||
ctx.FsClient.SetAccessLogTarget(AccessLogTarget.All);
|
||||
ctx.FsClient.SetGlobalAccessLogMode(GlobalAccessLogMode.Log);
|
||||
|
||||
ctx.Horizon.Fs.SetAccessLogObject(accessLog);
|
||||
ctx.FsClient.SetAccessLogObject(accessLog);
|
||||
}
|
||||
|
||||
if (ctx.Options.ResultLog != null)
|
||||
|
Loading…
x
Reference in New Issue
Block a user