diff --git a/build/CodeGen/result_modules.csv b/build/CodeGen/result_modules.csv index 3b0173b6..ac03ac76 100644 --- a/build/CodeGen/result_modules.csv +++ b/build/CodeGen/result_modules.csv @@ -1,7 +1,9 @@ Name,Index Fs,2 Loader,9 +Sf,10 Kvdb,20 +Sm,21 Sdmmc,24 Bcat,122 LibHac,428 \ No newline at end of file diff --git a/build/CodeGen/result_paths.csv b/build/CodeGen/result_paths.csv index 436ef9c3..4004fe98 100644 --- a/build/CodeGen/result_paths.csv +++ b/build/CodeGen/result_paths.csv @@ -1,7 +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 \ No newline at end of file diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 13eab837..110e62c5 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -268,12 +268,44 @@ 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, diff --git a/src/LibHac/ApplicationId.cs b/src/LibHac/ApplicationId.cs new file mode 100644 index 00000000..c00d623a --- /dev/null +++ b/src/LibHac/ApplicationId.cs @@ -0,0 +1,20 @@ +using System; + +namespace LibHac +{ + public readonly struct ApplicationId : IEquatable + { + 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); + } +} diff --git a/src/LibHac/Arp/ApplicationLaunchProperty.cs b/src/LibHac/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 00000000..57e5ef21 --- /dev/null +++ b/src/LibHac/Arp/ApplicationLaunchProperty.cs @@ -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; + } +} diff --git a/src/LibHac/Arp/ArpClient.cs b/src/LibHac/Arp/ArpClient.cs new file mode 100644 index 00000000..71cc6684 --- /dev/null +++ b/src/LibHac/Arp/ArpClient.cs @@ -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; + } + } + } +} diff --git a/src/LibHac/Arp/Impl/IReader.cs b/src/LibHac/Arp/Impl/IReader.cs new file mode 100644 index 00000000..3a1ee9aa --- /dev/null +++ b/src/LibHac/Arp/Impl/IReader.cs @@ -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); + } +} diff --git a/src/LibHac/Bcat/BcatServer.cs b/src/LibHac/Bcat/BcatServer.cs index b2c13c9e..1ddf0c96 100644 --- a/src/LibHac/Bcat/BcatServer.cs +++ b/src/LibHac/Bcat/BcatServer.cs @@ -2,7 +2,6 @@ using LibHac.Bcat.Detail.Ipc; using LibHac.Bcat.Detail.Service; using LibHac.Bcat.Detail.Service.Core; -using LibHac.Common; using LibHac.Fs; namespace LibHac.Bcat @@ -11,42 +10,52 @@ namespace LibHac.Bcat { private const int ServiceTypeCount = 4; - private Horizon ServiceManager { get; } + internal HorizonClient Hos { get; } private ServiceCreator[] ServiceCreators { get; } = new ServiceCreator[ServiceTypeCount]; + private readonly object _bcatServiceInitLocker = new object(); private readonly object _storageManagerInitLocker = new object(); - private readonly object _fsInitLocker = new object(); private DeliveryCacheStorageManager StorageManager { get; set; } - private FileSystemClient FsClient { get; set; } - public BcatServer(Horizon horizon) + public BcatServer(HorizonClient horizonClient) { - ServiceManager = horizon; + Hos = horizonClient; - InitServiceCreator(BcatServiceType.BcatU, AccessControl.Bit1); - InitServiceCreator(BcatServiceType.BcatS, AccessControl.Bit2); - InitServiceCreator(BcatServiceType.BcatM, AccessControl.Bit2 | AccessControl.Bit3); - InitServiceCreator(BcatServiceType.BcatA, AccessControl.All); + 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); } - public Result GetServiceCreator(out IServiceCreator serviceCreator, BcatServiceType type) + private void InitBcatService(BcatServiceType type, string name, AccessControl accessControl) { - if ((uint)type >= ServiceTypeCount) + InitServiceCreator(type, name, accessControl); + + IServiceCreator service = GetServiceCreator(type); + + Result rc = Hos.Sm.RegisterService(service, name); + if (rc.IsFailure()) { - serviceCreator = default; - return ResultLibHac.ArgumentOutOfRange.Log(); + throw new HorizonResultException(rc, "Abort"); } - - serviceCreator = ServiceCreators[(int)type]; - return Result.Success; } - private void InitServiceCreator(BcatServiceType type, AccessControl accessControl) + 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); - ServiceCreators[(int)type] = new ServiceCreator(this, type, accessControl); + return ServiceCreators[(int)type]; } internal DeliveryCacheStorageManager GetStorageManager() @@ -56,7 +65,7 @@ namespace LibHac.Bcat internal FileSystemClient GetFsClient() { - return FsClient ?? InitFsClient(); + return Hos.Fs; } private DeliveryCacheStorageManager InitStorageManager() @@ -72,26 +81,5 @@ namespace LibHac.Bcat return StorageManager; } } - - private FileSystemClient InitFsClient() - { - lock (_fsInitLocker) - { - if (FsClient != null) - { - return FsClient; - } - - Result rc = ServiceManager.OpenFileSystemClient(out FileSystemClient fsClient); - - if (!rc.IsSuccess()) - throw new HorizonResultException(rc, "Abort"); - - fsClient.SetAccessLogTarget(AccessLogTarget.All); - - FsClient = fsClient; - return fsClient; - } - } } } diff --git a/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs b/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs index 39d9f4c6..015d39ba 100644 --- a/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs +++ b/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs @@ -4,7 +4,10 @@ namespace LibHac.Bcat.Detail.Ipc { public interface IServiceCreator { + Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, + ulong processId); + Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service, - TitleId applicationId); + ApplicationId applicationId); } } diff --git a/src/LibHac/Bcat/Detail/Service/AccessControl.cs b/src/LibHac/Bcat/Detail/Service/AccessControl.cs index 643fa3e8..007f1194 100644 --- a/src/LibHac/Bcat/Detail/Service/AccessControl.cs +++ b/src/LibHac/Bcat/Detail/Service/AccessControl.cs @@ -6,11 +6,10 @@ namespace LibHac.Bcat.Detail.Service internal enum AccessControl { None = 0, - Bit0 = 1 << 0, - Bit1 = 1 << 1, - Bit2 = 1 << 2, - Bit3 = 1 << 3, - Bit4 = 1 << 4, + MountOwnDeliveryCacheStorage = 1 << 1, + MountOthersDeliveryCacheStorage = 1 << 2, + DeliveryTaskManagement = 1 << 3, + Debug = 1 << 4, All = ~0 } } diff --git a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs index 2d219f1a..66b0ee13 100644 --- a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs +++ b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheStorageManager.cs @@ -159,6 +159,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetPassphrasePath(Span pathBuffer, ulong applicationId) { + // returns "mount:/passphrase.bin" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -169,6 +170,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetDeliveryListPath(Span pathBuffer, ulong applicationId) { + // returns "mount:/list.msgpack" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -179,6 +181,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetEtagFilePath(Span pathBuffer, ulong applicationId) { + // returns "mount:/etag.bin" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -189,6 +192,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetNaRequiredPath(Span pathBuffer, ulong applicationId) { + // returns "mount:/na_required" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -199,6 +203,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetIndexLockPath(Span pathBuffer, ulong applicationId) { + // returns "mount:/index.lock" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -210,6 +215,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetFilePath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName, ref FileName fileName) { + // returns "mount:/directories/%s/files/%s", directoryName, fileName lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -224,6 +230,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetFilesMetaPath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName) { + // returns "mount:/directories/%s/files.meta", directoryName lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -237,6 +244,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetDirectoriesPath(Span pathBuffer, ulong applicationId) { + // returns "mount:/directories" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -247,6 +255,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetDirectoryPath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName) { + // returns "mount:/directories/%s", directoryName lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -259,6 +268,7 @@ namespace LibHac.Bcat.Detail.Service.Core public void GetDirectoriesMetaPath(Span pathBuffer, ulong applicationId) { + // returns "mount:/directories.meta" lock (_locker) { var sb = new U8StringBuilder(pathBuffer); @@ -304,9 +314,11 @@ namespace LibHac.Bcat.Detail.Service.Core private int FindEntry(ulong applicationId) { - for (int i = 0; i < Entries.Length; i++) + Entry[] entries = Entries; + + for (int i = 0; i < entries.Length; i++) { - if (Entries[i].ApplicationId == applicationId) + if (entries[i].ApplicationId == applicationId) { return i; } diff --git a/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs b/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs index eaf5039d..423e08ad 100644 --- a/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs +++ b/src/LibHac/Bcat/Detail/Service/ServiceCreator.cs @@ -1,5 +1,5 @@ -using LibHac.Bcat.Detail.Ipc; -using LibHac.Ncm; +using LibHac.Arp; +using LibHac.Bcat.Detail.Ipc; namespace LibHac.Bcat.Detail.Service { @@ -8,29 +8,44 @@ namespace LibHac.Bcat.Detail.Service private BcatServer Server { get; } // ReSharper disable once UnusedAutoPropertyAccessor.Local - private BcatServiceType ServiceType { get; } + private string ServiceName { get; } private AccessControl AccessControl { get; } - public ServiceCreator(BcatServer server, BcatServiceType type, AccessControl accessControl) + public ServiceCreator(BcatServer server, string serviceName, AccessControl accessControl) { Server = server; - ServiceType = type; + ServiceName = serviceName; AccessControl = accessControl; } - public Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service, - TitleId applicationId) + public Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, ulong processId) { - service = default; + Result rc = Server.Hos.Arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, + processId); - if (!AccessControl.HasFlag(AccessControl.Bit2)) + 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, - TitleId applicationId) + ApplicationId applicationId) { service = default; diff --git a/src/LibHac/Horizon.cs b/src/LibHac/Horizon.cs index f46ac72f..7f052f08 100644 --- a/src/LibHac/Horizon.cs +++ b/src/LibHac/Horizon.cs @@ -1,38 +1,26 @@ -using LibHac.Bcat; -using LibHac.Bcat.Detail.Ipc; -using LibHac.Common; +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 FileSystemServer FileSystemServer { get; private set; } - public BcatServer BcatServer { get; private set; } + private FileSystemServer FileSystemServer { get; set; } + internal ServiceManager ServiceManager { get; } private readonly object _initLocker = new object(); public Horizon(ITimeSpanGenerator timer) { Time = timer ?? new StopWatchTimeSpanGenerator(); + ServiceManager = new ServiceManager(this); } - public Result OpenFileSystemProxyService(out IFileSystemProxy service) - { - if (FileSystemServer is null) - { - service = default; - return ResultLibHac.ServiceNotInitialized.Log(); - } - - service = FileSystemServer.CreateFileSystemProxyService(); - return Result.Success; - } - - public Result OpenFileSystemClient(out FileSystemClient client) + private Result OpenFileSystemClient(out FileSystemClient client) { if (FileSystemServer is null) { @@ -44,32 +32,17 @@ namespace LibHac return Result.Success; } - public Result OpenBcatUService(out IServiceCreator service) => OpenBcatService(out service, BcatServiceType.BcatU); - public Result OpenBcatSService(out IServiceCreator service) => OpenBcatService(out service, BcatServiceType.BcatS); - public Result OpenBcatMService(out IServiceCreator service) => OpenBcatService(out service, BcatServiceType.BcatM); - public Result OpenBcatAService(out IServiceCreator service) => OpenBcatService(out service, BcatServiceType.BcatA); - - private Result OpenBcatService(out IServiceCreator service, BcatServiceType type) + public Result CreateHorizonClient(out HorizonClient client) { - if (BcatServer is null) + Result rc = OpenFileSystemClient(out FileSystemClient fsClient); + if (rc.IsFailure()) { - service = default; - return ResultLibHac.ServiceNotInitialized.Log(); + client = default; + return rc; } - return BcatServer.GetServiceCreator(out service, type); - } - - public void InitializeBcatServer() - { - if (BcatServer != null) return; - - lock (_initLocker) - { - if (BcatServer != null) return; - - BcatServer = new BcatServer(this); - } + client = new HorizonClient(this, fsClient); + return Result.Success; } public void InitializeFileSystemServer(FileSystemCreators fsCreators, IDeviceOperator deviceOperator) diff --git a/src/LibHac/HorizonClient.cs b/src/LibHac/HorizonClient.cs new file mode 100644 index 00000000..16207b76 --- /dev/null +++ b/src/LibHac/HorizonClient.cs @@ -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 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(InitArpClient, true); + } + + private ArpClient InitArpClient() + { + return new ArpClient(this); + } + } +} diff --git a/src/LibHac/Sf/ResultSf.cs b/src/LibHac/Sf/ResultSf.cs new file mode 100644 index 00000000..d38b7509 --- /dev/null +++ b/src/LibHac/Sf/ResultSf.cs @@ -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; + + /// Error code: 2010-0001; Inner value: 0x20a + public static Result.Base NotSupported => new Result.Base(ModuleSf, 1); + /// Error code: 2010-0003; Inner value: 0x60a + public static Result.Base PreconditionViolation => new Result.Base(ModuleSf, 3); + /// Error code: 2010-0202; Inner value: 0x1940a + public static Result.Base InvalidHeaderSize => new Result.Base(ModuleSf, 202); + /// Error code: 2010-0211; Inner value: 0x1a60a + public static Result.Base InvalidInHeader => new Result.Base(ModuleSf, 211); + /// Error code: 2010-0221; Inner value: 0x1ba0a + public static Result.Base UnknownCommandId => new Result.Base(ModuleSf, 221); + /// Error code: 2010-0232; Inner value: 0x1d00a + public static Result.Base InvalidOutRawSize => new Result.Base(ModuleSf, 232); + /// Error code: 2010-0235; Inner value: 0x1d60a + public static Result.Base InvalidNumInObjects => new Result.Base(ModuleSf, 235); + /// Error code: 2010-0236; Inner value: 0x1d80a + public static Result.Base InvalidNumOutObjects => new Result.Base(ModuleSf, 236); + /// Error code: 2010-0239; Inner value: 0x1de0a + public static Result.Base InvalidInObject => new Result.Base(ModuleSf, 239); + /// Error code: 2010-0261; Inner value: 0x20a0a + public static Result.Base TargetNotFound => new Result.Base(ModuleSf, 261); + /// Error code: 2010-0301; Inner value: 0x25a0a + public static Result.Base OutOfDomainEntries => new Result.Base(ModuleSf, 301); + + /// Error code: 2010-0800; Range: 800-899; Inner value: 0x6400a + public static Result.Base RequestContextChanged { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSf, 800, 899); } + /// Error code: 2010-0801; Range: 801-809; Inner value: 0x6420a + public static Result.Base RequestInvalidated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSf, 801, 809); } + /// Error code: 2010-0802; Inner value: 0x6440a + public static Result.Base RequestInvalidatedByUser => new Result.Base(ModuleSf, 802); + + /// Error code: 2010-0811; Range: 811-819; Inner value: 0x6560a + public static Result.Base RequestDeferred { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSf, 811, 819); } + /// Error code: 2010-0812; Inner value: 0x6580a + public static Result.Base RequestDeferredByUser => new Result.Base(ModuleSf, 812); + } +} diff --git a/src/LibHac/Sm/ResultSm.cs b/src/LibHac/Sm/ResultSm.cs new file mode 100644 index 00000000..c2248033 --- /dev/null +++ b/src/LibHac/Sm/ResultSm.cs @@ -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; + + /// Error code: 2021-0001; Inner value: 0x215 + public static Result.Base OutOfProcesses => new Result.Base(ModuleSm, 1); + /// Error code: 2021-0002; Inner value: 0x415 + public static Result.Base InvalidClient => new Result.Base(ModuleSm, 2); + /// Error code: 2021-0003; Inner value: 0x615 + public static Result.Base OutOfSessions => new Result.Base(ModuleSm, 3); + /// Error code: 2021-0004; Inner value: 0x815 + public static Result.Base AlreadyRegistered => new Result.Base(ModuleSm, 4); + /// Error code: 2021-0005; Inner value: 0xa15 + public static Result.Base OutOfServices => new Result.Base(ModuleSm, 5); + /// Error code: 2021-0006; Inner value: 0xc15 + public static Result.Base InvalidServiceName => new Result.Base(ModuleSm, 6); + /// Error code: 2021-0007; Inner value: 0xe15 + public static Result.Base NotRegistered => new Result.Base(ModuleSm, 7); + /// Error code: 2021-0008; Inner value: 0x1015 + public static Result.Base NotAllowed => new Result.Base(ModuleSm, 8); + /// Error code: 2021-0009; Inner value: 0x1215 + public static Result.Base TooLargeAccessControl => new Result.Base(ModuleSm, 9); + } +} diff --git a/src/LibHac/Sm/ServiceManager.cs b/src/LibHac/Sm/ServiceManager.cs new file mode 100644 index 00000000..9f37c8d2 --- /dev/null +++ b/src/LibHac/Sm/ServiceManager.cs @@ -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 Services { get; } = new Dictionary(); + + 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(); nameLen++) + { + if (SpanHelpers.AsReadOnlyByteSpan(ref name)[nameLen] == 0) + { + break; + } + } + + // Names must be all-zero after they end. + for (; nameLen < Unsafe.SizeOf(); nameLen++) + { + if (SpanHelpers.AsReadOnlyByteSpan(ref name)[nameLen] != 0) + { + return ResultSm.InvalidServiceName.Log(); + } + } + + return Result.Success; + } + } +} diff --git a/src/LibHac/Sm/ServiceManagerClient.cs b/src/LibHac/Sm/ServiceManagerClient.cs new file mode 100644 index 00000000..0930e6ed --- /dev/null +++ b/src/LibHac/Sm/ServiceManagerClient.cs @@ -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(out T serviceObject, ReadOnlySpan 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 name) + { + return Server.RegisterService(serviceObject, ServiceName.Encode(name)); + } + + public Result UnregisterService(ReadOnlySpan name) + { + return Server.UnregisterService(ServiceName.Encode(name)); + } + } +} diff --git a/src/LibHac/Sm/ServiceName.cs b/src/LibHac/Sm/ServiceName.cs new file mode 100644 index 00000000..7c3deb3a --- /dev/null +++ b/src/LibHac/Sm/ServiceName.cs @@ -0,0 +1,41 @@ +using System; +using System.Diagnostics; +using LibHac.Common; + +namespace LibHac.Sm +{ + [DebuggerDisplay("{ToString()}")] + public readonly struct ServiceName : IEquatable + { + private const int MaxLength = 8; + + public readonly ulong Name; + + public static ServiceName Encode(ReadOnlySpan 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)); + } + } +}