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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

namespace XFEEDToolBox.Core.Execution;

/// <summary>Reconciles an unaccepted key only in the exact original live game session.</summary>
public static class NativeUnsubmittedTradeIntents
{
    public static async Task ReconcileAsync(string directory,IOriginalCommerceChannel channel,PlayerState player,CancellationToken token)
    {
        if(!Directory.Exists(directory)||!player.InGame||!player.Docked||!player.CargoReliable||!player.CreditsReliable)return;
        foreach(var pending in Directory.EnumerateFiles(directory,"*.pending.json"))
        {
            var confirmed=pending.Replace(".pending.json",".confirmed.json",StringComparison.Ordinal);
            if(File.Exists(confirmed))continue;
            byte[] bytes;
            // Exclude an active writer before inspecting older incomplete files.
            await using(var input=new FileStream(pending,FileMode.Open,FileAccess.Read,FileShare.Read))
            {using var buffer=new MemoryStream();await input.CopyToAsync(buffer,token);bytes=buffer.ToArray();}
            if(bytes.Length==0)
            {
                // Only the still-live process that spans this file's creation
                // can prove a key was never accepted. A reload cannot do so.
                var created=new DateTimeOffset(File.GetCreationTimeUtc(pending));
                if(channel.ProcessStartedAt is not {} birth||created<birth||player.LoadGameAt is not {} loaded||created<loaded)continue;
                var emptyKey=Path.GetFileName(pending).Replace(".pending.json","",StringComparison.Ordinal);
                if(!Guid.TryParseExact(emptyKey,"N",out _))continue;
                var emptyStatus=await channel.CallAsync(new{op="commerce.operation.status",operationKey=emptyKey},token);
                if(emptyStatus.GetProperty("known").GetBoolean()||emptyStatus.GetProperty("mutationsSent").GetInt32()!=0)continue;
                var inventory=await channel.CallAsync(new{op="commerce.inventory"},token);
                if(!inventory.GetProperty("verified").GetBoolean()||inventory.GetProperty("count").GetInt32()!=player.CargoCount)continue;
                var stable=await channel.CallAsync(new{op="commerce.inventory"},token);
                if(!stable.GetProperty("verified").GetBoolean()||stable.GetProperty("count").GetInt32()!=inventory.GetProperty("count").GetInt32()
                    ||stable.GetProperty("credits").GetInt64()!=inventory.GetProperty("credits").GetInt64())continue;
                var proof=JsonSerializer.SerializeToUtf8Bytes(new{operationKey=emptyKey,submitted=false,malformedIntent=true,channel=channel.Identity,
                    processStartedAt=birth,fileCreatedAt=created,sessionStartedAt=loaded,pendingSha256=Convert.ToHexStringLower(SHA256.HashData(bytes)),
                    originalOperationStatus=emptyStatus,actualClientInventory=inventory,stableClientInventory=stable,reconciledAt=DateTimeOffset.UtcNow});
                await File.WriteAllBytesAsync(confirmed+".tmp",proof,token);token.ThrowIfCancellationRequested();File.Move(confirmed+".tmp",confirmed,false);
                continue;
            }
            using var document=JsonDocument.Parse(bytes);var row=document.RootElement;
            if(row.GetProperty("channel").GetString()!=channel.Identity||row.GetProperty("CommanderId").GetString()!=player.CommanderId
                ||row.GetProperty("CurrentShipId").GetInt64()!=player.CurrentShipId||row.GetProperty("LoadGameAt").GetDateTimeOffset()!=player.LoadGameAt)continue;
            var key=row.GetProperty("operationKey").GetString()!;
            var status=await channel.CallAsync(new{op="commerce.operation.status",operationKey=key},token);
            // The native map retains every accepted key for this process's
            // lifetime. A new process/session may never use absence as proof.
            if(status.GetProperty("known").GetBoolean()||status.GetProperty("mutationsSent").GetInt32()!=0)continue;
            var before=row.GetProperty("intent").GetProperty("before");
            var current=await channel.CallAsync(new{op="commerce.inventory"},token);
            if(!current.GetProperty("verified").GetBoolean()||!before.GetProperty("verified").GetBoolean()
                ||current.GetProperty("count").GetInt32()!=before.GetProperty("count").GetInt32()
                ||current.GetProperty("credits").GetInt64()!=before.GetProperty("credits").GetInt64())continue;
            await using var file=new FileStream(confirmed,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
            await JsonSerializer.SerializeAsync(file,new{operationKey=key,submitted=false,reconciledAt=DateTimeOffset.UtcNow,channel=channel.Identity,
                pendingSha256=Convert.ToHexStringLower(SHA256.HashData(bytes)),originalOperationStatus=status,actualClientInventory=current},cancellationToken:token);
        }
    }
}