"""Read original local DXBC; D3DDisassemble does not execute shader/game code."""
import ctypes,hashlib,json,re,struct,zlib
from pathlib import Path

source=Path('D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/Win64/EffectsBinary')
out=Path('artifacts/galaxy-query-research/power-outer-sources/effects');out.mkdir(exist_ok=True)
known={'Effects_Win64_SM50.arc':'e4608c25412842938b5022668ccf37493cfd4cb6ddcaa02d28016637b5910789',
       'Effects2_Win64_SM50.arc':'f205ce866b084f793554b867925fc53a4c8af2908abf2ed07389b1f437f365dc'}
targets={'BackgroundStars','BackgroundBlackHole','DistantMilkyWay','DistantMilkyWayDust','GalaxyMapFocusRing',
    'GalaxyMapSimpleBillboard','GalaxyMapFocusBillboard','StarMapDensityMap','MassiveStars','StarMapLine'}
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
rows=[]
for name,expected_hash in known.items():
    packed=(source/name).read_bytes()
    if hashlib.sha256(packed).hexdigest()!=expected_hash:raise ValueError('Original effects archive changed')
    compressed,inflated=struct.unpack_from('<II',packed,8)
    if compressed!=len(packed)-16 or not 0<inflated<100000000:raise ValueError('Original effects header changed')
    data=zlib.decompress(packed[16:],-15)
    if len(data)!=inflated:raise ValueError('Original effects length changed')
    (out/(name+'.raw')).write_bytes(data)
    row=dict(name=name,sourceSha256=expected_hash,inflatedBytes=len(data),inflatedSha256=hashlib.sha256(data).hexdigest(),selected=[])
    for match in re.finditer(b'DXBC',data):
        offset=match.start();size,chunks=struct.unpack_from('<II',data,offset+24)
        # Archive metadata also contains the literal string DXBC. Only actual
        # bounded containers can have a shader-name association below.
        if not 32<=size<=1024*1024 or not 1<=chunks<=16 or offset+size>len(data):continue
        suffix=data[offset+size:offset+size+256]
        label=re.search(rb'(vs|ps|gs|cs)_[A-Za-z0-9_]+_Win64_SM50\0',suffix)
        if label is None:continue
        label=label.group()[:-1].decode('ascii')
        if not any(label.startswith(stage+'_'+target+'_') for stage in ('vs','ps','gs','cs') for target in targets):continue
        blob=data[offset:offset+size];(out/(label+'.dxbc')).write_bytes(blob)
        buffer=ctypes.create_string_buffer(blob);result=ctypes.c_void_p()
        code=disassemble(buffer,len(blob),0,None,ctypes.byref(result))
        if code<0:raise ValueError(f'D3DDisassemble failed:{code}')
        table=ctypes.cast(result,ctypes.POINTER(ctypes.POINTER(ctypes.c_void_p)))[0]
        try:
            pointer=ctypes.WINFUNCTYPE(ctypes.c_void_p,ctypes.c_void_p)(table[3])(result)
            length=ctypes.WINFUNCTYPE(ctypes.c_size_t,ctypes.c_void_p)(table[4])(result)
            text=ctypes.string_at(pointer,length).decode().rstrip('\0')
        finally:ctypes.WINFUNCTYPE(ctypes.c_uint,ctypes.c_void_p)(table[2])(result)
        (out/(label+'.txt')).write_text(text)
        row['selected'].append(dict(name=label,offset=offset,bytes=size,sha256=hashlib.sha256(blob).hexdigest()))
    rows.append(row)
result=dict(processAccess=False,gameCalls=0,shaderExecution=False,disassembler='Windows d3dcompiler_47!D3DDisassemble',archives=rows)
(out/'original-effects.json').write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps({r['name']:[s['name'] for s in r['selected']] for r in rows},indent=2))
