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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;

namespace XFEEDToolBox.Core.Native;

public sealed record NativeBridgeDescription(int ProtocolVersion, int ProcessId, string ImageSha256, IReadOnlyList<string> Capabilities, string Status);

/// <summary>Offline research only. Compiled into the test harness, never into the shipping application.</summary>
public sealed class NativeBridgeClient : IAsyncDisposable
{
    private readonly SemaphoreSlim gate = new(1, 1);
    private Process? process;
    public NativeBridgeDescription? Description { get; private set; }
    public bool Connected => process is { HasExited: false };
    public event Action<string>? Diagnostic;

    public async Task ConnectAsync(string python, string hostScript, GameEnvironment environment, CancellationToken token = default)
    {
        if (environment.ProfileMode != "isolated-offline") throw new InvalidOperationException("原生研究仅限已识别的隔离离线副本,禁止连接在线客户端。当前发布版只使用文件与公共行情读取。");
        if (environment.ProcessId is null || environment.ImageSha256.Length != 64) throw new InvalidOperationException("无法唯一识别游戏进程及映像版本。");
        if (Connected && Description?.ProcessId == environment.ProcessId && Description.ImageSha256 == environment.ImageSha256) return;
        if (process != null) await DisposeAsync();
        var start = new ProcessStartInfo(python)
        {
            UseShellExecute = false, CreateNoWindow = true, RedirectStandardInput = true, RedirectStandardOutput = true,
            RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8
        };
        foreach (var arg in new[] { "-u", hostScript, "--pid", environment.ProcessId.Value.ToString(), "--image", environment.Executable, "--sha256", environment.ImageSha256 }) start.ArgumentList.Add(arg);
        start.Environment["PYTHONIOENCODING"] = "utf-8";
        process = Process.Start(start) ?? throw new InvalidOperationException("原生桥接进程无法启动。");
        process.ErrorDataReceived += (_, e) => { if (e.Data is { Length: > 0 } text) Diagnostic?.Invoke(text[..Math.Min(300, text.Length)]); };
        process.BeginErrorReadLine();
        try
        {
            var result = await CallAsync("describe", null, token);
            Description = JsonSerializer.Deserialize<NativeBridgeDescription>(result.GetRawText(), ToolboxSettings.JsonOptions) ?? throw new InvalidDataException("原生桥接未返回版本信息。");
            if (Description.ProtocolVersion != 1 || Description.ProcessId != environment.ProcessId || Description.ImageSha256 != environment.ImageSha256) throw new InvalidDataException("原生桥接身份或协议版本不匹配。");
        }
        catch { await DisposeAsync(); throw; }
    }

    public async Task<JsonElement> CallAsync(string method, object? parameters = null, CancellationToken token = default)
    {
        await gate.WaitAsync(token);
        try
        {
            if (!Connected) throw new InvalidOperationException("原生桥接未连接。");
            using var timeout = CancellationTokenSource.CreateLinkedTokenSource(token); timeout.CancelAfter(TimeSpan.FromSeconds(12));
            var id = Guid.NewGuid().ToString("N");
            var request = JsonSerializer.Serialize(new { id, method, parameters }, ToolboxSettings.JsonOptions).ReplaceLineEndings("");
            await process!.StandardInput.WriteLineAsync(request.AsMemory(), timeout.Token); await process.StandardInput.FlushAsync(timeout.Token);
            var line = await process.StandardOutput.ReadLineAsync(timeout.Token) ?? throw new IOException("原生桥接已断开。");
            using var doc = JsonDocument.Parse(line);
            var root = doc.RootElement;
            if (root.Text("id") != id) throw new InvalidDataException("原生指令关联编号不匹配;不自动重试交易。");
            if (!root.Flag("ok")) throw new InvalidOperationException(root.Text("error", "原生指令被拒绝。"));
            return root.Get("result").Clone();
        }
        finally { gate.Release(); }
    }

    public async ValueTask DisposeAsync()
    {
        if (process == null) return;
        try { if (!process.HasExited) { process.StandardInput.Close(); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)); await process.WaitForExitAsync(timeout.Token); } }
        catch (OperationCanceledException) { if (!process.HasExited) process.Kill(true); }
        finally { process.Dispose(); process = null; Description = null; }
    }
}

public sealed class NativeTradeDriver(NativeBridgeClient bridge) : ITradeCommandDriver
{
    public Task PreflightAsync(CancellationToken token)
    {
        var description = bridge.Description ?? throw new InvalidOperationException("请先连接原生桥接。");
        var missing = new[] { "trade.buy", "trade.sell", "navigation.travel", "station.services" }.Except(description.Capabilities).ToArray();
        if (missing.Length > 0) throw new InvalidOperationException("原生入口尚未验证:" + string.Join("、", missing) + "。观察接入不能作为自动跑商已完成的证明。");
        return Task.CompletedTask;
    }
    public async Task ServiceDockedAsync(DockedServiceRequest request,Func<CancellationToken,Task> beforeInput,CancellationToken token)
    {
        await beforeInput(token);
        await bridge.CallAsync("station.services",new{marketId=request.MarketId,reservedCargo=request.ReservedCargo},token);
    }
    public async Task ExecuteAsync(TradeAction action, Func<CancellationToken, Task> beforeInput, CancellationToken token)
    {
        await beforeInput(token);
        var method = action.Kind switch { TradeActionKind.Buy => "trade.buy", TradeActionKind.Sell => "trade.sell", _ => "navigation.travel" };
        await bridge.CallAsync(method, new { sourceMarketId = action.Leg.From.MarketId, destinationMarketId = action.Leg.To.MarketId, system = action.Leg.To.SystemName, commodity = action.Order?.Commodity, quantity = action.Order?.Quantity, expectedPrice = action.Kind == TradeActionKind.Buy ? action.Order?.BuyPrice : action.Order?.SellPrice }, token);
    }
    public async Task ReleaseAllAsync()
    {
        if (bridge.Connected) try { await bridge.CallAsync("commands.cancel"); } catch (IOException) { } catch (InvalidOperationException) { }
    }
}