using XFEEDToolBox.NativeSupport;
using System.Diagnostics;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Plugins;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
private int pluginQueryWorkerThread, pluginQueryApplyThread;
public object PluginQueryDiagnostics => new { workerThread = Volatile.Read(ref pluginQueryWorkerThread), applyThread = pluginQueryApplyThread };
private sealed record PluginConnection(string Executable, PluginStatus? Status = null, PluginPackage? Package = null,
PluginHandshake? Handshake = null, int ProcessId = 0, bool RelogReady = false, bool TradeReady = false,
string Waiting = "", string RelogIssue = "", string TradeIssue = "");
// Process/module enumeration, disk validation, IPC and response decoding
// stay on the worker even when an awaited I/O operation completes inline.
private Task<PluginConnection> ReadPluginConnectionAsync(string executable, string fallback, bool inGame, CancellationToken token)
=> Task.Run(async () =>
{
Volatile.Write(ref pluginQueryWorkerThread, Environment.CurrentManagedThreadId);
if (Environment.GetCommandLineArgs().Contains("--check-background-data")) await Task.Delay(500, token).ConfigureAwait(false);
if (executable.Length == 0) executable = fallback.Length != 0 ? fallback : FindSteamGame();
if (executable.Length == 0) return new PluginConnection(executable, Waiting: "未找到游戏目录,请选择 EliteDangerous64.exe。");
var state = await pluginManager.InspectAsync(executable, token).ConfigureAwait(false);
if (!state.Installed) return new PluginConnection(executable, state, Waiting: state.Message);
var package = pluginManager.Package();
var games = Process.GetProcessesByName("EliteDangerous64");
try
{
var matching = games.Where(p => string.Equals(p.MainModule?.FileName, executable, StringComparison.OrdinalIgnoreCase)).ToArray();
if (matching.Length != 1) return new PluginConnection(executable, state, package, Waiting: matching.Length == 0
? "游戏尚未运行,请先启动游戏并进入飞船驾驶舱。" : "检测到多个游戏进程,请保留要控制的唯一游戏实例。");
var game = matching[0];
var loader = Path.Combine(Path.GetDirectoryName(executable)!, "dinput8.dll");
if (!game.Modules.Cast<ProcessModule>().Any(m => string.Equals(m.FileName, loader, StringComparison.OrdinalIgnoreCase)))
return new PluginConnection(executable, state, package, Waiting: "插件尚未加载,请在游戏下次启动后重试。");
var hello = await GamePluginManager.ProbeAsync(game.Id, token: token).ConfigureAwait(false);
// Observers and extension loading wait for an actual Journal session.
if (inGame && !state.PendingRemoval && hello.Loaded && hello.Active && package.SupportsNavigation && !hello.ObservationStarted)
hello = await GamePluginManager.StartNavigationObservationAsync(game.Id, token).ConfigureAwait(false);
var relog = false; var trade = false; var relogIssue = "当前插件尚未提供可用的重进接口"; var tradeIssue = "";
if (inGame && !state.PendingRemoval && hello.Loaded && hello.Active)
{
if (hello.SupportsRelog && package.SupportsRelog)
{
try
{
await new NativeOriginalSessionMenuRecovery(game.Id, state.Installation!.GameSha256,
Path.Combine(NativeSupportPackage.Directory,"DockingControl")).PrepareAsync(token).ConfigureAwait(false);
relog = true; relogIssue = "";
}
catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; }
catch (Exception error) { relogIssue = "重进连接未就绪:" + error.Message; }
}
if (hello.SupportsNavigation && package.SupportsNavigation
&& (runtime.FlightDriver is not NativeFlightControlDriver current || current.ProcessId != game.Id))
{
var processId = game.Id; var hash = state.Installation!.GameSha256;
try
{
await runtime.BindFlightDriverAsync(new NativeFlightControlDriver(processId, hash,
() => runtime.Player, () => runtime.Settings.NavigationFuelReserve,
new NativeGameInputChannel(processId, hash, NativeSupportPackage.Directory),
cancellation => EnsureNativeFlightProtectionAsync(processId, hash, cancellation)), token).ConfigureAwait(false);
}
catch(Exception error)when(!token.IsCancellationRequested)
{return new PluginConnection(executable,state,package,hello,game.Id,relog,false,Waiting:"插件已安装,飞行遥测尚未就绪:"+error.Message,RelogIssue:relogIssue);}
}
if (runtime.FlightDriver is NativeFlightControlDriver driver && driver.ProcessId == game.Id)
{
try
{
var capabilities = await new NativeCommerceChannel(game.Id).CallAsync(new { op = "describe" }, token).ConfigureAwait(false);
trade = NativeTradeCapabilities.SupportsDockedTrade(capabilities);
}
catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; }
catch (Exception error) { tradeIssue = error.Message; }
}
}
return new PluginConnection(executable, state, package, hello, game.Id, relog, trade,
RelogIssue: relogIssue, TradeIssue: tradeIssue);
}
finally { foreach (var game in games) game.Dispose(); }
}, token);
}
using XFEEDToolBox.NativeSupport;
using System.Diagnostics;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Plugins;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
private int pluginQueryWorkerThread, pluginQueryApplyThread;
public object PluginQueryDiagnostics => new { workerThread = Volatile.Read(ref pluginQueryWorkerThread), applyThread = pluginQueryApplyThread };
private sealed record PluginConnection(string Executable, PluginStatus? Status = null, PluginPackage? Package = null,
PluginHandshake? Handshake = null, int ProcessId = 0, bool RelogReady = false, bool TradeReady = false,
string Waiting = "", string RelogIssue = "", string TradeIssue = "");
// Process/module enumeration, disk validation, IPC and response decoding
// stay on the worker even when an awaited I/O operation completes inline.
private Task<PluginConnection> ReadPluginConnectionAsync(string executable, string fallback, bool inGame, CancellationToken token)
=> Task.Run(async () =>
{
Volatile.Write(ref pluginQueryWorkerThread, Environment.CurrentManagedThreadId);
if (Environment.GetCommandLineArgs().Contains("--check-background-data")) await Task.Delay(500, token).ConfigureAwait(false);
if (executable.Length == 0) executable = fallback.Length != 0 ? fallback : FindSteamGame();
if (executable.Length == 0) return new PluginConnection(executable, Waiting: "未找到游戏目录,请选择 EliteDangerous64.exe。");
var state = await pluginManager.InspectAsync(executable, token).ConfigureAwait(false);
if (!state.Installed) return new PluginConnection(executable, state, Waiting: state.Message);
var package = pluginManager.Package();
var games = Process.GetProcessesByName("EliteDangerous64");
try
{
var matching = games.Where(p => string.Equals(p.MainModule?.FileName, executable, StringComparison.OrdinalIgnoreCase)).ToArray();
if (matching.Length != 1) return new PluginConnection(executable, state, package, Waiting: matching.Length == 0
? "游戏尚未运行,请先启动游戏并进入飞船驾驶舱。" : "检测到多个游戏进程,请保留要控制的唯一游戏实例。");
var game = matching[0];
var loader = Path.Combine(Path.GetDirectoryName(executable)!, "dinput8.dll");
if (!game.Modules.Cast<ProcessModule>().Any(m => string.Equals(m.FileName, loader, StringComparison.OrdinalIgnoreCase)))
return new PluginConnection(executable, state, package, Waiting: "插件尚未加载,请在游戏下次启动后重试。");
var hello = await GamePluginManager.ProbeAsync(game.Id, token: token).ConfigureAwait(false);
// Observers and extension loading wait for an actual Journal session.
if (inGame && !state.PendingRemoval && hello.Loaded && hello.Active && package.SupportsNavigation && !hello.ObservationStarted)
hello = await GamePluginManager.StartNavigationObservationAsync(game.Id, token).ConfigureAwait(false);
var relog = false; var trade = false; var relogIssue = "当前插件尚未提供可用的重进接口"; var tradeIssue = "";
if (inGame && !state.PendingRemoval && hello.Loaded && hello.Active)
{
if (hello.SupportsRelog && package.SupportsRelog)
{
try
{
await new NativeOriginalSessionMenuRecovery(game.Id, state.Installation!.GameSha256,
Path.Combine(NativeSupportPackage.Directory,"DockingControl")).PrepareAsync(token).ConfigureAwait(false);
relog = true; relogIssue = "";
}
catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; }
catch (Exception error) { relogIssue = "重进连接未就绪:" + error.Message; }
}
if (hello.SupportsNavigation && package.SupportsNavigation
&& (runtime.FlightDriver is not NativeFlightControlDriver current || current.ProcessId != game.Id))
{
var processId = game.Id; var hash = state.Installation!.GameSha256;
try
{
await runtime.BindFlightDriverAsync(new NativeFlightControlDriver(processId, hash,
() => runtime.Player, () => runtime.Settings.NavigationFuelReserve,
new NativeGameInputChannel(processId, hash, NativeSupportPackage.Directory),
cancellation => EnsureNativeFlightProtectionAsync(processId, hash, cancellation)), token).ConfigureAwait(false);
}
catch(Exception error)when(!token.IsCancellationRequested)
{return new PluginConnection(executable,state,package,hello,game.Id,relog,false,Waiting:"插件已安装,飞行遥测尚未就绪:"+error.Message,RelogIssue:relogIssue);}
}
if (runtime.FlightDriver is NativeFlightControlDriver driver && driver.ProcessId == game.Id)
{
try
{
var capabilities = await new NativeCommerceChannel(game.Id).CallAsync(new { op = "describe" }, token).ConfigureAwait(false);
trade = NativeTradeCapabilities.SupportsDockedTrade(capabilities);
}
catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; }
catch (Exception error) { tradeIssue = error.Message; }
}
}
return new PluginConnection(executable, state, package, hello, game.Id, relog, trade,
RelogIssue: relogIssue, TradeIssue: tradeIssue);
}
finally { foreach (var game in games) game.Dispose(); }
}, token);
}