XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using XFEEDToolBox.NativeSupport;
using XFEEDToolBox.Core.Execution;
using System.Diagnostics;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using XFEEDToolBox.Core.Plugins;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.ViewModels;

public partial class ToolboxViewModel
{
    private readonly GamePluginManager pluginManager=new(NativeSupportPackage.Directory);
    [ObservableProperty] public partial string PluginGameExecutable { get; set; } = "";
    [ObservableProperty] public partial string PluginStatusText { get; set; } = "插件未安装,自动贸易不可启动。";
    [ObservableProperty] public partial bool PluginBusy { get; set; }
    [ObservableProperty] public partial bool PluginInstalled { get; set; }
    [ObservableProperty] public partial bool PluginTradeReady { get; set; }
    private DateTimeOffset? nativeSessionSeen;
    private bool nativeStatusChecking;
    private Task? nativeStatusRead;
    private DateTimeOffset nativeStatusRetryAt;
    private bool nativeStatusConnected;
    private void ObserveNativeSession(PlayerState player)
    {
        if(OfflinePreview)return;
        if(!player.InGame){PluginNavigationReady=false;PluginRelogReady=false;nativeSessionSeen=null;nativeStatusConnected=false;runtime.UnbindFlightDriver();return;}
        if(nativeStatusChecking||player.LoadGameAt is null||nativeSessionSeen==player.LoadGameAt&&(nativeStatusConnected||DateTimeOffset.UtcNow<nativeStatusRetryAt))return;
        if(NavigationActive||PluginBusy)return;
        nativeSessionSeen=player.LoadGameAt;
        nativeStatusRetryAt=DateTimeOffset.UtcNow.AddSeconds(3);
        _=ReadPluginStatusAsync();
    }
    private void InitializePluginSettings()
    { PluginGameExecutable=runtime.Settings.PluginGameExecutable;if(!OfflinePreview&&PluginGameExecutable.Length==0)PluginGameExecutable=FindSteamGame(); }
    private static string FindSteamGame()
    {
        var steam=Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\Valve\Steam","SteamPath",null) as string;if(steam is null)return "";
        var libraries=new List<string>{steam};var vdf=Path.Combine(steam,"steamapps","libraryfolders.vdf");
        if(File.Exists(vdf))foreach(Match match in Regex.Matches(File.ReadAllText(vdf),"\"path\"\\s+\"([^\"]+)\""))libraries.Add(match.Groups[1].Value.Replace("\\\\","\\"));
        return libraries.Select(l=>Path.Combine(l,"steamapps","common","Elite Dangerous","Products","elite-dangerous-odyssey-64","EliteDangerous64.exe")).FirstOrDefault(File.Exists)??"";
    }
    [RelayCommand] private async Task ReadPluginStatusAsync()
    {
        if(OfflinePreview)return;
        if(Environment.GetCommandLineArgs().Contains("--smoke-interface"))return;
        if(nativeStatusRead is {} pending){await pending;return;}
        var task=ReadPluginStatusCoreAsync();nativeStatusRead=task;
        try{await task;}finally{nativeStatusRead=null;}
    }
    private async Task ReadPluginStatusCoreAsync()
    {
        nativeStatusChecking=true;
        nativeStatusConnected=false;RelogUnavailableReason="正在连接当前游戏的重进接口";RefreshRelogAvailability();
        PluginTradeReady=false;PluginNavigationReady=false;PluginRelogReady=false;
        NavigationNativeStatus="正在检查游戏插件与导航连接…";
        var session=CurrentPlayer.LoadGameAt;var inGame=CurrentPlayer.InGame;
        try
        {
            var connection=await ReadPluginConnectionAsync(PluginGameExecutable,gameEnvironment?.Executable??"",inGame,lifetime.Token);
            pluginQueryApplyThread=Environment.CurrentManagedThreadId;
            PluginGameExecutable=connection.Executable;PluginInstalled=connection.Status?.Installed==true;
            if(connection.Package is {} availablePackage&&connection.Handshake is {} loadedPlugin&&availablePackage.Version!=loadedPlugin.Version)AutomaticPluginUpdateRequired=true;
            if(connection.Status is {} status)PluginStatusText=status.Message;
            if(connection.Waiting.Length!=0){NavigationNativeStatus=PluginStatusText=connection.Waiting;RelogUnavailableReason=connection.Waiting;return;}
            if(CurrentPlayer.LoadGameAt!=session||CurrentPlayer.InGame!=inGame){nativeSessionSeen=null;return;}
            var state=connection.Status!;var package=connection.Package!;var hello=connection.Handshake!;
            nativeStatusConnected=connection.RelogReady||!inGame;
            PluginRelogReady=connection.RelogReady;RelogUnavailableReason=connection.RelogIssue;
            PluginTradeReady=connection.TradeReady;
            if(connection.TradeIssue.Length!=0)runtime.Log("交易接口",connection.TradeIssue);
            if(inGame&&!connection.RelogReady&&connection.RelogIssue.Length!=0)runtime.Log("重进接口",connection.RelogIssue);
            PluginNavigationReady=inGame&&!state.PendingRemoval&&hello.Loaded&&hello.Active&&hello.SupportsNavigation&&package.SupportsNavigation
                &&runtime.FlightDriver is NativeFlightControlDriver driver&&driver.ProcessId==connection.ProcessId;
            PluginStatusText=$"插件已加载 · PID {connection.ProcessId} · {hello.Version}\n"+(hello.Active?"基础通信可用。\n"+(PluginNavigationReady?"自动导航驱动已就绪。":"等待进入飞行状态并完成原生遥测握手。")+"\n"+(PluginTradeReady?"自动贸易接口已就绪。":"自动贸易接口尚未就绪。")+"\n"+(PluginRelogReady?"原生重进接口已连接。":RelogUnavailableReason):"插件已停用,加载文件等待游戏退出后清理。");
            NavigationNativeStatus=PluginNavigationReady?"原生导航已连接:姿态、实时热量、恒星边界、目标锁定和充能等待联锁。":hello.Active?"插件已加载,等待飞行状态和原生遥测。":"插件已停用。";
            if(PluginNavigationReady&&runtime.FlightDriver is NativeFlightControlDriver{SupportsReferenceGeometry:false})
            {
                NavigationNativeStatus="已连接旧版导航插件;空间站与行星参考系需要更新插件后重启游戏。";
                PluginStatusText+=$"\n可更新至 {package.Version},补齐行星与空间站参考系。请退出游戏后点击安装 / 更新。";
            }
            else if(PluginNavigationReady&&runtime.FlightDriver is NativeFlightControlDriver{SupportsAdaptiveFlightControl:false})
            {
                NavigationNativeStatus="当前加载的是旧版导航插件;更新并重启游戏后启用快速对齐与高功率补给。";
                PluginStatusText+=$"\n可更新至 {package.Version},启用新的转向、热量预测和补给控制。";
            }
            else if(PluginNavigationReady&&runtime.FlightDriver is NativeFlightControlDriver{SupportsApplicationSteeringAxes:false})
            {
                PluginNavigationReady=false;
                NavigationNativeStatus="当前交互接口尚未支持绕星三轴姿态;请在结束游玩后更新游戏插件。";
                PluginStatusText+=$"\n可更新至 {package.Version},提供软件控制的偏航、俯仰和滚转输入。";
            }
            if(PluginNavigationReady)await EnsureGalaxyDisplayAsync();
        }
        catch(OperationCanceledException)when(lifetime.IsCancellationRequested){}
        catch(Exception e){nativeStatusConnected=false;PluginRelogReady=false;RelogUnavailableReason="重进连接未就绪:"+e.Message;PluginTradeReady=false;PluginStatusText=(PluginInstalled?"插件已安装,实时握手未就绪:":"插件检查失败:")+e.Message;NavigationNativeStatus=PluginStatusText;}
        finally{nativeStatusChecking=false;RefreshNavigationStartHint();RefreshRelogAvailability();}
    }
    [RelayCommand] private async Task InstallPluginAsync(CancellationToken cancellationToken=default)
    {
        using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
        if(PluginBusy)return;PluginBusy=true;
        try{PluginStatusText=(await pluginManager.InstallAsync(PluginGameExecutable,token)).Message;await runtime.SaveSettingsAsync(runtime.Settings with{PluginGameExecutable=PluginGameExecutable});await ReadPluginStatusAsync();runtime.Log("插件",PluginStatusText);}
        catch(OperationCanceledException){PluginStatusText="插件操作已取消;已完成的步骤保留。";}
        catch(Exception e){PluginStatusText=e.Message;}finally{PluginBusy=false;}
    }
    [RelayCommand] private async Task UninstallPluginAsync(CancellationToken cancellationToken=default)
    {
        using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
        if(PluginBusy)return;PluginBusy=true;runtime.Runner.Stop();runtime.FlightAutomation.Stop();runtime.Navigator.Stop();PluginTradeReady=false;PluginNavigationReady=false;
        try
        {
            var games=Process.GetProcessesByName("EliteDangerous64");
            try{foreach(var game in games)if(string.Equals(game.MainModule?.FileName,PluginGameExecutable,StringComparison.OrdinalIgnoreCase))try{await GamePluginManager.ProbeAsync(game.Id,true,token);}catch(OperationCanceledException)when(!token.IsCancellationRequested){}catch(IOException){}}
            finally{foreach(var game in games)game.Dispose();}
            var status=await pluginManager.UninstallAsync(PluginGameExecutable,token);PluginInstalled=status.Installed;PluginStatusText=status.Message;runtime.Log("插件",PluginStatusText);
        }
        catch(OperationCanceledException){PluginStatusText="插件清理已取消;已完成的步骤保留。";}
        catch(Exception e){PluginStatusText=e.Message;}finally{PluginBusy=false;}
    }
    [RelayCommand] private async Task ChoosePluginGameAsync()
    {
        var picker=new Windows.Storage.Pickers.FileOpenPicker();picker.FileTypeFilter.Add(".exe");InitializePicker(picker);
        if(await picker.PickSingleFileAsync() is { } file){PluginGameExecutable=file.Path;await ReadPluginStatusAsync();}
    }
    private async Task RequirePluginTradeAsync()
    {
        await ReadPluginStatusAsync();
        if(!PluginInstalled)throw new InvalidOperationException("自动贸易需要先在设置 → 游戏插件中安装插件。");
        if(!PluginNavigationReady||runtime.FlightDriver is not NativeFlightControlDriver driver)
            throw new InvalidOperationException("插件导航接口尚未就绪,工具箱暂不发出贸易或飞行指令。");
        var hello=await new NavigationPipeClient(driver.ProcessId).CallAsync(new{op="describe"},lifetime.Token);
        if(!NativeTradeCapabilities.SupportsDockedTrade(hello))
            throw new NotSupportedException("当前插件尚未提供经过验证的停靠交易接口。");
        PluginTradeReady=true;
    }
}