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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
"""Inspect the original map's material parameters; no game calls or shader claims."""
import hashlib,json,math,struct
from pathlib import Path

folder=Path('artifacts/galaxy-query-research/power-outer-sources/material')
key='19b9e393c96329f63707aeade6a46e8cf3b71398'
f=(folder/(key+'.fres')).read_bytes();b=(folder/(key+'.stream0')).read_bytes()
layout=json.loads((folder/'layout.json').read_text())
if hashlib.sha256(f).hexdigest()!=layout['fresSha256'] or hashlib.sha256(b).hexdigest()!=layout['streams'][0]['sha256']:raise ValueError('Pinned material source changed')
np=struct.unpack_from('<I',f,0x34)[0];nr=struct.unpack_from('<I',f,0x20)[0]
nb=struct.unpack_from('<I',f,0x38)[0]-struct.unpack_from('<I',f,0x40)[0]
if (np,nr,nb,len(b))!=(3,55,51,28106950):raise ValueError('Original bundle schema changed')
cursor=0;count=0
while count<np:
    kind,n=struct.unpack_from('<HH',b,cursor);cursor+=4;count+=n
if count!=np:raise ValueError('Invalid pool grouping')
pools=[struct.unpack_from('<8I',b,cursor+i*32) for i in range(np)];cursor+=np*32
buffers=[struct.unpack_from('<IIHHIQQ',b,cursor+i*32) for i in range(nb)]
cursor+=nb*32
buffer_sizes=[]
for entry in buffers:
    buffer_sizes.append([struct.unpack_from('<II',b,cursor+i*8) for i in range(entry[3])])
    cursor+=entry[3]*8
roots=[struct.unpack_from('<4I',b,cursor+i*16) for i in range(nr)];cursor+=nr*16
refs={}
while cursor+16<=len(b):
    s,o,t,v=struct.unpack_from('<4I',b,cursor)
    # Empty-vector end pointers may point exactly one past a frozen pool.
    if s>=np or t>=np or o>=pools[s][2] or v>pools[t][2]:break
    if (s,o) in refs:raise ValueError('Duplicate original relocation')
    refs[s,o]=(t,v);cursor+=16
models,exports=struct.unpack_from('<II',b,cursor)
if (models,exports,len(refs))!=(4,4,146) or b[cursor+8:cursor+16]!=bytes.fromhex('ffbf7f3f08040201'):raise ValueError('Original GPU association table changed')
pool_start=cursor+16+models*12+exports*24
if pool_start!=5756:raise ValueError('Original frozen data offset changed')
def at(pointer):
    p,o=pointer
    if p>=np or o<0 or o>=pools[p][2]:raise ValueError('Frozen pointer out of bounds')
    return pool_start+pools[p][3]+o
def plus(pointer,offset):return (pointer[0],pointer[1]+offset)
def cstring(data,offset):
    if not 0<=offset<len(data):raise ValueError('String offset outside original table')
    end=data.find(b'\0',offset,offset+512)
    if end<0:raise ValueError('Unterminated original material property')
    return data[offset:end].decode('ascii')
def name_hash(name):
    h=5381
    for ch in name.lower().encode('ascii'):h=(h*33+ch)&0xffffffff
    return h
names=layout_names=[s.decode() for s in f[144:3076].split(b'\0') if s and all(31<v<127 for v in s)]
names_by_id={name_hash(name):name for name in names}
root_by_id={(uid,kind):(p,o) for uid,kind,p,o in roots}
import_count=struct.unpack_from('<I',f,0x28)[0]
if import_count!=31:raise ValueError('Original external binding table changed')
imports=[struct.unpack_from('<QQI',f,0x990+i*20) for i in range(import_count)]
if any(index>=len(roots) or uid not in names_by_id or offset>=pools[roots[index][2]][2] for uid,index,offset in imports):raise ValueError('Original external binding is invalid')
external={}
for uid,index,offset in imports:
    root=roots[index];external[(root[2],offset)]=dict(id=hex(uid),name=names_by_id[uid],sourceRootIndex=index)
payload=pool_start+sum(p[2] for p in pools);materials=[];textures=[]
buffer_offsets=[[] for _ in buffers]
for column in range(max(map(len,buffer_sizes))):
    for group,parts in enumerate(buffer_sizes):
        if len(parts)<=column:continue
        tag,size=parts[column]
        buffer_offsets[group].append(dict(tag=tag,offset=payload,size=size))
        payload+=size
