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

namespace XFEEliteDangerous.OfflineRuntime;

/// <summary>Observes every call to the pinned formatted engine log sink, before its own level filter.</summary>
internal static unsafe class NativeEngineLog
{
    private static NativeHook? hook;
    [ThreadStatic]private static bool recording;
    private static long unavailable,reentrant;
    internal static void Install(nint image)
    {
        const string prefix="48895c241048897424205557415441564157488d6c24c94881ecc0000000488b";
        if(!new ReadOnlySpan<byte>((void*)(image+0x560DE0),prefix.Length/2).SequenceEqual(Convert.FromHexString(prefix)))
            throw new InvalidDataException("The pinned engine log sink differs.");
        hook=NativeHook.ObserveFourArgumentsAndCaller(image+0x560DE0,(nint)(delegate* unmanaged<nint,uint,nint,nint,nint,void>)&Observe);
        hook.Enable();
        OfflineDiagnostics.Current?.Write("info","native.engine","log.sink.installed","Capturing formatted native engine logs before their level filter.",
            new Dictionary<string,string?>{["nativeEntryRva"]="0x560DE0",["build"]="2026.09.03.332841"});
    }
    [UnmanagedCallersOnly]private static void Observe(nint logger,uint severity,nint message,nint unused,nint caller)
    {
        if(recording){Interlocked.Increment(ref reentrant);return;}
        recording=true;
        try
        {
            var fields=NativeCodeMap.Locate(caller);
            if(fields.GetValueOrDefault("nativeFunctionRva")=="0x99E190"&&OfflineCurl.NativeCompletionContext is {} completion)
                foreach(var entry in completion)fields[entry.Key]=entry.Value;
            fields["nativeSinkRva"]="0x560DE0";fields["nativeSeverity"]=severity.ToString();
            fields["nativeThreadId"]=ProbeNative.GetCurrentThreadId().ToString();
            fields["unavailableMessages"]=Interlocked.Read(ref unavailable).ToString();
            fields["reentrantMessages"]=Interlocked.Read(ref reentrant).ToString();
            var text=Text(message);
            if(text is null){Interlocked.Increment(ref unavailable);OfflineDiagnostics.Current?.Write("warning","native.engine","message.unreadable","Native text was unreadable.",fields);return;}
            var level=new[]{"error","failed","exception","disconnect","assert","invalid"}.Any(value=>text.Contains(value,StringComparison.OrdinalIgnoreCase))?"warning":"debug";
            OfflineDiagnostics.Current?.Write(level,"native.engine","engine.message",text,fields);
        }
        catch(Exception error){OfflineDiagnostics.Current?.Write("error","native.engine","observer.failed","Native logging observation failed.",exception:error);}
        finally{recording=false;}
    }
    private static string? Text(nint pointer)
    {
        if(pointer<0x10000)return null;
        Span<byte> bytes=stackalloc byte[4096];
        for(var offset=0;offset<bytes.Length;offset+=64)
        {
            fixed(byte* destination=bytes)
                if(!ProbeNative.ReadProcessMemory(Native.GetCurrentProcess(),pointer+offset,destination+offset,64,out var read)||read!=64)
                    return offset==0?null:Encoding.UTF8.GetString(bytes[..offset])+" [unreadable suffix]";
            var end=bytes.Slice(offset,64).IndexOf((byte)0);
            if(end>=0)return Encoding.UTF8.GetString(bytes[..(offset+end)]);
        }
        return Encoding.UTF8.GetString(bytes)+" [truncated]";
    }
}