"""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))
"""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))