mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-11 01:37:24 +01:00
chore: remove named pipe transport, use startinfo for pathing
This commit is contained in:
parent
0d8f577576
commit
7b286c427c
9 changed files with 41 additions and 196 deletions
|
|
@ -108,6 +108,11 @@ 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);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ struct DalamudStartInfo {
|
|||
std::string ConfigurationPath;
|
||||
std::string LogPath;
|
||||
std::string LogName;
|
||||
std::string TempDirectory;
|
||||
std::string PluginDirectory;
|
||||
std::string AssetDirectory;
|
||||
ClientLanguage Language = ClientLanguage::English;
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstrin
|
|||
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
|
||||
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
|
||||
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
|
||||
args.emplace_back(L"--dalamud-temp-directory=\"" + unicode::convert<std::wstring>(g_startInfo.TempDirectory) + L"\"");
|
||||
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
|
||||
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
|
||||
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
|
||||
|
|
@ -268,7 +269,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);
|
||||
|
|
@ -297,7 +298,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);
|
||||
|
|
@ -315,9 +316,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;
|
||||
|
|
@ -385,7 +386,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());
|
||||
|
|
@ -400,7 +401,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)
|
||||
))
|
||||
|
|
@ -416,7 +417,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);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public string? ConfigurationPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string? TempDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the log files.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ 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;
|
||||
|
|
@ -321,6 +322,10 @@ 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..]);
|
||||
|
|
@ -433,6 +438,7 @@ namespace Dalamud.Injector
|
|||
startInfo.ConfigurationPath = configurationPath;
|
||||
startInfo.PluginDirectory = pluginDirectory;
|
||||
startInfo.AssetDirectory = assetDirectory;
|
||||
startInfo.TempDirectory = tempDirectory;
|
||||
startInfo.Language = clientLanguage;
|
||||
startInfo.Platform = platform;
|
||||
startInfo.DelayInitializeMs = delayInitializeMs;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
|||
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Networking.Rpc.Transport;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Networking.Rpc;
|
||||
|
||||
|
|
@ -26,7 +25,6 @@ internal class RpcHostService : IServiceType, IInternalDisposableService
|
|||
public RpcHostService()
|
||||
{
|
||||
this.StartUnixTransport();
|
||||
this.StartPipeTransport();
|
||||
|
||||
if (this.transports.Count == 0)
|
||||
{
|
||||
|
|
@ -90,16 +88,4 @@ internal class RpcHostService : IServiceType, IInternalDisposableService
|
|||
transport.Start();
|
||||
this.log.Information("RpcHostService listening to UNIX socket: {Socket}", transport.SocketPath);
|
||||
}
|
||||
|
||||
private void StartPipeTransport()
|
||||
{
|
||||
// Wine doesn't support named pipes.
|
||||
if (Util.IsWine())
|
||||
return;
|
||||
|
||||
var transport = new PipeRpcTransport(this.registry);
|
||||
this.transports.Add(transport);
|
||||
transport.Start();
|
||||
this.log.Information("RpcHostService listening to named pipe: {Pipe}", transport.PipeName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
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.Rpc.Transport;
|
||||
|
||||
/// <summary>
|
||||
/// Simple multi-client JSON-RPC named pipe host using StreamJsonRpc.
|
||||
/// </summary>
|
||||
internal class PipeRpcTransport : IRpcTransport
|
||||
{
|
||||
private readonly ModuleLog log = new("RPC/Transport/NamedPipe");
|
||||
|
||||
private readonly RpcServiceRegistry registry;
|
||||
private readonly CancellationTokenSource cts = new();
|
||||
private readonly ConcurrentDictionary<Guid, RpcConnection> sessions = new();
|
||||
private Task? acceptLoopTask;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PipeRpcTransport"/> class.
|
||||
/// </summary>
|
||||
/// <param name="registry">The RPC service registry to use.</param>
|
||||
/// <param name="pipeName">The pipe name to create.</param>
|
||||
public PipeRpcTransport(RpcServiceRegistry registry, string? pipeName = null)
|
||||
{
|
||||
this.registry = registry;
|
||||
// Default pipe name based on current process ID for uniqueness per Dalamud instance.
|
||||
this.PipeName = pipeName ?? $"DalamudRPC.{Environment.ProcessId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the named pipe this RPC host is using.
|
||||
/// </summary>
|
||||
public string PipeName { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyDictionary<Guid, RpcConnection> Connections => this.sessions;
|
||||
|
||||
/// <summary>Starts accepting client connections.</summary>
|
||||
public void Start()
|
||||
{
|
||||
if (this.acceptLoopTask != null) return;
|
||||
this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
/// <summary>Invoke an RPC request on a specific client expecting a result.</summary>
|
||||
/// <param name="clientId">The client ID to invoke.</param>
|
||||
/// <param name="method">The method to invoke.</param>
|
||||
/// <param name="arguments">Any arguments to invoke.</param>
|
||||
/// <returns>An optional return based on the specified RPC.</returns>
|
||||
/// <typeparam name="T">The expected response type.</typeparam>
|
||||
public Task<T> InvokeClientAsync<T>(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<T>(method, arguments);
|
||||
}
|
||||
|
||||
/// <summary>Send a notification to all connected clients (no response expected).</summary>
|
||||
/// <param name="method">The method name to broadcast.</param>
|
||||
/// <param name="arguments">The arguments to broadcast.</param>
|
||||
/// <returns>Returns a Task when completed.</returns>
|
||||
public Task BroadcastNotifyAsync(string method, params object[] arguments)
|
||||
{
|
||||
var list = this.sessions.Values;
|
||||
var tasks = new List<Task>(list.Count);
|
||||
foreach (var s in list)
|
||||
{
|
||||
tasks.Add(s.Rpc.NotifyAsync(method, arguments));
|
||||
}
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,30 +31,30 @@ internal class UnixRpcTransport : IRpcTransport
|
|||
/// Initializes a new instance of the <see cref="UnixRpcTransport"/> class.
|
||||
/// </summary>
|
||||
/// <param name="registry">The RPC service registry to use.</param>
|
||||
/// <param name="socketPath">The Unix socket path to create. If null, defaults to a path based on process ID.</param>
|
||||
public UnixRpcTransport(RpcServiceRegistry registry, string? socketPath = null)
|
||||
/// <param name="socketDirectory">The Unix socket directory to use. If null, defaults to Dalamud home directory.</param>
|
||||
/// <param name="socketName">The name of the socket to create.</param>
|
||||
public UnixRpcTransport(RpcServiceRegistry registry, string? socketDirectory = null, string? socketName = null)
|
||||
{
|
||||
this.registry = registry;
|
||||
socketName ??= $"DalamudRPC.{Environment.ProcessId}.sock";
|
||||
|
||||
if (socketPath != null)
|
||||
if (!socketDirectory.IsNullOrEmpty())
|
||||
{
|
||||
this.SocketPath = socketPath;
|
||||
this.SocketPath = Path.Combine(socketDirectory, socketName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dalamudConfigPath = Service<Dalamud>.Get().StartInfo.ConfigurationPath;
|
||||
var dalamudHome = Path.GetDirectoryName(dalamudConfigPath);
|
||||
var socketName = $"DalamudRPC.{Environment.ProcessId}.sock";
|
||||
socketDirectory = Service<Dalamud>.Get().StartInfo.TempDirectory;
|
||||
|
||||
if (dalamudHome == null)
|
||||
if (socketDirectory == null)
|
||||
{
|
||||
this.SocketPath = Path.Combine(Path.GetTempPath(), socketName);
|
||||
this.log.Warning("Dalamud home is empty! UDS socket will be in temp.");
|
||||
this.log.Warning("Temp dir was not set in StartInfo; using system temp for unix socket.");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SocketPath = Path.Combine(dalamudHome, socketName);
|
||||
this.cleanupSocketDirectory = dalamudHome;
|
||||
this.SocketPath = Path.Combine(socketDirectory, socketName);
|
||||
this.cleanupSocketDirectory = socketDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,15 +76,8 @@ internal class UnixRpcTransport : IRpcTransport
|
|||
var socketDir = Path.GetDirectoryName(this.SocketPath);
|
||||
if (!string.IsNullOrEmpty(socketDir) && !Directory.Exists(socketDir))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(socketDir);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.log.Error(ex, "Failed to create socket directory: {Path}", socketDir);
|
||||
return;
|
||||
}
|
||||
this.log.Error("Directory for unix socket does not exist: {Path}", socketDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete existing socket for this PID, if it exists.
|
||||
|
|
@ -103,6 +96,7 @@ internal class UnixRpcTransport : IRpcTransport
|
|||
this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning);
|
||||
|
||||
// note: needs to be run _after_ we're alive so that we don't delete our own socket.
|
||||
// TODO: This should *probably* be handed by the launcher instead.
|
||||
if (this.cleanupSocketDirectory != null)
|
||||
{
|
||||
Task.Run(async () => await UnixSocketUtil.CleanStaleSockets(this.cleanupSocketDirectory));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
namespace Dalamud.Networking.Rpc;
|
||||
|
||||
/// <summary>
|
||||
/// A set of utilities to help manage Unix sockets.
|
||||
Loading…
Add table
Add a link
Reference in a new issue