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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

internal static class CgCargoJourney
{
    // Explicit current CG route. Cached prices are ceilings/floors only; the
    // driver obtains original prices and stock immediately before each order.
    internal static async Task RunAsync(int pid,string directory,CancellationToken externalToken=default,TradeOrder? requestedOrder=null)
    {
        directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
        using var runLease=new FileStream(Path.Combine(directory,"runner.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
        var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
        await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
        if(!initial.InGame||initial.CurrentShipId!=5||string.IsNullOrWhiteSpace(initial.CommanderId)||initial.CargoCapacity!=192)
            throw new InvalidOperationException("Actual Corsair/5 with the verified 192t hold is required.");
        var workRoot=Directory.GetParent(directory)!.FullName;
        var useNativeCruise=true;var navigationPolicy=Path.Combine(workRoot,"navigation-policy.json");
        if(File.Exists(navigationPolicy))
        {using var p=JsonDocument.Parse(await File.ReadAllTextAsync(navigationPolicy));useNativeCruise=p.RootElement.GetProperty("useNativeCruise").GetBoolean();}
        var lossHold=Path.Combine(workRoot,"ship-loss-hold.json");
        if(File.Exists(lossHold)){using var h=JsonDocument.Parse(await File.ReadAllTextAsync(lossHold));if(h.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("Ship loss hold is active; no CG input is permitted.");}
        using(var acceptance=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(workRoot,"one-ton-journey","result.json"))))
            if(!acceptance.RootElement.GetProperty("passed").GetBoolean())throw new InvalidOperationException("The original 1t journey must pass before a CG batch.");
        var accepted=TradeJourneyCheckpoints.Load(Path.Combine(workRoot,"one-ton-journey","journey.json"));
        if(accepted.Commander!=initial.CommanderId||accepted.ShipId!=5||accepted.Sold is not {QuantityDelta:-1,Commodity:"biowaste"})
            throw new InvalidDataException("Original completed 1t sale receipt required before CG production.");
        var membershipPath=Path.Combine(Directory.GetParent(directory)!.FullName,"cg-membership-864","membership-latest.json");
        long contributionBefore;
        using(var membership=JsonDocument.Parse(await File.ReadAllTextAsync(membershipPath)))
        {
            var root=membership.RootElement;var body=root.GetProperty("Body").GetString()!;
            var confirmedGoal=CommunityGoalCatalogue.RequireTrade(CommunityGoalCatalogue.Parse(body),DateTimeOffset.UtcNow,true);
            if(root.GetProperty("Proof").GetString()!=Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body)))
                ||confirmedGoal.Id!=864)
                throw new InvalidDataException("Original active CG 864 membership evidence required before carrying cargo.");
            contributionBefore=confirmedGoal.Contribution??0;
        }
        var planPath=Path.Combine(directory,"plan.json");
        var plannedOrder=requestedOrder??new TradeOrder("uranium","Uranium",192,7241,57104);
        if(File.Exists(planPath))
        {
            using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(planPath));var plan=saved.RootElement;
            if(plan.GetProperty("goalId").GetUInt64()!=864||plan.GetProperty("sourceMarket").GetInt64()!=3228792064)
                throw new InvalidDataException("Persisted CG route differs from this cargo journey.");
            plannedOrder=new(plan.GetProperty("commodity").GetString()!,plan.TryGetProperty("label",out var label)?label.GetString()!:"Uranium",plan.GetProperty("quantity").GetInt32(),
                plan.GetProperty("buyCeiling").GetInt64(),plan.GetProperty("sellFloor").GetInt64());
            contributionBefore=plan.GetProperty("contributionBefore").GetInt64();
        }
        else
        {
            await using var file=new FileStream(planPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
            await JsonSerializer.SerializeAsync(file,new{goalId=864UL,sourceMarket=3228792064L,quantity=plannedOrder.Quantity,commodity=plannedOrder.Commodity,label=plannedOrder.Name,
                buyCeiling=plannedOrder.BuyPrice,sellFloor=plannedOrder.SellPrice,contributionBefore});
        }
        var checkpointPath=Path.Combine(directory,"journey.json");
        var checkpoint=File.Exists(checkpointPath)?TradeJourneyCheckpoints.Load(checkpointPath):new(initial.CommanderId,5,DateTimeOffset.UtcNow);
        if(checkpoint.Commander!=initial.CommanderId||checkpoint.ShipId!=5)throw new InvalidDataException("CG journey belongs to another ship or commander.");
        if(!NativeTradeBatchPolicy.Allows(plannedOrder.Commodity)||plannedOrder.Quantity is <1 or >192||plannedOrder.BuyPrice is <=0 or >100000||plannedOrder.SellPrice<=plannedOrder.BuyPrice)
            throw new InvalidDataException("Persisted CG batch exceeds the original interface limits.");
        if(checkpoint.Bought is {} reserved&&(reserved.Commodity!=plannedOrder.Commodity||reserved.QuantityDelta!=plannedOrder.Quantity||reserved.MarketId!=3228792064))
            throw new InvalidDataException("CG journey has a different reserved cargo; no order was submitted.");
        var attempt=Guid.NewGuid().ToString("N");
        var guardian=PersistentProtectionProcess.Current??throw new InvalidOperationException("CG trading requires independently owned protection.");
        guardian.RequireReady(pid);
        using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(60));
        using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop"))||!guardian.IsAlive)stop.Cancel();},null,100,100);
        using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));var channel=new NativeCommerceChannel(pid);
        NativeWalletObservation? wallet=null;PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
        async Task Check(CancellationToken token)
        {
            token.ThrowIfCancellationRequested();var current=Player();
            if(!current.InGame||current.CurrentShipId!=5||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
                ||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current)||!guardian.IsAlive)
                throw new InvalidOperationException("CG session, ship or continuous protection changed.");
            var hold=Path.Combine(Directory.GetParent(directory)!.FullName,"maintenance-hold.json");
            if(current.Docked&&File.Exists(hold))
            {
                using var maintenance=JsonDocument.Parse(await File.ReadAllTextAsync(hold,token));
                if(maintenance.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("Maintenance hold requires staying docked.");
            }
            JsonElement health;var readyUntil=DateTimeOffset.UtcNow.AddSeconds(15);
            while(true)
            {
                token.ThrowIfCancellationRequested();current=Player();
                if(!current.InGame||current.CurrentShipId!=5||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
                    ||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current)||!guardian.IsAlive)
                    throw new InvalidOperationException("CG ship or protection changed while waiting for post-transition telemetry.");
                try{guardian.RequireReady(pid);health=await channel.CallAsync(new{op="health.read"},token);break;}
                catch(Exception error)when((NavigationRecovery.CanRetry(error,token)||error is InvalidOperationException&&error.Message=="Native main-ship health unavailable or stale")&&DateTimeOffset.UtcNow<readyUntil)
                {
                    // Journal can confirm a real jump before the new main-ship
                    // sampler publishes its first verified frame. No flight or
                    // trade input is sent until that original frame is ready.
                    await Task.Delay(80,token);
                }
            }
            if(!health.GetProperty("localPlayerShipVerified").GetBoolean()||!health.GetProperty("shipHealth").GetProperty("verified").GetBoolean()
                ||health.GetProperty("shipHealth").GetProperty("hullFraction").GetDouble()<.5)
                throw new InvalidOperationException("Fresh independent main-ship health required.");
        }
        async Task Travel(Station target,Func<CancellationToken,Task> beforeInput,CancellationToken token)
        {
            await beforeInput(token);if(Player().Docked&&Player().MarketId==target.MarketId)return;
            if(Player().Docked)
            {
                using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);
                var catalogue=await source.ReadLocationsAsync(token);
                var departureTarget=catalogue.Locations.FirstOrDefault(x=>x.Category==SystemLocationCategory.Station&&x.MarketId is >0&&x.MarketId!=Player().MarketId)
                    ??throw new InvalidOperationException("A verified alternate local target is required for native departure.");
                Console.WriteLine("原始自动起飞:"+Player().StationName);
                await LocalDepartureAcceptance.RunAsync(pid,departureTarget.Name,Path.Combine(directory,"departure-"+Guid.NewGuid().ToString("N")+".json"),token,stopAfterDeparture:false);
            }
            await Check(token);
            if(!Player().SystemName.Equals(target.SystemName,StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine("游戏原始航线:"+target.SystemName);
                await InterstellarTravelAcceptance.RunAsync(pid,target.SystemName,Path.Combine(directory,"interstellar-"+Guid.NewGuid().ToString("N")+".json"),token);
            }
            await Check(token);Console.WriteLine("原始超巡、空管与自动降落:"+target.Name);
            await LocalNavigationAcceptance.RunAsync(pid,target.Name,Path.Combine(directory,"arrival-"+Guid.NewGuid().ToString("N")+".json"),
                requireCruise:true,useNativeCruise:useNativeCruise,allowObscuration:true,externalToken:token,keepHandoffScene:true);
            var dockObservedUntil=DateTimeOffset.UtcNow.AddSeconds(5);
            while(!Player().Docked||Player().MarketId!=target.MarketId)
            {
                token.ThrowIfCancellationRequested();guardian.RequireReady(pid);
                if(Player().Docked||DateTimeOffset.UtcNow>=dockObservedUntil)break;
                await Task.Delay(50,token);
            }
            if(!Player().Docked||Player().MarketId!=target.MarketId)throw new InvalidOperationException("Original docking differs from the CG route.");
        }
        var from=new Station{MarketId=3228792064,Name="Polyakov Station",SystemName="Duamta"};
        var to=new Station{MarketId=CommunityGoalCatalogue.HomerMarket,Name="Homer Platform",SystemName="AD Leonis"};
        var order=plannedOrder;var leg=new TradeLeg{Number=1,From=from,To=to,Orders=[order]};
        var policy=new NativeTradeBatchPolicy(192,Math.Max(initial.Rebuy,4427098));
        var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),Travel,Check,batchPolicy:policy);
        driver.WalletObserved+=value=>wallet=value;
        driver.ServiceConfirmed+=receipt=>ledger.Record(receipt.ToCashEntry());driver.Status+=Console.WriteLine;
        string? failure=null;Task? services=null;var started=DateTimeOffset.UtcNow;long? openingCredits=null,closingCredits=null;
        var continuousArrivalAccepted=false;var walletReconciled=false;var openingPath=Path.Combine(directory,"opening-wallet.json");
        try
        {
            guardian.RequireReady(pid);await driver.PreflightAsync(stop.Token);await Check(stop.Token);
            if(File.Exists(openingPath))
            {
                using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(openingPath,stop.Token));openingCredits=saved.RootElement.GetProperty("credits").GetInt64();
            }
            else
            {
                if(checkpoint.Bought is null)openingCredits=wallet?.Credits??throw new InvalidDataException("Original opening wallet not observed.");
                else
                {
                    // Upgrade an existing in-flight checkpoint using its first
                    // actual confirmed debit, never a derived or assumed balance.
                    var entries=Directory.EnumerateFiles(Path.Combine(directory,"ledger"),"*.entry.json").Select(path=>
                    {
                        using var stored=JsonDocument.Parse(File.ReadAllText(path));var root=stored.RootElement;
                        return (Sequence:root.GetProperty("Sequence").GetInt64(),Key:root.GetProperty("Entry").GetProperty("OperationKey").GetString()!);
                    }).OrderBy(entry=>entry.Sequence).ToArray();
                    if(entries.Length==0)throw new InvalidDataException("Reserved CG cargo is missing its accounting history.");
                    using var original=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(directory,"operations",entries[0].Key+".confirmed.json"),stop.Token));
                    var receipt=original.RootElement.GetProperty("receipt");
                    if(!receipt.GetProperty("serverConfirmed").GetBoolean()||!receipt.GetProperty("clientApplied").GetBoolean())throw new InvalidDataException("Opening CG debit is not confirmed.");
                    openingCredits=receipt.GetProperty("creditsBefore").GetInt64();
                }
                await using var file=new FileStream(openingPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
                await JsonSerializer.SerializeAsync(file,new{credits=openingCredits,at=DateTimeOffset.UtcNow},cancellationToken:stop.Token);
            }
            TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
            if(checkpoint.Bought is null)
            {
                if(!Player().Docked||Player().MarketId!=from.MarketId||Player().CargoCount!=0)
                    throw new InvalidOperationException("First CG batch requires the confirmed empty hold at Polyakov Station.");
                Console.WriteLine($"采购 {order.Quantity}t {order.Name},并完成本站支持的维护。");
                services=driver.ServiceDockedAsync(new(from.MarketId,order.Quantity),Check,stop.Token);
                var action=new TradeAction(TradeActionKind.Buy,leg,order);
                var receipt=await driver.ExecuteTradeAsync(action,Check,stop.Token);receipt.Validate(action,Player(),started);
                ledger.RecordTrade(receipt);checkpoint=checkpoint with{Bought=receipt};TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
                Console.WriteLine(JsonSerializer.Serialize(new{bought=receipt.QuantityDelta,receipt.UnitPrice,receipt.CreditsAfter}));
                await services;services=null;
            }
            else ledger.RecordTrade(checkpoint.Bought);
            if(checkpoint.Sold is null)
            {
                await Travel(to,Check,stop.Token);await Check(stop.Token);
                Console.WriteLine("Homer 原始卖出与加油维修补给。");
                services=driver.ServiceDockedAsync(new(to.MarketId,order.Quantity),Check,stop.Token);
                var action=new TradeAction(TradeActionKind.Sell,leg,order);
                var receipt=await driver.ExecuteTradeAsync(action,Check,stop.Token);receipt.Validate(action,Player(),started);
                ledger.RecordTrade(receipt);checkpoint=checkpoint with{Sold=receipt};TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
                await services;services=null;
            }
            else
            {
                ledger.RecordTrade(checkpoint.Sold);
                // A previous process can have saved the sale before its
                // concurrent service task finished. Reconfirm services before
                // accepting the completed journey; never purchase again.
                await driver.ServiceDockedAsync(new(to.MarketId,order.Quantity),Check,stop.Token);
            }
            if(!Player().Docked||Player().MarketId!=to.MarketId||Player().CargoCount!=0)throw new InvalidOperationException("CG final docking or empty hold not confirmed.");
            var membership=await new NativeCommunityGoalClient(channel).ReadAsync(true,stop.Token);
            var goal=CommunityGoalCatalogue.RequireTrade(membership.Goals,DateTimeOffset.UtcNow,true);
            await File.WriteAllTextAsync(Path.Combine(directory,"membership-after.json"),JsonSerializer.Serialize(membership),stop.Token);
            if(goal.Id!=864||goal.Contribution is null||goal.Contribution<contributionBefore+order.Quantity)
                throw new InvalidOperationException("Original CG contribution has not confirmed this batch's delivered increment.");
            var inventory=await channel.CallAsync(new{op="commerce.inventory"},stop.Token);closingCredits=inventory.GetProperty("credits").GetInt64();
            if(inventory.GetProperty("count").GetInt32()!=0||openingCredits+ledger.Snapshot.CashChange!=closingCredits)
                throw new InvalidDataException("Final original wallet does not reconcile with this journey's confirmed receipts.");
            walletReconciled=true;
            // The next batch must measure its own increment from this newly
            // confirmed contribution, rather than reusing the original signup's
            // zero baseline. An existing journey still keeps its saved plan.
            var membershipTemporary=membershipPath+"."+Guid.NewGuid().ToString("N")+".tmp";
            try
            {
                await File.WriteAllTextAsync(membershipTemporary,JsonSerializer.Serialize(membership),stop.Token);
                File.Move(membershipTemporary,membershipPath,true);
            }
            finally{if(File.Exists(membershipTemporary))File.Delete(membershipTemporary);}
            continuousArrivalAccepted=Directory.EnumerateFiles(directory,"arrival-*.json").Any(path=>
            {
                using var report=JsonDocument.Parse(File.ReadAllText(path));var value=report.RootElement;
                bool True(string name)=>value.TryGetProperty(name,out var field)&&field.ValueKind==JsonValueKind.True;
                // Provenance diagnostics also end in .json but are not arrival
                // reports. Software cruise proves its original safe-drop call
                // instead of claiming the disabled game assist engaged.
                var cruise=True("cruiseEngaged")||value.TryGetProperty("useNativeCruise",out var native)&&native.ValueKind==JsonValueKind.False
                    &&True("cruiseObserved")&&True("safeDropRequested");
                return True("passed")&&cruise&&True("dockingGranted")&&True("dockingEngaged")&&True("finalDocked");
            });
        }
        catch(Exception error){failure=error.ToString();Console.Error.WriteLine(error.Message);}
        finally
        {
            stop.Cancel();if(services is not null)try{await services;}catch(Exception error){failure??=error.ToString();}
            await driver.ReleaseAllAsync(); // The independent protection outlives this journey.
            var result=JsonSerializer.Serialize(new{passed=failure is null,pid,started=checkpoint.StartedAt,attemptStarted=started,finished=DateTimeOffset.UtcNow,checkpoint,profit=ledger.Snapshot,
                finalSystem=Player().SystemName,finalStation=Player().StationName,Player().CargoCount,Player().Credits,openingCredits,closingCredits,walletReconciled,
                continuousArrivalAccepted,failure,rewardsClaimed=0,screenUse=false},ToolboxSettings.JsonOptions);
            await File.WriteAllTextAsync(Path.Combine(directory,"attempt-"+attempt+".json"),result);
            await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),result);
        }
        if(failure is not null)throw new InvalidOperationException("CG cargo journey paused for reconciliation; original receipts were retained.");
        Console.WriteLine(JsonSerializer.Serialize(new{passed=true,quantity=order.Quantity,profit=ledger.Snapshot}));
    }
}