XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFEEliteDangerous.Core;
using XFEEliteDangerous.NativeFormat;

namespace XFEEliteDangerous.OfflineRuntime;

internal static unsafe partial class OfflineCurl
{
    private static readonly StringBuilder fixtureHeaders = new();
    private static readonly MemoryStream fixtureBody = new();
    private static byte[] fixtureUpload = [];
    private static int fixtureCursor;
    private static int fixtureProgressCalls;
    [UnmanagedCallersOnly] private static int FixtureOption(nint handle,int option,nint value)=>0;
    [UnmanagedCallersOnly] private static int FixtureProgress(nint context,double downloadTotal,double downloadNow,double uploadTotal,double uploadNow)
    { fixtureProgressCalls++;return 0; }
    [UnmanagedCallersOnly] private static nuint FixtureWrite(byte* bytes,nuint size,nuint count,nint context)
    {
        var span=new ReadOnlySpan<byte>(bytes,checked((int)(size*count)));
        if(context==1)fixtureHeaders.Append(Encoding.ASCII.GetString(span));else fixtureBody.Write(span);
        return (nuint)span.Length;
    }
    [UnmanagedCallersOnly] private static nuint FixtureRead(byte* bytes,nuint size,nuint count,nint context)
    {
        var length=Math.Min(checked((int)(size*count)),fixtureUpload.Length-fixtureCursor);
        fixtureUpload.AsSpan(fixtureCursor,length).CopyTo(new Span<byte>(bytes,length));fixtureCursor+=length;return(nuint)length;
    }
    internal static int CallbackSelfTest(OfflineGameData source,string directory)
    {
        if(hooks.Count!=0)throw new InvalidOperationException("Callback fixtures never run against installed game hooks.");
        var oldData=data;var oldKey=key;var oldLog=log;var oldOption=originalOption;var oldCompleted=completed;
        var allocations=new List<nint>();var handle=(nint)0x12345;var multi=(nint)0x54321;
        try
        {
            data=source;key=RandomNumberGenerator.GetBytes(32);log=Path.Combine(directory,"callback-fixture.tsv");originalOption=&FixtureOption;
            requests[handle]=new();
            var headers=Allocate(32);Marshal.WriteIntPtr(headers,TextBuffer("Nonce: 001122334455"));Marshal.WriteIntPtr(headers+8,headers+16);
            Marshal.WriteIntPtr(headers+16,TextBuffer("Encrypted: 1"));Marshal.WriteIntPtr(headers+24,0);
            fixtureProgressCalls=0;
            Set(20056,(nint)(delegate* unmanaged<nint,double,double,double,double,int>)&FixtureProgress);
            Set(10023,headers);Set(20079,(nint)(delegate* unmanaged<byte*,nuint,nuint,nint,nuint>)&FixtureWrite);Set(10029,1);
            Set(20011,(nint)(delegate* unmanaged<byte*,nuint,nuint,nint,nuint>)&FixtureWrite);Set(10001,2);
            Set(10002,TextBuffer("https://local.invalid/2.0/server/time"));
            if(!Complete()||requests[handle].Status!=200)return 24;
            using(var time=JsonDocument.Parse(fixtureBody.ToArray()))if(time.RootElement.GetProperty("unixTimestamp").GetInt64()<1)return 25;
            var url=TextBuffer("https://local.invalid/2.0/elite/user/login?");Set(10002,url);
            // The caller's memory is no longer valid after setopt. This catches use-after-free
            // without ever starting a game, making a connection, or dereferencing game memory.
            new Span<byte>((void*)url,20).Fill((byte)'x');
            if(!Complete()||requests[handle].Status!=200)return 20;
            using(var login=JsonDocument.Parse(Response()))if(!login.RootElement.TryGetProperty("runId",out _))return 21;
            fixtureUpload=Encoding.ASCII.GetBytes(EliteRequestEnvelopeCodec.Encode(key,"001122334455",[],
                "{\"privacy_mode\":\"PRIVATE\",\"accepting_squadron_invites\":false}"u8).BodyBase64);fixtureCursor=0;
            Set(10002,TextBuffer("https://local.invalid/2.0/elite/commander/settings?"));
            Set(46,1);Set(14,fixtureUpload.Length);Set(20012,(nint)(delegate* unmanaged<byte*,nuint,nuint,nint,nuint>)&FixtureRead);Set(10009,1);
            if(!Complete()||requests[handle].Status!=200||fixtureCursor!=fixtureUpload.Length||Response()!="{}")return 22;
            var settings=source.InvokeAsync("GET","/2.0/elite/commander/settings",new Dictionary<string,string>(),[]).GetAwaiter().GetResult();
            using(var parsed=JsonDocument.Parse(settings.Payload))if(parsed.RootElement.GetProperty("settings").GetProperty("accepting_squadron_invites").GetBoolean())return 23;
            if(fixtureProgressCalls!=0)return 26;
            return 0;
            bool Complete()
            {
                fixtureHeaders.Clear();fixtureBody.SetLength(0);
                if(((delegate* unmanaged<nint,nint,int>)&Add)(multi,handle)!=0)return false;
                var remaining=0;var message=((delegate* unmanaged<nint,int*,nint>)&Next)(multi,&remaining);
                return message!=0&&*(int*)message==1&&*(nint*)(message+8)==handle&&*(int*)(message+16)==0&&remaining==0;
            }
            string Response()
            {
                var fields=fixtureHeaders.ToString().Split("\r\n").Where(line=>line.Contains(':')).Select(line=>line.Split(':',2)).ToDictionary(pair=>pair[0],pair=>pair[1].Trim());
                var result=EliteResponseEnvelopeCodec.Decode(key,new(fields["Nonce"],fields["uncompressedSize"],Encoding.ASCII.GetString(fixtureBody.ToArray())));
                try{return Encoding.UTF8.GetString(result.Payload);}finally{CryptographicOperations.ZeroMemory(result.Payload);}
            }
            void Set(int option,nint value){if(((delegate* unmanaged<nint,int,nint,int>)&Option)(handle,option,value)!=0)throw new InvalidOperationException("Owned fixture option failed.");}
            nint Allocate(int count){var pointer=(nint)NativeMemory.AllocZeroed((nuint)count);allocations.Add(pointer);return pointer;}
            nint TextBuffer(string text){var bytes=Encoding.UTF8.GetBytes(text+'\0');var pointer=Allocate(bytes.Length);Marshal.Copy(bytes,0,pointer,bytes.Length);return pointer;}
        }
        finally
        {
            requests.TryRemove(handle,out _);if(queues.TryRemove(multi,out var queue))NativeMemory.Free((void*)queue.Message);
            foreach(var pointer in allocations)NativeMemory.Free((void*)pointer);
            CryptographicOperations.ZeroMemory(key);CryptographicOperations.ZeroMemory(fixtureUpload);fixtureUpload=[];
            data=oldData;key=oldKey;log=oldLog;originalOption=oldOption;completed=oldCompleted;fixtureHeaders.Clear();fixtureBody.SetLength(0);
        }
    }
}