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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
"""Bounded native DLL verification. No screen or OS-input automation."""
import argparse
import ctypes as c
from ctypes import wintypes as w
import hashlib
import json
from pathlib import Path
import time
import uuid

p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--pid',type=int,required=True)
p.add_argument('--profile',type=Path,required=True)
p.add_argument('--dll',type=Path)
p.add_argument('--output',type=Path,required=True)
p.add_argument('--seconds',type=int,default=8,choices=range(2,31))
p.add_argument('--axis',choices=['observe','yaw','pitch','forward'],default='observe')
args=p.parse_args()
profile=json.loads(args.profile.read_text(encoding='utf-8-sig'))
if hashlib.sha256(Path(profile['image']).read_bytes()).hexdigest()!=profile['sha256']:
    raise SystemExit('Game image does not match the native build profile')
if args.dll:
    import frida
    dll=args.dll.resolve(strict=True)
    if not dll.is_relative_to(Path('artifacts/navigation-native-build').resolve()):
        raise SystemExit('Only this task\'s own native build may be loaded')
    session=frida.attach(args.pid)
    try:
        script=session.create_script('''
const game=Process.getModuleByName('EliteDangerous64.exe');
if(game.path.toLowerCase()!==IMAGE.toLowerCase())throw new Error('Unexpected game image');
rpc.exports={load(path){const load=new NativeFunction(Module.getGlobalExportByName('LoadLibraryW'),'pointer',['pointer']);const module=load(Memory.allocUtf16String(path));if(module.isNull())throw new Error('Native module load failed');const own=Process.findModuleByAddress(module);const start=new NativeFunction(own.getExportByName('XFENavigationStart'),'uint',[]);if(start()!==1)throw new Error('Explicit native observation initialization failed');return module.toString();}};
'''.replace('IMAGE',json.dumps(profile['image'])))
        script.load();print(json.dumps({'loadedModule':script.exports_sync.load(str(dll)),'sha256':hashlib.sha256(dll.read_bytes()).hexdigest()}),flush=True);script.unload()
    finally:session.detach()

k=c.WinDLL('kernel32',use_last_error=True)
k.CreateFileW.argtypes=[w.LPCWSTR,w.DWORD,w.DWORD,c.c_void_p,w.DWORD,w.DWORD,w.HANDLE];k.CreateFileW.restype=w.HANDLE
k.WaitNamedPipeW.argtypes=[w.LPCWSTR,w.DWORD];k.WaitNamedPipeW.restype=w.BOOL
k.WriteFile.argtypes=[w.HANDLE,c.c_void_p,w.DWORD,c.POINTER(w.DWORD),c.c_void_p];k.WriteFile.restype=w.BOOL
k.ReadFile.argtypes=[w.HANDLE,c.c_void_p,w.DWORD,c.POINTER(w.DWORD),c.c_void_p];k.ReadFile.restype=w.BOOL
k.CloseHandle.argtypes=[w.HANDLE]
pipe_name=rf'\\.\pipe\XFEEDToolBox.Navigation.{args.pid}'
def call(request):
    # The single-instance server closes and recreates its endpoint between messages.
    # Retry only connection establishment; never replay a command after a write.
    deadline=time.monotonic()+.25
    while True:
        if k.WaitNamedPipeW(pipe_name,40):
            pipe=k.CreateFileW(pipe_name,0xC0000000,0,None,3,0,None)
            if pipe!=c.c_void_p(-1).value:break
        error=c.get_last_error()
        if error not in (2,121,231) or time.monotonic()>=deadline:raise c.WinError(error)
        time.sleep(.001)
    try:
        body=(json.dumps(request)+'\n').encode();count=w.DWORD()
        if not k.WriteFile(pipe,body,len(body),c.byref(count),None):raise c.WinError(c.get_last_error())
        buffer=c.create_string_buffer(65536)
        if not k.ReadFile(pipe,buffer,len(buffer),c.byref(count),None):raise c.WinError(c.get_last_error())
        result=json.loads(buffer.raw[:count.value])
        if not result.get('ok'):raise RuntimeError(result.get('error','Native operation failed'))
        return result['result']
    finally:k.CloseHandle(pipe)

args.output.parent.mkdir(parents=True,exist_ok=True)
owner=uuid.uuid4().hex
with args.output.open('x',encoding='utf-8') as output:
    deadline=time.monotonic()+5
    while True:
        try:description=call({'op':'describe'});break
        except OSError:
            if time.monotonic()>deadline:raise
            time.sleep(.15)
    output.write(json.dumps({'kind':'native-description',**description})+'\n');output.flush();print(json.dumps(description),flush=True)
    if description.get('gameSha256')!=profile['sha256'] or description.get('processId')!=args.pid:raise RuntimeError('Native module identity mismatch')
    if not description.get('continuousPose'):
        raise RuntimeError('Native telemetry is not available; no input has been sent')
    begin=time.monotonic();samples=0
    try:
        while time.monotonic()-begin<args.seconds:
            frame=call({'op':'read'});elapsed=time.monotonic()-begin
            row={'kind':'native-pipe-sample','elapsed':elapsed,**frame}
            if args.axis!='observe':
                # A brief low-authority pulse, followed by zero axes; no FSD commands are supported here.
                axes=[0.0]*6
                if 2<=elapsed<4:axes[{'forward':0,'yaw':3,'pitch':4}[args.axis]]=.08
                row['command']=call({'op':'calibrate.axes','owner':owner,'sequence':frame['sequence'],'ttlMilliseconds':180,'axes':axes})
                row['requestedAxes']=axes
            output.write(json.dumps(row)+'\n');output.flush();samples+=1;time.sleep(.045)
    finally:
        if args.axis!='observe':
            release=call({'op':'release','owner':owner});output.write(json.dumps({'kind':'released',**release})+'\n');output.flush()
            for _ in range(8):
                time.sleep(.06)
                output.write(json.dumps({'kind':'native-release-observation',**call({'op':'read'})})+'\n');output.flush()
    print(json.dumps({'samples':samples,'mode':args.axis,'screenUse':False,'fsdCommandsSent':0}),flush=True)