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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
#if PLUGIN_SESSION_RECOVERY
namespace XFEEDToolBox.Plugin;

internal static unsafe partial class SessionRecoveryRuntime
{
    private struct TradePauseAuthorization
    {internal string? owner,run;internal ulong epoch,expires,entity;}
    private struct TradePauseResumeWork
    {internal string? id,owner,run;internal ulong epoch,expires,sequence;internal bool pending,applied,failed;}
    private static TradePauseAuthorization tradePauseAuthorization;
    private static TradePauseResumeWork tradePauseWork;
    private static readonly object tradePauseDispatchLock=new();
    private struct TradePauseIdentity
    {internal ulong entity,tick,utc,sequence;internal double hull,shield;internal bool verified;}
    private static TradePauseIdentity tradePauseIdentity;
    private static void ObserveTradePauseIdentity(bool force=false)
    {
        if(!enabled||GetCurrentThreadId()!=expectedThread)return;
        Root current;TradePauseIdentity previous;lock(gate){current=root;previous=tradePauseIdentity;}
        var now=GetTickCount64();if(!force&&previous.tick!=0&&now>=previous.tick&&now-previous.tick<50)return;
        var valid=current.valid&&Fresh(current.tick,now)&&current.state==current.next&&current.state is "InGame" or "Pause";
        ulong entity=0;double hull=double.NaN,shield=double.NaN;
        valid=valid&&Navigation.TryReadTradePauseShipHealth(out entity,out hull,out shield);
        var observed=new TradePauseIdentity{entity=entity,hull=hull,shield=shield,tick=now,utc=Utc(),sequence=previous.sequence+1,verified=valid};
        lock(gate)tradePauseIdentity=observed;
    }
    private static bool TradePauseIdentityReady(ulong entity,ulong now)
    {
        lock(gate)return tradePauseIdentity.verified&&tradePauseIdentity.entity==entity&&entity!=0&&Fresh(tradePauseIdentity.tick,now)
            &&double.IsFinite(tradePauseIdentity.hull)&&tradePauseIdentity.hull>=.5;
    }
    private static JsonObject TradePauseIdentityDescription()
    {
        lock(gate)
        {
            var value=tradePauseIdentity;
            return Obj(("verified",value.verified&&Fresh(value.tick,GetTickCount64())),("entityId",value.entity.ToString()),("sequence",value.sequence),
                ("observedAtUnixMilliseconds",value.utc),("hullFraction",double.IsFinite(value.hull)?(double?)value.hull:null),
                ("shieldFraction",double.IsFinite(value.shield)?(double?)value.shield:null),("source","current-main-ship-on-original-menu-thread"),("physicsTelemetryRequired",false));
        }
    }
    private static bool TradePauseKey(string value)=>value.Length==32&&value.All(c=>c is >= '0' and <= '9' or >= 'a' and <= 'f');
    private static bool TradePauseMenuReady(Root r,Menu m,ulong now)
        =>enabled&&r.valid&&r.thread==expectedThread&&m.thread==expectedThread&&r.flow==m.flow
            &&r.state=="Pause"&&r.next=="Pause"&&m.categoryName=="Pause"&&m.kind==1&&m.ready&&Fresh(r.tick,now)&&Fresh(m.tick,now)
            &&m.names.Count(name=>name=="InGame")==1&&m.names.Where((name,index)=>name=="InGame"&&m.allowed[index]).Count()==1;
    private static bool TradePauseAuthorized(TradePauseAuthorization authorization,TradePauseResumeWork wanted,ulong now)
        =>authorization.expires>now&&authorization.entity!=0&&authorization.owner==wanted.owner&&authorization.run==wanted.run
            &&authorization.epoch==wanted.epoch&&wanted.pending&&wanted.expires>now;
    private static JsonObject TradePauseDescription()
    {
        lock(gate)
        {
            var now=GetTickCount64();var authorization=tradePauseAuthorization;var wanted=tradePauseWork;
            return Obj(("schema",1),("scopeActive",authorization.expires>now),("owner",authorization.owner),("runId",authorization.run),
                ("entityId",authorization.entity.ToString()),("scopeExpiresInMilliseconds",authorization.expires>now?authorization.expires-now:0),
                ("requestId",wanted.id),("pending",wanted.pending&&wanted.expires>now&&authorization.expires>now),
                ("applied",wanted.applied),("failed",wanted.failed||wanted.pending&&(wanted.expires<=now||authorization.expires<=now)),
                ("action","InGame"),("onlyStablePauseCategory",true));
        }
    }
    private static void CancelTradePauseScope(string? owner=null,string? run=null)
    {
        lock(tradePauseDispatchLock)lock(gate)
        {
            if(owner is not null&&(tradePauseAuthorization.owner!=owner||tradePauseAuthorization.run!=run))return;
            tradePauseAuthorization.expires=0;++tradePauseAuthorization.epoch;tradePauseWork.pending=false;
        }
    }
    private static JsonNode TradePauseRequest(string op,JsonElement request)
    {
        var owner=request.Str("owner");var run=request.Str("runId");
        if(!TradePauseKey(owner)||!TradePauseKey(run))throw new InvalidOperationException("An explicit running trade scope is required");
        if(op=="trade.pause.end"){CancelTradePauseScope(owner,run);return Description();}
        lock(tradePauseDispatchLock)
        {
            var now=GetTickCount64();Root observed;lock(gate)observed=root;
            if(!enabled||!observed.valid||!Fresh(observed.tick,now)||observed.thread!=expectedThread||observed.state!=observed.next
                ||observed.state is not("InGame" or "Pause"))throw new InvalidOperationException("A stable current game or Pause context is required");
            if(op=="trade.pause.renew")
            {
                var ttl=request.Int("ttlMilliseconds");var entity=request.U64("entityId");
                if(ttl is <1 or >750||entity==0||!TradePauseIdentityReady(entity,now))throw new InvalidOperationException("The running trade main ship is not verified");
                lock(gate)
                {
                    if(work.pending&&work.expires>now)throw new InvalidOperationException("An explicit session workflow owns the original menu");
                    var before=tradePauseAuthorization;
                    if(before.expires>now&&(before.owner!=owner||before.run!=run))throw new InvalidOperationException("Another running trade scope owns pause recovery");
                    if(before.expires>now&&before.entity!=entity&&observed.state!="InGame")throw new InvalidOperationException("A changed ship cannot be admitted from a Pause menu");
                    if(before.owner!=owner||before.run!=run||before.entity!=entity||before.expires<=now)
                    {++before.epoch;tradePauseWork.pending=false;}
                    before.owner=owner;before.run=run;before.entity=entity;before.expires=now+(ulong)ttl;tradePauseAuthorization=before;
                }
                return Description();
            }
            if(op!="trade.pause.resume")throw new InvalidOperationException("Unknown running-trade pause operation");
            var id=request.Str("requestId");var expected=request.U64("menuSequence");var deadline=request.Int("ttlMilliseconds");
            if(!TradePauseKey(id)||expected==0||deadline is <1 or >150)throw new InvalidOperationException("A bounded original Pause resume request is required");
            lock(gate)
            {
                if(tradePauseWork.id==id)return Description();
                var authorization=tradePauseAuthorization;
                if(authorization.owner!=owner||authorization.run!=run||authorization.expires<=now||authorization.entity==0
                    ||work.pending&&work.expires>now||tradePauseWork.pending&&tradePauseWork.expires>now||!TradePauseMenuReady(root,menu,now)
                    ||expected>menu.sequence||menu.sequence-expected>32)throw new InvalidOperationException("The running trade Pause menu changed before resume");
                tradePauseWork=new(){id=id,owner=owner,run=run,epoch=authorization.epoch,expires=Math.Min(now+(ulong)deadline,menu.tick+200),sequence=expected,pending=true};
            }
            return Description();
        }
    }
    private static void PollTradePauseResume(nuint activity,Menu observed,Root r)
    {
        lock(tradePauseDispatchLock)
        {
            var now=GetTickCount64();TradePauseAuthorization authorization;TradePauseResumeWork wanted;Work other;
            lock(gate){authorization=tradePauseAuthorization;wanted=tradePauseWork;other=work;}
            if(!TradePauseAuthorized(authorization,wanted,now))return;
            if(other.pending&&other.expires>now||!TradePauseMenuReady(r,observed,now)||observed.sequence<wanted.sequence
                ||GetCurrentThreadId()!=expectedThread)return;
            ObserveTradePauseIdentity(force:true);
            if(!TradePauseIdentityReady(authorization.entity,GetTickCount64()))return;
            var fresh=CaptureMenu(activity);Root current;lock(gate)current=root;
            if(!TradePauseMenuReady(current,fresh,GetTickCount64())||current.flow!=r.flow||current.csm!=r.csm
                ||Name(Pointer(Pointer(current.csm+0x100)))!="Pause"||Name(Pointer(Pointer(current.csm+0x108)))!="Pause")return;
            var index=Array.FindIndex(fresh.names,name=>name=="InGame");if(index<0||!fresh.allowed[index])return;
            lock(gate)
            {
                if(tradePauseWork.id!=wanted.id||!TradePauseAuthorized(tradePauseAuthorization,tradePauseWork,GetTickCount64()))return;
                tradePauseWork.pending=false;
            }
            var applied=false;
            try{Call(image+PauseSelect,(ulong)(activity+0x80),0,(ulong)index);applied=true;}
            finally{lock(gate)if(tradePauseWork.id==wanted.id){tradePauseWork.applied=applied;tradePauseWork.failed=!applied;}}
        }
    }
    private static bool TradePauseGuardSelfTest()
    {
        var previousEnabled=enabled;var previousThread=expectedThread;var previousIdentity=tradePauseIdentity;
        try
        {
            enabled=true;expectedThread=7;
            var r=new Root{valid=true,state="Pause",next="Pause",flow=0x10000,tick=1000,thread=7};
            var m=new Menu{ready=true,flow=r.flow,tick=1000,sequence=9,thread=7,categoryName="Pause",kind=1,names=["InGame","ExitOptions"],allowed=[true,true]};
            if(!TradePauseMenuReady(r,m,1100))return false;
            foreach(var state in new[]{"InGame","MainMenu","ExitOptions","Rebirth","NetworkError","Pause Options"})
            {var wrong=r;wrong.state=wrong.next=state;if(TradePauseMenuReady(wrong,m,1100))return false;}
            var transitional=r;transitional.next="InGame";if(TradePauseMenuReady(transitional,m,1100))return false;
            m.allowed[0]=false;if(TradePauseMenuReady(r,m,1100))return false;m.allowed[0]=true;
            m.names=["InGame","InGame"];if(TradePauseMenuReady(r,m,1100))return false;m.names=["InGame","ExitOptions"];
            m.kind=2;if(TradePauseMenuReady(r,m,1100))return false;m.kind=1;
            if(TradePauseMenuReady(r,m,1201))return false;
            // A stopped physics producer is immaterial: this separate current
            // menu-thread identity must still be fresh, healthy and identical.
            tradePauseIdentity=new(){verified=true,entity=42,tick=1000,hull=1};
            if(!TradePauseIdentityReady(42,1100)||TradePauseIdentityReady(43,1100)||TradePauseIdentityReady(42,1201))return false;
            tradePauseIdentity.hull=.49;if(TradePauseIdentityReady(42,1100))return false;
            tradePauseIdentity.hull=1;tradePauseIdentity.verified=false;if(TradePauseIdentityReady(42,1100))return false;
            var authorization=new TradePauseAuthorization{owner="one",run="run",entity=42,epoch=3,expires=1200};
            var wanted=new TradePauseResumeWork{owner="one",run="run",epoch=3,expires=1150,pending=true};
            if(!TradePauseAuthorized(authorization,wanted,1100)||TradePauseAuthorized(authorization,wanted,1150))return false;
            authorization.epoch++;if(TradePauseAuthorized(authorization,wanted,1100))return false;
            authorization.epoch=3;authorization.expires=0;return !TradePauseAuthorized(authorization,wanted,1100);
        }
        finally{enabled=previousEnabled;expectedThread=previousThread;tradePauseIdentity=previousIdentity;}
    }
}
#endif