diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 9c8fd9721..5be8f97d0 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -108,11 +108,6 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.LogName = json.value("LogName", config.LogName); config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory); config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory); - - if (json.contains("TempDirectory") && !json["TempDirectory"].is_null()) { - config.TempDirectory = json.value("TempDirectory", config.TempDirectory); - } - config.Language = json.value("Language", config.Language); config.Platform = json.value("Platform", config.Platform); config.GameVersion = json.value("GameVersion", config.GameVersion); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index 308dcab7d..0eeaddeed 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -44,7 +44,6 @@ struct DalamudStartInfo { std::string ConfigurationPath; std::string LogPath; std::string LogName; - std::string TempDirectory; std::string PluginDirectory; std::string AssetDirectory; ClientLanguage Language = ClientLanguage::English; diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index b75256af8..b0ec1cefa 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -122,7 +122,6 @@ static DalamudExpected append_injector_launch_args(std::vector(g_startInfo.LogName) + L"\""); args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert(g_startInfo.PluginDirectory) + L"\""); args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert(g_startInfo.AssetDirectory) + L"\""); - args.emplace_back(L"--dalamud-temp-directory=\"" + unicode::convert(g_startInfo.TempDirectory) + L"\""); args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast(g_startInfo.Language))); args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs)); // NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler @@ -269,7 +268,7 @@ LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex) if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) - return EXCEPTION_CONTINUE_SEARCH; + return EXCEPTION_CONTINUE_SEARCH; } return exception_handler(ex); @@ -298,7 +297,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536)) { hWritePipe.emplace(hWritePipeRaw, &CloseHandle); - + if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle); @@ -316,9 +315,9 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) } // additional information - STARTUPINFOEXW siex{}; + STARTUPINFOEXW siex{}; PROCESS_INFORMATION pi{}; - + siex.StartupInfo.cb = sizeof siex; siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE; @@ -386,7 +385,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) argstr.push_back(L' '); } argstr.pop_back(); - + if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr)) { logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError()); @@ -401,7 +400,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) TRUE, // Set handle inheritance to FALSE EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W) nullptr, // Use parent's environment block - nullptr, // Use parent's starting directory + nullptr, // Use parent's starting directory &siex.StartupInfo, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) )) @@ -417,7 +416,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) } CloseHandle(pi.hThread); - + g_crashhandler_process = pi.hProcess; g_crashhandler_pipe_write = hWritePipe->release(); logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId); diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index 8c66a85ba..a0d7f8b0b 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -34,12 +34,6 @@ public record DalamudStartInfo /// public string? ConfigurationPath { get; set; } - /// - /// Gets or sets the directory for temporary files. This directory needs to exist and be writable to the user. - /// It should also be predictable and easy for launchers to find. - /// - public string? TempDirectory { get; set; } - /// /// Gets or sets the path of the log files. /// diff --git a/Dalamud.Injector/Program.cs b/Dalamud.Injector/Program.cs index 13fcacef2..e224791e6 100644 --- a/Dalamud.Injector/Program.cs +++ b/Dalamud.Injector/Program.cs @@ -291,7 +291,6 @@ namespace Dalamud.Injector var configurationPath = startInfo.ConfigurationPath; var pluginDirectory = startInfo.PluginDirectory; var assetDirectory = startInfo.AssetDirectory; - var tempDirectory = startInfo.TempDirectory; var delayInitializeMs = startInfo.DelayInitializeMs; var logName = startInfo.LogName; var logPath = startInfo.LogPath; @@ -322,10 +321,6 @@ namespace Dalamud.Injector { assetDirectory = args[i][key.Length..]; } - else if (args[i].StartsWith(key = "--dalamud-temp-directory=")) - { - tempDirectory = args[i][key.Length..]; - } else if (args[i].StartsWith(key = "--dalamud-delay-initialize=")) { delayInitializeMs = int.Parse(args[i][key.Length..]); @@ -438,7 +433,6 @@ namespace Dalamud.Injector startInfo.ConfigurationPath = configurationPath; startInfo.PluginDirectory = pluginDirectory; startInfo.AssetDirectory = assetDirectory; - startInfo.TempDirectory = tempDirectory; startInfo.Language = clientLanguage; startInfo.Platform = platform; startInfo.DelayInitializeMs = delayInitializeMs; diff --git a/Dalamud.Test/Rpc/DalamudUriTests.cs b/Dalamud.Test/Pipes/DalamudUriTests.cs similarity index 98% rename from Dalamud.Test/Rpc/DalamudUriTests.cs rename to Dalamud.Test/Pipes/DalamudUriTests.cs index b371a5698..4977f3814 100644 --- a/Dalamud.Test/Rpc/DalamudUriTests.cs +++ b/Dalamud.Test/Pipes/DalamudUriTests.cs @@ -1,11 +1,10 @@ using System; using System.Linq; -using Dalamud.Networking.Rpc.Model; - +using Dalamud.Networking.Pipes; using Xunit; -namespace Dalamud.Test.Rpc +namespace Dalamud.Test.Pipes { public class DalamudUriTests { diff --git a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs index ec1c51a12..415e1b169 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs @@ -1,6 +1,4 @@ -using Dalamud.Plugin.Services; - -namespace Dalamud.Game.Addon.Events; +namespace Dalamud.Game.Addon.Events; /// /// AddonEventManager memory address resolver. diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs index bc9e4b639..854d666fd 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs @@ -1,4 +1,4 @@ -using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Lifecycle; diff --git a/Dalamud/Game/BaseAddressResolver.cs b/Dalamud/Game/BaseAddressResolver.cs index d41b1d9d8..4133117d7 100644 --- a/Dalamud/Game/BaseAddressResolver.cs +++ b/Dalamud/Game/BaseAddressResolver.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using Dalamud.Plugin.Services; - namespace Dalamud.Game; /// diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 53774121d..2fc859d09 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -1,5 +1,3 @@ -using Dalamud.Plugin.Services; - namespace Dalamud.Game.ClientState; /// diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index a6432e242..f81154693 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -1,7 +1,6 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; -using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; diff --git a/Dalamud/Game/Config/GameConfigAddressResolver.cs b/Dalamud/Game/Config/GameConfigAddressResolver.cs index e03f4f40b..2491c4033 100644 --- a/Dalamud/Game/Config/GameConfigAddressResolver.cs +++ b/Dalamud/Game/Config/GameConfigAddressResolver.cs @@ -1,6 +1,4 @@ -using Dalamud.Plugin.Services; - -namespace Dalamud.Game.Config; +namespace Dalamud.Game.Config; /// /// Game config system address resolver. diff --git a/Dalamud/Game/DutyState/DutyStateAddressResolver.cs b/Dalamud/Game/DutyState/DutyStateAddressResolver.cs index 480b699a0..1bca93efb 100644 --- a/Dalamud/Game/DutyState/DutyStateAddressResolver.cs +++ b/Dalamud/Game/DutyState/DutyStateAddressResolver.cs @@ -1,5 +1,3 @@ -using Dalamud.Plugin.Services; - namespace Dalamud.Game.DutyState; /// diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 1295e2047..92b89c5a9 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -1,5 +1,3 @@ -using Dalamud.Plugin.Services; - namespace Dalamud.Game.Gui; /// diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs index f97450c28..450e1fa9f 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs @@ -1,5 +1,3 @@ -using Dalamud.Plugin.Services; - namespace Dalamud.Game.Gui.NamePlate; /// diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs index 48abc2d97..de92f7c10 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -1,5 +1,3 @@ -using Dalamud.Plugin.Services; - namespace Dalamud.Game.Network; /// diff --git a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs index 34c071556..9cd46f798 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs @@ -1,6 +1,4 @@ -using Dalamud.Plugin.Services; - -namespace Dalamud.Game.Network.Internal; +namespace Dalamud.Game.Network.Internal; /// /// Internal address resolver for the network handlers. diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 262e98fa5..c8a371aee 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -8,8 +8,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; -using Dalamud.Plugin.Services; - using Iced.Intel; using Newtonsoft.Json; using Serilog; diff --git a/Dalamud/Game/TargetSigScanner.cs b/Dalamud/Game/TargetSigScanner.cs index 540d0ea47..f60c32d9a 100644 --- a/Dalamud/Game/TargetSigScanner.cs +++ b/Dalamud/Game/TargetSigScanner.cs @@ -1,9 +1,8 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using Dalamud.IoC; using Dalamud.IoC.Internal; -using Dalamud.Plugin.Services; namespace Dalamud.Game; diff --git a/Dalamud/Networking/Rpc/Service/Links/PluginLinkHandler.cs b/Dalamud/Networking/Pipes/Api/PluginLinkHandler.cs similarity index 91% rename from Dalamud/Networking/Rpc/Service/Links/PluginLinkHandler.cs rename to Dalamud/Networking/Pipes/Api/PluginLinkHandler.cs index 3b7f18437..78fbb0d82 100644 --- a/Dalamud/Networking/Rpc/Service/Links/PluginLinkHandler.cs +++ b/Dalamud/Networking/Pipes/Api/PluginLinkHandler.cs @@ -3,13 +3,12 @@ using Dalamud.Console; using Dalamud.IoC; using Dalamud.IoC.Internal; -using Dalamud.Networking.Rpc.Model; +using Dalamud.Networking.Pipes.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; - #pragma warning disable DAL_RPC -namespace Dalamud.Networking.Rpc.Service.Links; +namespace Dalamud.Networking.Pipes.Api; /// [PluginInterface] @@ -45,7 +44,7 @@ public class PluginLinkHandler : IInternalDisposableService, IPluginLinkHandler private void HandleUri(DalamudUri uri) { - var target = uri.Path.Split("/").ElementAtOrDefault(1); + var target = uri.Path.Split("/").FirstOrDefault(); var thisPlugin = ConsoleManagerPluginUtil.GetSanitizedNamespaceName(this.localPlugin.InternalName); if (target == null || !string.Equals(target, thisPlugin, StringComparison.OrdinalIgnoreCase)) { diff --git a/Dalamud/Networking/Rpc/Model/DalamudUri.cs b/Dalamud/Networking/Pipes/DalamudUri.cs similarity index 98% rename from Dalamud/Networking/Rpc/Model/DalamudUri.cs rename to Dalamud/Networking/Pipes/DalamudUri.cs index 852478762..7e639cbbe 100644 --- a/Dalamud/Networking/Rpc/Model/DalamudUri.cs +++ b/Dalamud/Networking/Pipes/DalamudUri.cs @@ -2,7 +2,7 @@ using System.Collections.Specialized; using System.Web; -namespace Dalamud.Networking.Rpc.Model; +namespace Dalamud.Networking.Pipes; /// /// A Dalamud Uri, in the format: diff --git a/Dalamud/Networking/Rpc/Service/ClientHelloService.cs b/Dalamud/Networking/Pipes/Internal/ClientHelloService.cs similarity index 82% rename from Dalamud/Networking/Rpc/Service/ClientHelloService.cs rename to Dalamud/Networking/Pipes/Internal/ClientHelloService.cs index c5a4c851a..9c182561e 100644 --- a/Dalamud/Networking/Rpc/Service/ClientHelloService.cs +++ b/Dalamud/Networking/Pipes/Internal/ClientHelloService.cs @@ -1,14 +1,14 @@ -using System.Diagnostics; -using System.Threading.Tasks; +using System.Threading.Tasks; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState; +using Dalamud.Networking.Pipes.Rpc; using Dalamud.Utility; using Lumina.Excel.Sheets; -namespace Dalamud.Networking.Rpc.Service; +namespace Dalamud.Networking.Pipes.Internal; /// /// A minimal service to respond with information about this client. @@ -40,9 +40,7 @@ internal sealed class ClientHelloService : IInternalDisposableService ApiVersion = "1.0", DalamudVersion = Util.GetScmVersion(), GameVersion = dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown", - ProcessId = Environment.ProcessId, - ProcessStartTime = new DateTimeOffset(Process.GetCurrentProcess().StartTime).ToUnixTimeSeconds(), - ClientState = await this.GetClientIdentifier(), + ClientIdentifier = await this.GetClientIdentifier(), }; } @@ -117,17 +115,7 @@ internal record ClientHelloResponse public string? GameVersion { get; init; } /// - /// Gets the process ID of this client. + /// Gets an identifier for this client. /// - public int? ProcessId { get; init; } - - /// - /// Gets the time this process started. - /// - public long? ProcessStartTime { get; init; } - - /// - /// Gets a state for this client for user display. - /// - public string? ClientState { get; init; } + public string? ClientIdentifier { get; init; } } diff --git a/Dalamud/Networking/Rpc/Service/LinkHandlerService.cs b/Dalamud/Networking/Pipes/Internal/LinkHandlerService.cs similarity index 97% rename from Dalamud/Networking/Rpc/Service/LinkHandlerService.cs rename to Dalamud/Networking/Pipes/Internal/LinkHandlerService.cs index 9fa311ede..3cc4af9f4 100644 --- a/Dalamud/Networking/Rpc/Service/LinkHandlerService.cs +++ b/Dalamud/Networking/Pipes/Internal/LinkHandlerService.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using Dalamud.Logging.Internal; -using Dalamud.Networking.Rpc.Model; +using Dalamud.Networking.Pipes.Rpc; using Dalamud.Utility; -namespace Dalamud.Networking.Rpc.Service; +namespace Dalamud.Networking.Pipes.Internal; /// /// A service responsible for handling Dalamud URIs and dispatching them accordingly. diff --git a/Dalamud/Networking/Pipes/Rpc/PipeRpcHost.cs b/Dalamud/Networking/Pipes/Rpc/PipeRpcHost.cs new file mode 100644 index 000000000..ad1cc72cd --- /dev/null +++ b/Dalamud/Networking/Pipes/Rpc/PipeRpcHost.cs @@ -0,0 +1,167 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO.Pipes; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Logging.Internal; +using Dalamud.Utility; + +namespace Dalamud.Networking.Pipes.Rpc; + +/// +/// Simple multi-client JSON-RPC named pipe host using StreamJsonRpc. +/// +internal class PipeRpcHost : IDisposable +{ + private readonly ModuleLog log = new("RPC/Host"); + + private readonly RpcServiceRegistry registry = new(); + private readonly CancellationTokenSource cts = new(); + private readonly ConcurrentDictionary sessions = new(); + private Task? acceptLoopTask; + + /// + /// Initializes a new instance of the class. + /// + /// The pipe name to create. + public PipeRpcHost(string? pipeName = null) + { + // Default pipe name based on current process ID for uniqueness per Dalamud instance. + this.PipeName = pipeName ?? $"DalamudRPC.{Environment.ProcessId}"; + } + + /// + /// Gets the name of the named pipe this RPC host is using. + /// + public string PipeName { get; } + + /// Adds a local object exposing RPC methods callable by clients. + /// An arbitrary service object that will be introspected to add to RPC. + public void AddService(object service) => this.registry.AddService(service); + + /// + /// Adds a standalone JSON-RPC method callable by clients. + /// + /// The name to add. + /// The delegate that acts as the handler. + public void AddMethod(string name, Delegate handler) => this.registry.AddMethod(name, handler); + + /// Starts accepting client connections. + public void Start() + { + if (this.acceptLoopTask != null) return; + this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning); + } + + /// Invoke an RPC request on a specific client expecting a result. + /// The client ID to invoke. + /// The method to invoke. + /// Any arguments to invoke. + /// An optional return based on the specified RPC. + /// The expected response type. + public Task InvokeClientAsync(Guid clientId, string method, params object[] arguments) + { + if (!this.sessions.TryGetValue(clientId, out var session)) + throw new KeyNotFoundException($"No client {clientId}"); + + return session.Rpc.InvokeAsync(method, arguments); + } + + /// Send a notification to all connected clients (no response expected). + /// The method name to broadcast. + /// The arguments to broadcast. + /// Returns a Task when completed. + public Task BroadcastNotifyAsync(string method, params object[] arguments) + { + var list = this.sessions.Values; + var tasks = new List(list.Count); + foreach (var s in list) + { + tasks.Add(s.Rpc.NotifyAsync(method, arguments)); + } + + return Task.WhenAll(tasks); + } + + /// + /// Gets a list of connected client IDs. + /// + /// Connected client IDs. + public IReadOnlyCollection GetClientIds() => this.sessions.Keys.AsReadOnlyCollection(); + + /// + public void Dispose() + { + this.cts.Cancel(); + this.acceptLoopTask?.Wait(1000); + + foreach (var kv in this.sessions) + { + kv.Value.Dispose(); + } + + this.sessions.Clear(); + this.cts.Dispose(); + this.log.Information("PipeRpcHost disposed ({Pipe})", this.PipeName); + GC.SuppressFinalize(this); + } + + private PipeSecurity BuildPipeSecurity() + { + var ps = new PipeSecurity(); + ps.AddAccessRule(new PipeAccessRule(WindowsIdentity.GetCurrent().User!, PipeAccessRights.FullControl, AccessControlType.Allow)); + + return ps; + } + + private async Task AcceptLoopAsync() + { + this.log.Information("PipeRpcHost starting on pipe {Pipe}", this.PipeName); + var token = this.cts.Token; + var security = this.BuildPipeSecurity(); + + while (!token.IsCancellationRequested) + { + NamedPipeServerStream? server = null; + try + { + server = NamedPipeServerStreamAcl.Create( + this.PipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous, + 65536, + 65536, + security); + + await server.WaitForConnectionAsync(token).ConfigureAwait(false); + + var session = new RpcConnection(server, this.registry); + this.sessions.TryAdd(session.Id, session); + + this.log.Debug("RPC connection created: {Id}", session.Id); + + _ = session.Completion.ContinueWith(t => + { + this.sessions.TryRemove(session.Id, out _); + this.log.Debug("RPC connection removed: {Id}", session.Id); + }, TaskScheduler.Default); + } + catch (OperationCanceledException) + { + server?.Dispose(); + break; + } + catch (Exception ex) + { + server?.Dispose(); + this.log.Error(ex, "Error in pipe accept loop"); + await Task.Delay(500, token).ConfigureAwait(false); + } + } + } +} diff --git a/Dalamud/Networking/Rpc/RpcConnection.cs b/Dalamud/Networking/Pipes/Rpc/RpcConnection.cs similarity index 76% rename from Dalamud/Networking/Rpc/RpcConnection.cs rename to Dalamud/Networking/Pipes/Rpc/RpcConnection.cs index 5288948eb..8e1c3a085 100644 --- a/Dalamud/Networking/Rpc/RpcConnection.cs +++ b/Dalamud/Networking/Pipes/Rpc/RpcConnection.cs @@ -1,37 +1,34 @@ -using System.IO; +using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; -using Dalamud.Networking.Rpc.Service; - using Serilog; - using StreamJsonRpc; -namespace Dalamud.Networking.Rpc; +namespace Dalamud.Networking.Pipes.Rpc; /// -/// A single RPC client session connected via a stream (named pipe or Unix socket). +/// A single RPC client session connected via named pipe. /// internal class RpcConnection : IDisposable { - private readonly Stream stream; + private readonly NamedPipeServerStream pipe; private readonly RpcServiceRegistry registry; private readonly CancellationTokenSource cts = new(); /// /// Initializes a new instance of the class. /// - /// The stream that this connection will handle. + /// The named pipe that this connection will handle. /// A registry of RPC services. - public RpcConnection(Stream stream, RpcServiceRegistry registry) + public RpcConnection(NamedPipeServerStream pipe, RpcServiceRegistry registry) { this.Id = Guid.CreateVersion7(); - this.stream = stream; + this.pipe = pipe; this.registry = registry; var formatter = new JsonMessageFormatter(); - var handler = new HeaderDelimitedMessageHandler(stream, stream, formatter); + var handler = new HeaderDelimitedMessageHandler(pipe, pipe, formatter); this.Rpc = new JsonRpc(handler); this.Rpc.AllowModificationWhileListening = true; @@ -75,11 +72,11 @@ internal class RpcConnection : IDisposable try { - this.stream.Dispose(); + this.pipe.Dispose(); } catch (Exception ex) { - Log.Debug(ex, "Error disposing stream for client {Id}", this.Id); + Log.Debug(ex, "Error disposing pipe for client {Id}", this.Id); } this.cts.Dispose(); diff --git a/Dalamud/Networking/Pipes/Rpc/RpcHostService.cs b/Dalamud/Networking/Pipes/Rpc/RpcHostService.cs new file mode 100644 index 000000000..78df27323 --- /dev/null +++ b/Dalamud/Networking/Pipes/Rpc/RpcHostService.cs @@ -0,0 +1,49 @@ +using Dalamud.Logging.Internal; + +namespace Dalamud.Networking.Pipes.Rpc; + +/// +/// The Dalamud service repsonsible for hosting the RPC. +/// +[ServiceManager.EarlyLoadedService] +internal class RpcHostService : IServiceType, IInternalDisposableService +{ + private readonly ModuleLog log = new("RPC"); + private readonly PipeRpcHost host; + + /// + /// Initializes a new instance of the class. + /// + [ServiceManager.ServiceConstructor] + public RpcHostService() + { + this.host = new PipeRpcHost(); + this.host.Start(); + + this.log.Information("RpcHostService started on pipe {Pipe}", this.host.PipeName); + } + + /// + /// Gets the RPC host to drill down. + /// + public PipeRpcHost Host => this.host; + + /// + /// Add a new service Object to the RPC host. + /// + /// The object to add. + public void AddService(object service) => this.host.AddService(service); + + /// + /// Add a new standalone method to the RPC host. + /// + /// The method name to add. + /// The handler to add. + public void AddMethod(string name, Delegate handler) => this.host.AddMethod(name, handler); + + /// + public void DisposeService() + { + this.host.Dispose(); + } +} diff --git a/Dalamud/Networking/Rpc/RpcServiceRegistry.cs b/Dalamud/Networking/Pipes/Rpc/RpcServiceRegistry.cs similarity index 98% rename from Dalamud/Networking/Rpc/RpcServiceRegistry.cs rename to Dalamud/Networking/Pipes/Rpc/RpcServiceRegistry.cs index 6daea14bf..71037d45e 100644 --- a/Dalamud/Networking/Rpc/RpcServiceRegistry.cs +++ b/Dalamud/Networking/Pipes/Rpc/RpcServiceRegistry.cs @@ -3,7 +3,7 @@ using System.Threading; using StreamJsonRpc; -namespace Dalamud.Networking.Rpc; +namespace Dalamud.Networking.Pipes.Rpc; /// /// Thread-safe registry of local RPC target objects that are exposed to every connected JsonRpc session. diff --git a/Dalamud/Networking/Rpc/RpcHostService.cs b/Dalamud/Networking/Rpc/RpcHostService.cs deleted file mode 100644 index bbe9dc8eb..000000000 --- a/Dalamud/Networking/Rpc/RpcHostService.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; - -using Dalamud.Logging.Internal; -using Dalamud.Networking.Rpc.Transport; - -namespace Dalamud.Networking.Rpc; - -/// -/// The Dalamud service repsonsible for hosting the RPC. -/// -[ServiceManager.EarlyLoadedService] -internal class RpcHostService : IServiceType, IInternalDisposableService -{ - private readonly ModuleLog log = new("RPC"); - private readonly RpcServiceRegistry registry = new(); - private readonly List transports = []; - - /// - /// Initializes a new instance of the class. - /// - [ServiceManager.ServiceConstructor] - public RpcHostService() - { - this.StartUnixTransport(); - - if (this.transports.Count == 0) - { - this.log.Warning("No RPC hosts could be started on this platform"); - } - } - - /// - /// Gets all active RPC transports. - /// - public IReadOnlyList Transports => this.transports; - - /// - /// Add a new service Object to the RPC host. - /// - /// The object to add. - public void AddService(object service) => this.registry.AddService(service); - - /// - /// Add a new standalone method to the RPC host. - /// - /// The method name to add. - /// The handler to add. - public void AddMethod(string name, Delegate handler) => this.registry.AddMethod(name, handler); - - /// - public void DisposeService() - { - foreach (var host in this.transports) - { - host.Dispose(); - } - - this.transports.Clear(); - } - - /// - public async Task InvokeClientAsync(Guid clientId, string method, params object[] arguments) - { - var clients = this.transports.SelectMany(t => t.Connections).ToImmutableDictionary(); - - if (!clients.TryGetValue(clientId, out var session)) - throw new KeyNotFoundException($"No client {clientId}"); - - return await session.Rpc.InvokeAsync(method, arguments).ConfigureAwait(false); - } - - /// - public async Task BroadcastNotifyAsync(string method, params object[] arguments) - { - await foreach (var transport in this.transports.ToAsyncEnumerable().ConfigureAwait(false)) - { - await transport.BroadcastNotifyAsync(method, arguments).ConfigureAwait(false); - } - } - - private void StartUnixTransport() - { - var transport = new UnixRpcTransport(this.registry); - this.transports.Add(transport); - transport.Start(); - this.log.Information("RpcHostService listening to UNIX socket: {Socket}", transport.SocketPath); - } -} diff --git a/Dalamud/Networking/Rpc/Service/Links/DebugLinkHandler.cs b/Dalamud/Networking/Rpc/Service/Links/DebugLinkHandler.cs deleted file mode 100644 index 269617fc0..000000000 --- a/Dalamud/Networking/Rpc/Service/Links/DebugLinkHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Dalamud.Game.Gui.Toast; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.Internal; -using Dalamud.Networking.Rpc.Model; - -namespace Dalamud.Networking.Rpc.Service.Links; - -#if DEBUG - -/// -/// A debug controller for link handling. -/// -[ServiceManager.EarlyLoadedService] -internal sealed class DebugLinkHandler : IInternalDisposableService -{ - private readonly LinkHandlerService linkHandlerService; - - /// - /// Initializes a new instance of the class. - /// - /// Injected LinkHandler. - [ServiceManager.ServiceConstructor] - public DebugLinkHandler(LinkHandlerService linkHandler) - { - this.linkHandlerService = linkHandler; - - this.linkHandlerService.Register("debug", this.HandleLink); - } - - /// - public void DisposeService() - { - this.linkHandlerService.Unregister("debug", this.HandleLink); - } - - private void HandleLink(DalamudUri uri) - { - var action = uri.Path.Split("/").GetValue(1)?.ToString(); - switch (action) - { - case "toast": - this.ShowToast(uri); - break; - case "notification": - this.ShowNotification(uri); - break; - } - } - - private void ShowToast(DalamudUri uri) - { - var message = uri.QueryParams.Get("message") ?? "Hello, world!"; - Service.Get().ShowNormal(message); - } - - private void ShowNotification(DalamudUri uri) - { - Service.Get().AddNotification( - new Notification - { - Title = uri.QueryParams.Get("title"), - Content = uri.QueryParams.Get("content") ?? "Hello, world!", - }); - } -} - -#endif diff --git a/Dalamud/Networking/Rpc/Transport/IRpcTransport.cs b/Dalamud/Networking/Rpc/Transport/IRpcTransport.cs deleted file mode 100644 index ad7578eb4..000000000 --- a/Dalamud/Networking/Rpc/Transport/IRpcTransport.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Dalamud.Networking.Rpc.Transport; - -/// -/// Interface for RPC host implementations (named pipes or Unix sockets). -/// -internal interface IRpcTransport : IDisposable -{ - /// - /// Gets a list of active RPC connections. - /// - IReadOnlyDictionary Connections { get; } - - /// Starts accepting client connections. - void Start(); - - /// Invoke an RPC request on a specific client expecting a result. - /// The client ID to invoke. - /// The method to invoke. - /// Any arguments to invoke. - /// An optional return based on the specified RPC. - /// The expected response type. - Task InvokeClientAsync(Guid clientId, string method, params object[] arguments); - - /// Send a notification to all connected clients (no response expected). - /// The method name to broadcast. - /// The arguments to broadcast. - /// Returns a Task when completed. - Task BroadcastNotifyAsync(string method, params object[] arguments); -} diff --git a/Dalamud/Networking/Rpc/Transport/UnixRpcTransport.cs b/Dalamud/Networking/Rpc/Transport/UnixRpcTransport.cs deleted file mode 100644 index 17da51444..000000000 --- a/Dalamud/Networking/Rpc/Transport/UnixRpcTransport.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using Dalamud.Logging.Internal; -using Dalamud.Utility; - -namespace Dalamud.Networking.Rpc.Transport; - -/// -/// Simple multi-client JSON-RPC Unix socket host using StreamJsonRpc. -/// -internal class UnixRpcTransport : IRpcTransport -{ - private readonly ModuleLog log = new("RPC/Transport/UnixSocket"); - - private readonly RpcServiceRegistry registry; - private readonly CancellationTokenSource cts = new(); - private readonly ConcurrentDictionary sessions = new(); - private readonly string? cleanupSocketDirectory; - - private Task? acceptLoopTask; - private Socket? listenSocket; - - /// - /// Initializes a new instance of the class. - /// - /// The RPC service registry to use. - /// The Unix socket directory to use. If null, defaults to Dalamud home directory. - /// The name of the socket to create. - public UnixRpcTransport(RpcServiceRegistry registry, string? socketDirectory = null, string? socketName = null) - { - this.registry = registry; - socketName ??= $"DalamudRPC.{Environment.ProcessId}.sock"; - - if (!socketDirectory.IsNullOrEmpty()) - { - this.SocketPath = Path.Combine(socketDirectory, socketName); - } - else - { - socketDirectory = Service.Get().StartInfo.TempDirectory; - - if (socketDirectory == null) - { - this.SocketPath = Path.Combine(Path.GetTempPath(), socketName); - this.log.Warning("Temp dir was not set in StartInfo; using system temp for unix socket."); - } - else - { - this.SocketPath = Path.Combine(socketDirectory, socketName); - this.cleanupSocketDirectory = socketDirectory; - } - } - } - - /// - /// Gets the path of the Unix socket this RPC host is using. - /// - public string SocketPath { get; } - - /// - public IReadOnlyDictionary Connections => this.sessions; - - /// Starts accepting client connections. - public void Start() - { - if (this.acceptLoopTask != null) return; - - // Make the directory for the socket if it doesn't exist - var socketDir = Path.GetDirectoryName(this.SocketPath); - if (!string.IsNullOrEmpty(socketDir) && !Directory.Exists(socketDir)) - { - this.log.Error("Directory for unix socket does not exist: {Path}", socketDir); - return; - } - - // Delete existing socket for this PID, if it exists. - if (File.Exists(this.SocketPath)) - { - try - { - File.Delete(this.SocketPath); - } - catch (Exception ex) - { - this.log.Warning(ex, "Failed to delete existing socket file: {Path}", this.SocketPath); - } - } - - this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning); - } - - /// Invoke an RPC request on a specific client expecting a result. - /// The client ID to invoke. - /// The method to invoke. - /// Any arguments to invoke. - /// An optional return based on the specified RPC. - /// The expected response type. - public Task InvokeClientAsync(Guid clientId, string method, params object[] arguments) - { - if (!this.sessions.TryGetValue(clientId, out var session)) - throw new KeyNotFoundException($"No client {clientId}"); - - return session.Rpc.InvokeAsync(method, arguments); - } - - /// Send a notification to all connected clients (no response expected). - /// The method name to broadcast. - /// The arguments to broadcast. - /// Returns a Task when completed. - public Task BroadcastNotifyAsync(string method, params object[] arguments) - { - var list = this.sessions.Values; - var tasks = new List(list.Count); - foreach (var s in list) - { - tasks.Add(s.Rpc.NotifyAsync(method, arguments)); - } - - return Task.WhenAll(tasks); - } - - /// - public void Dispose() - { - this.cts.Cancel(); - this.acceptLoopTask?.Wait(1000); - - foreach (var kv in this.sessions) - { - kv.Value.Dispose(); - } - - this.sessions.Clear(); - - this.listenSocket?.Dispose(); - - if (File.Exists(this.SocketPath)) - { - try - { - File.Delete(this.SocketPath); - } - catch (Exception ex) - { - this.log.Warning(ex, "Failed to delete socket file on dispose: {Path}", this.SocketPath); - } - } - - this.cts.Dispose(); - this.log.Information("UnixRpcHost disposed ({Socket})", this.SocketPath); - GC.SuppressFinalize(this); - } - - private async Task AcceptLoopAsync() - { - var token = this.cts.Token; - - try - { - var endpoint = new UnixDomainSocketEndPoint(this.SocketPath); - this.listenSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); - this.listenSocket.Bind(endpoint); - this.listenSocket.Listen(128); - - while (!token.IsCancellationRequested) - { - Socket? clientSocket = null; - try - { - clientSocket = await this.listenSocket.AcceptAsync(token).ConfigureAwait(false); - - var stream = new NetworkStream(clientSocket, ownsSocket: true); - var session = new RpcConnection(stream, this.registry); - this.sessions.TryAdd(session.Id, session); - - this.log.Debug("RPC connection created: {Id}", session.Id); - - _ = session.Completion.ContinueWith(t => - { - this.sessions.TryRemove(session.Id, out _); - this.log.Debug("RPC connection removed: {Id}", session.Id); - }, TaskScheduler.Default); - } - catch (OperationCanceledException) - { - clientSocket?.Dispose(); - break; - } - catch (Exception ex) - { - clientSocket?.Dispose(); - this.log.Error(ex, "Error in socket accept loop"); - await Task.Delay(500, token).ConfigureAwait(false); - } - } - } - catch (Exception ex) - { - this.log.Error(ex, "Fatal error in Unix socket accept loop"); - } - } -} diff --git a/Dalamud/Plugin/Services/ISelfTestRegistry.cs b/Dalamud/Plugin/SelfTest/ISelfTestRegistry.cs similarity index 95% rename from Dalamud/Plugin/Services/ISelfTestRegistry.cs rename to Dalamud/Plugin/SelfTest/ISelfTestRegistry.cs index 50d3d35ce..7e9faf3f9 100644 --- a/Dalamud/Plugin/Services/ISelfTestRegistry.cs +++ b/Dalamud/Plugin/SelfTest/ISelfTestRegistry.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using Dalamud.Plugin.SelfTest; +using Dalamud.Plugin.Services; -namespace Dalamud.Plugin.Services; +namespace Dalamud.Plugin.SelfTest; /// /// Interface for registering and unregistering self-test steps from plugins. diff --git a/Dalamud/Plugin/Services/IPluginLinkHandler.cs b/Dalamud/Plugin/Services/IPluginLinkHandler.cs index 37101222a..c05757ac7 100644 --- a/Dalamud/Plugin/Services/IPluginLinkHandler.cs +++ b/Dalamud/Plugin/Services/IPluginLinkHandler.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using Dalamud.Networking.Rpc.Model; +using Dalamud.Networking.Pipes; namespace Dalamud.Plugin.Services; diff --git a/Dalamud/Plugin/Services/ISigScanner.cs b/Dalamud/Plugin/Services/ISigScanner.cs index 017c4fe9d..fbbd8b05a 100644 --- a/Dalamud/Plugin/Services/ISigScanner.cs +++ b/Dalamud/Plugin/Services/ISigScanner.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; -namespace Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; + +namespace Dalamud.Game; /// /// A SigScanner facilitates searching for memory signatures in a given ProcessModule. diff --git a/Dalamud/Plugin/Services/ITargetManager.cs b/Dalamud/Plugin/Services/ITargetManager.cs index 0c14571c5..9c9fce550 100644 --- a/Dalamud/Plugin/Services/ITargetManager.cs +++ b/Dalamud/Plugin/Services/ITargetManager.cs @@ -1,7 +1,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; -namespace Dalamud.Plugin.Services; +namespace Dalamud.Game.ClientState.Objects; /// /// Get and set various kinds of targets for the player. @@ -37,13 +37,13 @@ public interface ITargetManager : IDalamudService /// Set to null to clear the target. /// public IGameObject? SoftTarget { get; set; } - + /// /// Gets or sets the gpose target. /// Set to null to clear the target. /// public IGameObject? GPoseTarget { get; set; } - + /// /// Gets or sets the mouseover nameplate target. /// Set to null to clear the target.