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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using static XFEEDToolBox.Plugin.JsonWire;

namespace XFEEDToolBox.Plugin;

internal static unsafe partial class Navigation
{
    private struct RearmTarget { public fixed byte slot[48]; public int clip, hopper; }
    [InlineArray(64)] private struct Rearm64 { private RearmTarget item; }
    private struct CommerceReadRequest
    {
        public ulong generation, entity, epoch, expires;
        public nuint life, request;
        public uint market, state, error, kind, commodity, price;
        public int delta, before, after, totalBefore;
        public ulong queued, observed;
        public bool mutationSent, clientApplied;
        public bool refreshClient,profileApplied,profileValidated;
        public bool formal;
        public int capacityBound;
        public fixed byte commodityName[128];
        public ulong serverCredits;
        public int serverCargo;
        public nuint maintenance;
        public bool borrowed;
        public ulong creditsBefore;
        public float fuelTarget, reserveTarget;
        public bool serviceReplyVerified;
        public ulong creditsResult;
        public float fuelResult, reserveResult, fuelCapacityResult, reserveCapacityResult;
        public uint nextRefuelCost, nextRepairCost, integrityWear;
        public fixed byte servicePayload[4096];
        public uint servicePayloadLength, rearmCount;
        public Rearm64 rearm;
        public CommerceReadRequest()
        {
            this = default;
            // The original response structure initializes these output-only fields to zero.
            nextRepairCost = 0; integrityWear = 0;
        }
    }
    private static readonly object commerceReadLock = new();
    private static CommerceReadRequest commerceRead;
    private static ulong commerceReadGeneration;
    private static string commerceReadBody = "";
    private static readonly byte[] commerceReadBuffer = new byte[1024 * 1024];
    private static bool commerceReconciliationRequired;
    private static readonly Dictionary<string, ulong> commerceOperationKeys = new(StringComparer.Ordinal);
    private struct CommerceShipInventory { public uint count, capacity; public ulong credits; public bool verified; }