if payload!=len(b):raise ValueError('Original column-major buffers were not consumed exactly')
for group,entry in enumerate(buffers):
    uid,kind,_,buffer_count,_,metadata_bytes,gpu_bytes=entry
    parts=buffer_offsets[group];data=b[parts[0]['offset']:parts[0]['offset']+parts[0]['size']]
    if kind==193506774:
        pointer=root_by_id[uid,kind];descriptor=refs[plus(pointer,24)];offset=at(descriptor)
        reserved0,reserved1,total_bytes,width,height,depth,array,mips=struct.unpack_from('<8I',b,offset)
        if reserved0 or reserved1 or total_bytes!=parts[1]['size'] or not (1<=mips<=12):raise ValueError('Original texture descriptor changed')
        mip_layout=[dict(zip(('offset','sliceBytes','size','rowPitch','depthPitch'),struct.unpack_from('<5I',b,offset+32+i*20))) for i in range(mips)]
        textures.append(dict(name=names_by_id.get(uid),id=hex(uid),root=pointer,descriptor=descriptor,
            sourceFormat=b[at(pointer)+32],width=width,height=height,depth=depth,array=array,mips=mips,
            mipLayout=mip_layout,buffers=parts,gpuOffset=parts[1]['offset'],gpuBytes=parts[1]['size']));continue
    if kind!=193491583:continue
    pointer=root_by_id[uid,kind];root=at(pointer);texture_count,parameter_count=struct.unpack_from('<QQ',b,root)
    if texture_count>16 or parameter_count>64:raise ValueError('Original material count changed')
    material=dict(name=names_by_id[uid],id=hex(uid),root=pointer,shaderClass=cstring(data,0),stringTableBytes=metadata_bytes,parameters=[],textures=[])
    value_pointer=refs[plus(pointer,40)]
    if parameter_count:
        parameter_pointer=refs[plus(pointer,24)]
        for i in range(parameter_count):
            name_offset,value_type,value_offset,padding=struct.unpack_from('<4I',b,at(plus(parameter_pointer,i*16)))
            sizes={0:4,1:8,2:12,3:16,4:64,5:4,6:4}
            if padding or value_type not in sizes or value_offset>65536:raise ValueError('Unsupported original material value layout')
            raw=b[at(plus(value_pointer,value_offset)):at(plus(value_pointer,value_offset))+sizes[value_type]]
            item=dict(name=cstring(data,name_offset),type=value_type,offset=value_offset,raw=raw.hex())
            if value_type<=4:
                values=struct.unpack('<'+'f'*(len(raw)//4),raw)
                if not all(math.isfinite(v) for v in values):raise ValueError('Non-finite material parameter')
                item['floatValues']=values
            else:item['integerValue']=struct.unpack('<I',raw)[0]
            material['parameters'].append(item)
    if texture_count:
        texture_pointer=refs[plus(pointer,16)]
        for i in range(texture_count):
            name_offset,value_type,slot,reserved=struct.unpack_from('<4I',b,at(plus(texture_pointer,i*24)))
            if value_type not in (7,8):raise ValueError(f'Unsupported original texture binding: {material["name"]} #{i}: {name_offset},{value_type},{slot},{reserved}')
            raw=b[at(plus(texture_pointer,i*24)):at(plus(texture_pointer,i*24))+24]
            # Tag 7 carries inline/default data; retain it without assigning an
            # external-resource meaning to its packed words.
            binding=None
            if value_type==8:
                handles=refs[plus(pointer,32)];binding=external.get(plus(handles,slot*8))
                if binding is None:raise ValueError('Original texture external binding is missing')
            material['textures'].append(dict(name=cstring(data,name_offset),type=value_type,word2=slot,word3=reserved,raw=raw.hex(),binding=binding))
    materials.append(material)
if len(materials)!=26 or len(textures)!=21:raise ValueError('Original payload was not consumed exactly')
result=dict(resource=key,sourceArchiveSha256=layout['archiveSha256'],frozenSha256=hashlib.sha256(b).hexdigest(),
    processAccess=False,gameCalls=0,shaderBytecodeFound=False,poolDataOffset=pool_start,materials=materials,textures=textures)
(folder/'original-materials.json').write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps(dict(materials=len(materials),textures=len(textures),sourceArchiveSha256=result['sourceArchiveSha256'],
    shaderClasses=sorted({m['shaderClass'] for m in materials})),indent=2))