"""Resolve actual original effect shader aliases by the native loader's keys.
4FA720 indexes ahsd3f headers by five uints at+10, then resolves the second
frozen effect object's20-byte key table(+58/+60) into the first object's shader
slots. Similar names, nearby DXBC and matching signatures are not substitutes.
"""
from pathlib import Path
import ctypes,hashlib,json,re,struct
folder=Path('artifacts/galaxy-query-research/power-outer-sources')
source=folder/'effects/Effects_Win64_SM50.arc.raw'
data=source.read_bytes()
if hashlib.sha256(data).hexdigest()!='649e3107c868fe31ba23c9e1d19be2b99777b908431987f04f08cd39f0f798ca':
raise ValueError('Pinned shader archive changed')
out=folder/'authored-models/effects';out.mkdir(exist_ok=True)
shader_map={}
for match in re.finditer(re.escape(b'\x05\x02ahsd3f'),data):
offset=match.start();size=struct.unpack_from('<I',data,offset+8)[0]
if not 48<=size<=1024*1024 or offset+size>len(data):raise ValueError('Original shader record exceeds bounds')
record=data[offset:offset+size]
key=struct.unpack_from('<5I',record,16)
names=list(dict.fromkeys(x[:-1].decode('ascii') for x in re.findall(rb'(?:vs|ps|gs|cs)_[A-Za-z0-9_]+_Win64_SM50\0',record)))
if len(names)!=1 or key in shader_map:raise ValueError('Original shader key/name is ambiguous')
positions=[]
for item in re.finditer(b'DXBC',record):
p=item.start()
if p+32>len(record):continue
n,chunks=struct.unpack_from('<2I',record,p+24)
if 32<=n<=len(record)-p and 1<=chunks<=16:positions.append((p,n))
if len(positions)!=1:raise ValueError('Original shader record container is ambiguous')
p,n=positions[0];blob=record[p:p+n]
shader_map[key]=dict(name=names[0],archiveOffset=offset,archiveBytes=size,dxbcOffset=offset+p,
dxbcBytes=n,stage=struct.unpack_from('<H',record,0x26)[0],sha256=hashlib.sha256(blob).hexdigest(),blob=blob)
targets={'Alpha_UnlitNoScale_StarMapFacingAlpha_NearFade_Forward','DistantMilkyWayDust_Forward',
'DistantMilkyWay_Forward','GalaxyGlow_Forward'}
rows=[]
dll=ctypes.WinDLL('d3dcompiler_47.dll');disassemble=dll.D3DDisassemble
disassemble.argtypes=[ctypes.c_void_p,ctypes.c_size_t,ctypes.c_uint,ctypes.c_char_p,ctypes.POINTER(ctypes.c_void_p)]
disassemble.restype=ctypes.c_int32
for match in re.finditer(re.escape(b'\x08\x02xfxd3f'),data):
effect=match.start();cursor=effect+8;objects=[]
for _ in range(2):
marker=data.index(b'\0\1voMrtP',cursor)
size,relocations=struct.unpack_from('<2I',data,marker+8)
objects.append((marker+16,data[marker+16:marker+16+size]))
cursor=marker+16+size+relocations*4
first=objects[0][1];second=objects[1][1]
pointer=struct.unpack_from('<Q',first)[0]
name=first[pointer:first.index(b'\0',pointer)].decode('ascii')
if name not in targets:continue
pointer,count=struct.unpack_from('<2Q',second,0x58)
first_count=struct.unpack_from('<Q',first,0x50)[0]
if count!=first_count or count!=2:raise ValueError('Original model shader slot count changed')
shaders=[]
for index in range(count):
key_offset=pointer+index*20;key=struct.unpack_from('<5I',second,key_offset)
if key not in shader_map:raise ValueError('Original model shader key not found')
original=shader_map[key];shader={k:v for k,v in original.items() if k!='blob'}
shader.update(slot=index,key=[hex(x) for x in key],keyArchiveOffset=objects[1][0]+key_offset)
shaders.append(shader);blob=original['blob']
(out/(shader['name']+'.dxbc')).write_bytes(blob)
result=ctypes.c_void_p();buffer=ctypes.create_string_buffer(blob)
if disassemble(buffer,len(blob),0,None,ctypes.byref(result))<0:raise ValueError('Original linked shader disassembly failed')
table=ctypes.cast(result,ctypes.POINTER(ctypes.POINTER(ctypes.c_void_p)))[0]
try:
p=ctypes.WINFUNCTYPE(ctypes.c_void_p,ctypes.c_void_p)(table[3])(result)
n=ctypes.WINFUNCTYPE(ctypes.c_size_t,ctypes.c_void_p)(table[4])(result)
text=ctypes.string_at(p,n).decode().rstrip('\0')
finally:ctypes.WINFUNCTYPE(ctypes.c_uint,ctypes.c_void_p)(table[2])(result)
(out/(shader['name']+'.txt')).write_text(text,encoding='utf-8')
rows.append(dict(effect=name,effectOffset=effect,firstObjectOffset=objects[0][0],secondObjectOffset=objects[1][0],shaders=shaders))
if {row['effect'] for row in rows}!=targets:raise ValueError('Original effect link selection incomplete')
native=folder/'background/effect-loader/solo-4fa720.c'
result=dict(source=str(source),sourceSha256=hashlib.sha256(data).hexdigest(),shaderRecordCount=len(shader_map),effects=rows,
nativeLoader=dict(imageSha256='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93',
rva='0x4FA720',source=str(native),sha256=hashlib.sha256(native.read_bytes()).hexdigest(),
shaderKeyFields='+10,+14,+18,+1C,+20',effectKeyTable='secondFrozen+58/+60,20-byte stride',
destinationSlots='firstFrozen+48,8-byte stride'),gameProcessAccess=False,gameCalls=0)
(out/'original-effect-shader-links.json').write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
print(json.dumps({row['effect']:[s['name'] for s in row['shaders']] for row in rows}))
"""Resolve actual original effect shader aliases by the native loader's keys.
4FA720 indexes ahsd3f headers by five uints at+10, then resolves the second
frozen effect object's20-byte key table(+58/+60) into the first object's shader
slots. Similar names, nearby DXBC and matching signatures are not substitutes.
"""
from pathlib import Path
import ctypes,hashlib,json,re,struct
folder=Path('artifacts/galaxy-query-research/power-outer-sources')
source=folder/'effects/Effects_Win64_SM50.arc.raw'
data=source.read_bytes()
if hashlib.sha256(data).hexdigest()!='649e3107c868fe31ba23c9e1d19be2b99777b908431987f04f08cd39f0f798ca':
raise ValueError('Pinned shader archive changed')
out=folder/'authored-models/effects';out.mkdir(exist_ok=True)
shader_map={}
for match in re.finditer(re.escape(b'\x05\x02ahsd3f'),data):
offset=match.start();size=struct.unpack_from('<I',data,offset+8)[0]
if not 48<=size<=1024*1024 or offset+size>len(data):raise ValueError('Original shader record exceeds bounds')
record=data[offset:offset+size]
key=struct.unpack_from('<5I',record,16)
names=list(dict.fromkeys(x[:-1].decode('ascii') for x in re.findall(rb'(?:vs|ps|gs|cs)_[A-Za-z0-9_]+_Win64_SM50\0',record)))
if len(names)!=1 or key in shader_map:raise ValueError('Original shader key/name is ambiguous')
positions=[]
for item in re.finditer(b'DXBC',record):
p=item.start()
if p+32>len(record):continue
n,chunks=struct.unpack_from('<2I',record,p+24)
if 32<=n<=len(record)-p and 1<=chunks<=16:positions.append((p,n))
if len(positions)!=1:raise ValueError('Original shader record container is ambiguous')
p,n=positions[0];blob=record[p:p+n]
shader_map[key]=dict(name=names[0],archiveOffset=offset,archiveBytes=size,dxbcOffset=offset+p,
dxbcBytes=n,stage=struct.unpack_from('<H',record,0x26)[0],sha256=hashlib.sha256(blob).hexdigest(),blob=blob)
targets={'Alpha_UnlitNoScale_StarMapFacingAlpha_NearFade_Forward','DistantMilkyWayDust_Forward',
'DistantMilkyWay_Forward','GalaxyGlow_Forward'}
rows=[]
dll=ctypes.WinDLL('d3dcompiler_47.dll');disassemble=dll.D3DDisassemble
disassemble.argtypes=[ctypes.c_void_p,ctypes.c_size_t,ctypes.c_uint,ctypes.c_char_p,ctypes.POINTER(ctypes.c_void_p)]
disassemble.restype=ctypes.c_int32
for match in re.finditer(re.escape(b'\x08\x02xfxd3f'),data):
effect=match.start();cursor=effect+8;objects=[]
for _ in range(2):
marker=data.index(b'\0\1voMrtP',cursor)
size,relocations=struct.unpack_from('<2I',data,marker+8)
objects.append((marker+16,data[marker+16:marker+16+size]))
cursor=marker+16+size+relocations*4
first=objects[0][1];second=objects[1][1]
pointer=struct.unpack_from('<Q',first)[0]
name=first[pointer:first.index(b'\0',pointer)].decode('ascii')
if name not in targets:continue
pointer,count=struct.unpack_from('<2Q',second,0x58)
first_count=struct.unpack_from('<Q',first,0x50)[0]
if count!=first_count or count!=2:raise ValueError('Original model shader slot count changed')
shaders=[]
for index in range(count):
key_offset=pointer+index*20;key=struct.unpack_from('<5I',second,key_offset)
if key not in shader_map:raise ValueError('Original model shader key not found')
original=shader_map[key];shader={k:v for k,v in original.items() if k!='blob'}
shader.update(slot=index,key=[hex(x) for x in key],keyArchiveOffset=objects[1][0]+key_offset)
shaders.append(shader);blob=original['blob']
(out/(shader['name']+'.dxbc')).write_bytes(blob)
result=ctypes.c_void_p();buffer=ctypes.create_string_buffer(blob)
if disassemble(buffer,len(blob),0,None,ctypes.byref(result))<0:raise ValueError('Original linked shader disassembly failed')
table=ctypes.cast(result,ctypes.POINTER(ctypes.POINTER(ctypes.c_void_p)))[0]
try:
p=ctypes.WINFUNCTYPE(ctypes.c_void_p,ctypes.c_void_p)(table[3])(result)
n=ctypes.WINFUNCTYPE(ctypes.c_size_t,ctypes.c_void_p)(table[4])(result)
text=ctypes.string_at(p,n).decode().rstrip('\0')
finally:ctypes.WINFUNCTYPE(ctypes.c_uint,ctypes.c_void_p)(table[2])(result)
(out/(shader['name']+'.txt')).write_text(text,encoding='utf-8')
rows.append(dict(effect=name,effectOffset=effect,firstObjectOffset=objects[0][0],secondObjectOffset=objects[1][0],shaders=shaders))
if {row['effect'] for row in rows}!=targets:raise ValueError('Original effect link selection incomplete')
native=folder/'background/effect-loader/solo-4fa720.c'
result=dict(source=str(source),sourceSha256=hashlib.sha256(data).hexdigest(),shaderRecordCount=len(shader_map),effects=rows,
nativeLoader=dict(imageSha256='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93',
rva='0x4FA720',source=str(native),sha256=hashlib.sha256(native.read_bytes()).hexdigest(),
shaderKeyFields='+10,+14,+18,+1C,+20',effectKeyTable='secondFrozen+58/+60,20-byte stride',
destinationSlots='firstFrozen+48,8-byte stride'),gameProcessAccess=False,gameCalls=0)
(out/'original-effect-shader-links.json').write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
print(json.dumps({row['effect']:[s['name'] for s in row['shaders']] for row in rows}))