    private static CommerceShipInventory ReadCommerceShipInventory(in Snapshot frame)
    {
        CommerceShipInventory result = default;
        try
        {
            if (!frame.valid || !frame.healthValid || GetTickCount64() - frame.tick > 200) return result;
            if (GameRoute(frame, out var pilot) == 0) return result;
            var cargo = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F2F25C));
            var wallet = RegisteredComponent(Pointer(pilot + 0xC8) - 0x30, Read<uint>(image + 0x5F59D90));
            if (cargo == 0 || Pointer(cargo) != image + 0x530B858 || wallet == 0 || Pointer(wallet) != image + 0x5295868) return result;
            result.count = Read<uint>(cargo + 0x28C); result.capacity = Read<uint>(cargo + 0x288);
            result.credits = Read<ulong>(wallet + 0x98) ^ Read<ulong>(wallet + 0x90);
            result.verified = result.capacity <= int.MaxValue && result.count <= result.capacity && result.credits < 100000000000000UL;
        }
        catch { result.verified = false; }
        return result;
    }
    private static bool CommerceDocked(in Snapshot frame, uint market)
    {
        return ReadCommerceDocking(LocalDocking(frame), market, out _, out _);
    }
    private static bool ReadCommerceDocking(nuint dock,uint market,out uint state,out ulong actualMarket)
    {
        state=0;actualMarket=0;if(dock==0)return false;
        state=Read<uint>(dock+0x4C0);var station=Pointer(dock+0x4E0);
        if(station==0||Read<uint>(station+0xC0)!=8)return false;
        actualMarket=Read<ulong>(station+0xB0);
        return market is not(0 or uint.MaxValue)&&(state is 5 or 7)&&actualMarket==market;
    }
    private static JsonObject CommerceDockedStatus(JsonElement request,in Snapshot frame)
    {
        var market=request.Required("marketId").GetUInt32();
        if(market is 0 or uint.MaxValue)throw new InvalidOperationException("Identified docked market required");
        var now=GetTickCount64();var fresh=frame.valid&&now>=frame.tick&&now-frame.tick<=200;
        var healthy=fresh&&frame.healthValid&&!frame.healthEmergency&&double.IsFinite(frame.hullFraction)&&frame.hullFraction>0;
        uint state=0;ulong actualMarket=0;var docked=false;var service=false;string? issue=null;
        if(fresh)try{docked=ReadCommerceDocking(LocalDocking(frame),market,out state,out actualMarket);if(docked&&healthy)service=OriginalServiceBinding(frame,0,market);}catch(Exception error){issue=error.GetType().Name;}
        var ready=inGame&&Enabled()&&healthy&&docked&&issue is null;
        var stage=!inGame?"notInGame":!Enabled()?"disabled":!fresh?"telemetry":!healthy?"health":issue is not null?"observation":!docked?"docking":!service?"services":"ready";
        return Obj(("ready",ready),("serviceReady",ready&&service),("stage",stage),("fresh",fresh),("healthy",healthy),
            ("entityId",frame.entity.ToString(System.Globalization.CultureInfo.InvariantCulture)),("marketId",market),("actualMarketId",actualMarket),("rawDockingState",state),("issue",issue),("mutationsSent",0));
    }
    private static uint CommerceExceptionCode(Exception error) => unchecked((uint)error.HResult);
    private static bool PollCommerceReadImpl(in Snapshot frame, ref CommerceReadRequest work, byte* body, nuint capacity, out nuint size)
    {
        size = 0;
        try
        {
            if (!CommerceWorkCurrent(frame, ref work)) return false;
            if (work.request == 0)
            {
                var route = GameRoute(frame, out _); var context = route != 0 ? route - 0x50 : 0;
                work.error = 3;
                if (context == 0 || Pointer(Pointer(context)) != image + 0x541C70 || Pointer(context + 8) == 0) { work.state = 4; return false; }
                if (work.kind != 0)
                {
                    if (GetTickCount64() - work.queued > 250 || !(work.kind == 3 ? StartOriginalServiceRead(frame, ref work) : work.kind >= 4 ? StartOriginalServiceMutation(frame, ref work, context) : StartCommerceMutation(frame, ref work, context))) { work.state = 4; return false; }
                }
                else
                {
                    var storage = (nuint)Call(image + 0x53A690, 0xF8); work.error = 4;
                    if (storage == 0) { work.state = 4; return false; }
                    work.request = (nuint)Call(image + 0x1E84F00, storage, context); work.error = 5;
                    if (work.request == 0 || Pointer(work.request) != image + 0x511F9D0) { work.state = 4; return false; }
                    Call(image + 0x3425CF0, work.request, work.market, 1, 0, 0);
                }
                work.state = 2;
            }
            work.error = 0;
            var request = work.request;
            if (Read<uint>(request + 0x3C) is 1 or 2)
                if ((byte)Call(image + 0x9A3250, request) == 0) return true;
            if ((byte)Call(image + 0x9B5480, request) == 0)
            {
                work.error = (uint)Call(image + 0x9A8F10, request); work.state = 4; return false;
            }
            nuint text = 0;
            Call(image + 0x9A8DC0, request, (ulong)&text);
            var copied = false;
            try
            {
                var length = text != 0 ? Read<ulong>(text) : 0;
                if (length != 0 && length < capacity)
                {
                    CopyCommerceMemory(text + 0x14, body, checked((int)length)); size = (nuint)length; copied = true;
                }
            }
            finally { if (text != 0) Call(image + 0x204010, (ulong)&text); }
            work.state = copied ? 3u : 4u; if (!copied) work.error = 6; return copied;
        }
        catch (Exception error) { work.error = CommerceExceptionCode(error); work.state = 4; return false; }
    }
    private static void CopyCommerceMemory(nuint source, byte* destination, int length)
    {
        Platform.ReadBytes(source, length).CopyTo(new Span<byte>(destination, length));
    }
    private static void DeleteCommerceRead(ref CommerceReadRequest work)
    {
        try
        {
            var instance = work.request;
            if (!work.borrowed && instance != 0 && (Pointer(instance) == image + 0x511F9D0 || Pointer(instance) == image + 0x51A4640))
            {
                var destroy = Pointer(Pointer(instance)); if (InImage(destroy)) Call(destroy, instance, 1);
            }
        }
        catch { }
        work.request = 0;
    }
    private static void PollCommerceRead(in Snapshot frame)
    {
        // A busy IPC reader must never make the game thread wait.
        if (!Monitor.TryEnter(commerceReadLock)) return;
        try { PollCommerceReadOwned(frame); }
        finally { Monitor.Exit(commerceReadLock); }
    }
    private static void PollCommerceReadOwned(in Snapshot frame)
    {
        CommerceReadRequest work = commerceRead;
        if (work.generation == 0 || work.state >= 3) return;
        if (commerceCompletion is {} completion && completion.generation == work.generation)
        { PollCommerceCompletion(frame, completion); return; }
        nuint length;
        fixed (byte* body = commerceReadBuffer)
        {
            PollCommerceReadImpl(frame, ref work, body, (nuint)commerceReadBuffer.Length, out length);
            if (work.state == 3)
            {
                // Copy/release the original request here; decode and validate
                // its immutable response on a worker, then apply on a later tick.
                var capacity = work.refreshClient ? ReadCommerceShipInventory(frame).capacity : 0;
                var prepared = new CommerceCompletion(work, new ReadOnlySpan<byte>(body, checked((int)length)).ToArray(), capacity);
                DeleteCommerceRead(ref work); prepared.work.request = 0;
                commerceCompletion = prepared; work.state = 2; commerceRead = work;
                try
                {
                    if (!ThreadPool.QueueUserWorkItem(static item => PrepareCommerceCompletion(item), prepared, preferLocal: false))
                        throw new InvalidOperationException("Response worker unavailable");
                }
                catch { prepared.work.state = 4; prepared.work.error = 8; Volatile.Write(ref prepared.ready, true); }
                return;
            }
        }
        if (work.state == 3) work.observed = GetTickCount64();
        var uncertain = (work.kind == 1 || work.kind >= 4) && work.mutationSent && !work.clientApplied && work.state >= 3;
        if (work.state >= 3) DeleteCommerceRead(ref work);
        commerceRead = work; if (uncertain) commerceReconciliationRequired = true;
    }
    private static bool CommerceOperationKey(string value) => value.Length == 32 && value.All(c => c is >= '0' and <= '9' or >= 'a' and <= 'f');
    private static CommerceReadRequest NewCommerceRead(in Snapshot frame, uint market, ulong generation) => new()
    {
        generation = generation, entity = frame.entity, epoch = controllerEpoch, expires = GetTickCount64() + 30000,
        life = frame.life, market = market, state = 1
    };
    private static IEnumerable<JsonElement> CommerceRows(JsonElement value) => value.ValueKind == JsonValueKind.Array ? value.EnumerateArray().ToArray() : value.ValueKind == JsonValueKind.Object ? value.EnumerateObject().Select(x => x.Value).ToArray() : [];
    private static JsonNode? CommerceNode(JsonElement value) => JsonNode.Parse(value.GetRawText());
    private static JsonArray CommerceFields(JsonElement value)
    {
        var result = new JsonArray();
        foreach (var field in value.EnumerateObject())
        {
            var type = field.Value.ValueKind switch { JsonValueKind.Object => "object", JsonValueKind.Array => "array", JsonValueKind.String => "string", JsonValueKind.Number => "number", JsonValueKind.True or JsonValueKind.False => "boolean", _ => "null" };
            var size = field.Value.ValueKind switch { JsonValueKind.Object => field.Value.EnumerateObject().Count(), JsonValueKind.Array => field.Value.GetArrayLength(), JsonValueKind.Null => 0, _ => 1 };
            result.Add((JsonNode)Obj(("name", field.Name), ("type", type), ("count", size)));
        }
        return result;
    }
    private static JsonObject CommerceReadCall(string op, JsonElement request, in Snapshot frame)
    {
        if(op=="commerce.docked.status")return CommerceDockedStatus(request,frame);
        if (op == "commerce.operation.status")
        {
            var key = request.Required("operationKey").GetString()!;
            if (!CommerceOperationKey(key)) throw new InvalidOperationException("Identified original operation required");
            ulong generation, current;
            lock (commerceReadLock) { commerceOperationKeys.TryGetValue(key, out generation); current = commerceRead.generation; }
            if (generation == 0) return Obj(("known", false), ("mutationsSent", 0));
            if (generation != current) return Obj(("known", true), ("receiptAvailable", false), ("generation", generation), ("reconciliationRequired", true));
            using var query = JsonDocument.Parse(Obj(("generation", generation)).ToJsonString());
            var receipt = CommerceReadCall("commerce.market.status", query.RootElement, frame); receipt["known"] = true; receipt["receiptAvailable"] = true; return receipt;
        }
        if (op == "commerce.inventory")
        {
            var value = ReadCommerceShipInventory(frame); if (!value.verified) throw new InvalidOperationException("Original cargo and wallet observation unavailable");
            return Obj(("count", value.count), ("capacity", value.capacity), ("credits", value.credits), ("verified", value.verified), ("entityId", frame.entity.ToString(System.Globalization.CultureInfo.InvariantCulture)));
        }
        if (op == "commerce.market.read")
        {
            var market = request.Required("marketId").GetUInt32();
            if (market == 0 || market == uint.MaxValue || !inGame || !Enabled() || !frame.valid || !frame.healthValid || frame.healthEmergency || GetTickCount64() - frame.tick > 200 || !CommerceDocked(frame, market)) throw new InvalidOperationException("Actual healthy docked ship and matching market required");
            lock (commerceReadLock)
            {
                if (commerceRead.generation != 0 && commerceRead.state < 3) throw new InvalidOperationException("Original market read already pending");
                var refresh=request.Bool("refreshClient");
                if(refresh&&commerceReconciliationRequired)throw new InvalidOperationException("An uncertain original order must be reconciled before refreshing client inventory");
                commerceRead = NewCommerceRead(frame, market, ++commerceReadGeneration);commerceRead.refreshClient=refresh; commerceReadBody = "";
                return Obj(("generation", commerceRead.generation), ("queued", true), ("mutationsSent", 0));
            }
        }
        CommerceReadRequest work; string body;
        lock (commerceReadLock) { work = commerceRead; body = commerceReadBody; }
        if (request.Required("generation").GetUInt64() != work.generation) throw new InvalidOperationException("Market read generation changed");
        var result = Obj(("generation", work.generation), ("marketId", work.market), ("state", work.state), ("error", work.error), ("mutationsSent", work.mutationSent ? 1 : 0),
            ("clientApplied", work.clientApplied), ("reconciliationRequired", commerceReconciliationRequired), ("commodityId", work.commodity), ("quantityDelta", work.delta), ("before", work.before), ("after", work.after), ("unitPrice", work.price), ("serviceKind", work.kind), ("creditsBefore", work.creditsBefore));
        result["serverConfirmed"]=work.serviceReplyVerified;
        result["profileRefreshRequested"]=work.refreshClient;result["profileApplied"]=work.profileApplied;
        if(work.profileApplied){result["serverCredits"]=work.serverCredits;result["serverCargoCount"]=work.serverCargo;}
        if(work.state==4&&work.kind==1)
        {
            result["failureStage"]=work.serviceReplyVerified?"client-application":"server-response-validation";
            if(body.Length!=0)try
            {
                using var failed=JsonDocument.Parse(body);var failedResponse=failed.RootElement;
                if(failedResponse.TryGetProperty("credits",out var balance)&&balance.TryGetUInt64(out var actual))result["serverCredits"]=actual;
                result["serverCommodityCount"]=CommerceInventoryCount(failedResponse,work.commodity,CommerceExpectedCommodity(work));
                if(failedResponse.TryGetProperty("error",out var error))result["serverError"]=CommerceNode(error);
            }catch{result["serverResponseReadable"]=false;}
        }
        if (work.state != 3) return result;
        result["serverConfirmed"] = work.serviceReplyVerified; result["credits"] = work.creditsResult; result["cargoBefore"] = work.totalBefore; result["cargoAfter"] = work.totalBefore + work.delta;
        if (work.kind >= 4)
        {
            result["costToRepair"] = work.nextRepairCost; result["integrityWear"] = work.integrityWear;
            result["fuelPrices"] = Obj(("fuelLevel", work.fuelResult), ("fuelCapacity", work.fuelCapacityResult), ("reserveLevel", work.reserveResult), ("reserveCapacity", work.reserveCapacityResult), ("costToRefuel", work.nextRefuelCost));
            return result;
        }
        using var document = JsonDocument.Parse(body); var response = document.RootElement;
        result["responseFields"] = response.ValueKind == JsonValueKind.Object ? CommerceFields(response) : new JsonArray();
        var commodities = new JsonArray(); result["commodities"] = commodities;
        if (response.TryGetProperty("credits", out var credits) && credits.ValueKind == JsonValueKind.Number && (credits.TryGetInt64(out _) || credits.TryGetUInt64(out _))) result["serverCredits"] = CommerceNode(credits);
        if (response.TryGetProperty("prices", out var prices) && prices.ValueKind == JsonValueKind.Object)
        {
            result["priceFields"] = CommerceFields(prices); var fuelPrices = new JsonObject(); result["fuelPrices"] = fuelPrices;
            var fuel = prices.TryGetProperty("fuel", out var nestedFuel) ? nestedFuel : prices;
            foreach (var key in new[] { "costPerPartialUnit", "costToRefuel", "fuelLevel", "fuelCapacity", "reserveLevel", "reserveCapacity" }) if (fuel.TryGetProperty(key, out var field)) fuelPrices[key] = CommerceNode(field);
            var servicePrices = new JsonObject(); result["servicePrices"] = servicePrices;
            foreach (var key in new[] { "fuel", "repair", "ammunition" }) if (prices.TryGetProperty(key, out var field)) servicePrices[key] = CommerceNode(field);
        }
        uint index = 0; var offset = request.TryGetProperty("offset", out var at) ? at.GetUInt32() : 0; var maximum = Math.Min(request.TryGetProperty("maximum", out var max) ? max.GetUInt32() : 20, 50u);
        if (response.TryGetProperty("commodities", out var rows) && rows.ValueKind is JsonValueKind.Array or JsonValueKind.Object)
        {
            foreach (var row in CommerceRows(rows))
            {
                if (index++ < offset || commodities.Count >= maximum) continue;
                var quote = new JsonObject(); foreach (var key in new[] { "id", "name", "buyPrice", "sellPrice", "meanPrice", "stock", "demand", "statusFlags", "legality" }) if (row.TryGetProperty(key, out var field)) quote[key] = CommerceNode(field);
                commodities.Add((JsonNode)quote);
            }
        }
        result["commodityCount"] = index; var cargo = new JsonObject(); result["cargo"] = cargo;
        if (response.TryGetProperty("inventory", out var inventory) && inventory.ValueKind == JsonValueKind.Array)
            foreach (var row in inventory.EnumerateArray())
                if (row.TryGetProperty("commodity", out var commodity) && commodity.ValueKind == JsonValueKind.String && row.TryGetProperty("qty", out var quantity) && quantity.TryGetInt32(out var count) && !row.Bool("stolen") && row.Int("marked") == 0)
                { var name = commodity.GetString()!; cargo[name] = (cargo[name]?.GetValue<int>() ?? 0) + count; }
        return result;
    }
}