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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
"""Read-only inventory of the installed game's signed local resource headers."""
import concurrent.futures, hashlib, json, pathlib, re, struct, sys
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

ROOT=pathlib.Path(r'D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/Win64')
KEY=json.loads(pathlib.Path('artifacts/ship-model-reference/local-asset-public-key.json').read_text())
N,E=int(KEY['n']),KEY['e']
OUTPUT=pathlib.Path(sys.argv[1]); OUTPUT.parent.mkdir(parents=True,exist_ok=True)
PATTERN=re.compile(rb'galax|milky|skybox|skydome|starfield|star_field|nebula|sector|catalog|stellarforge|starcatalog',re.I)
def decode(path, limit=None):
    with path.open('rb') as f: data=f.read(limit or -1)
    if data[:4] not in (b'FREA',b'FREE'): raise ValueError('Unknown archive')
    raw=pow(int.from_bytes(data[6:262],'big'),E,N).to_bytes(256,'big')
    sep=raw.index(b'\0',2)
    if sep<10: raise ValueError('Invalid signature')
    message=raw[sep+1:]; size=struct.unpack_from('<I',message)[0]
    key=bytes(x^path.name.lower().encode()[i%40] for i,x in enumerate(message[4:36]))
    end=262+(len(data)-262)//16*16
    decrypt=Cipher(algorithms.AES(key),modes.ECB()).decryptor()
    return (message[36:]+decrypt.update(data[262:end])+decrypt.finalize())[:size]
def scan(directory):
    count=0; found=[]
    for path in directory.iterdir():
        if not path.is_file(): continue
        with path.open('rb') as f: data=f.read(262)
        if data[:4]!=b'FREA': continue
        count+=1
        raw=pow(int.from_bytes(data[6:262],'big'),E,N).to_bytes(256,'big')
        sep=raw.find(b'\0',2)
        if sep<10: continue
        header=raw[sep+37:]
        if not PATTERN.search(header): continue
        prefix=decode(path,262+8192)
        strings=[m.group().decode('ascii') for m in re.finditer(rb'[A-Za-z_][A-Za-z0-9_.:/ -]{4,}',prefix)]
        found.append({'path':str(path),'bytes':path.stat().st_size,'strings':strings[:28]})
    return count,found
if __name__=='__main__':
    rows=[]; total=0
    with concurrent.futures.ThreadPoolExecutor(max_workers=6) as pool:
        for count,found in pool.map(scan,sorted(p for p in ROOT.iterdir() if p.is_dir())):
            total+=count; rows+=found
            OUTPUT.write_text(json.dumps({'count':total,'resources':rows},indent=2))
            print('scanned',total,'candidates',len(rows),flush=True)