"""Pin original D3D11 blend conversion and Galaxy integer publishers, offline."""
from pathlib import Path
import hashlib,json,struct
import pefile
from capstone import Cs,CS_ARCH_X86,CS_MODE_64
from capstone.x86_const import X86_OP_IMM,X86_OP_MEM,X86_OP_REG

image=Path('artifacts/ship-model-reference/client-copy/EliteDangerous64.exe').read_bytes()
sha=hashlib.sha256(image).hexdigest()
if sha!='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93':raise ValueError('Pinned image changed')
pe=pefile.PE(data=image,fast_load=True);pe.parse_data_directories(directories=[3])
decoder=Cs(CS_ARCH_X86,CS_MODE_64);decoder.detail=True
def function(rva):
    entry=next(e.struct for e in pe.DIRECTORY_ENTRY_EXCEPTION if e.struct.BeginAddress==rva)
    data=pe.get_data(rva,entry.EndAddress-rva)
    return data,list(decoder.disasm(data,rva))
body,instructions=function(0x513D70)
create=[i for i in instructions if i.mnemonic=='call' and i.operands[0].type==X86_OP_MEM and i.operands[0].mem.disp==0xA0]
clear=[i for i in instructions if i.mnemonic=='mov' and any(o.type==X86_OP_IMM and o.imm==0x108 for o in i.operands)]
if len(create)!=1 or len(clear)!=1:raise ValueError('Actual D3D11 blend descriptor creator changed')
blend=pe.get_data(0x4E2E460,16*8);ops=pe.get_data(0x4E2DAE8,5*4)
expected=[(1,1),(2,2),(3,5),(4,6),(5,5),(6,6),(7,7),(8,8),(9,7),(10,8),(14,14),(15,15),(16,18),(17,19),(18,18),(19,19)]
actual=[struct.unpack_from('<2I',blend,i*8) for i in range(16)]
if actual!=expected or struct.unpack('<5I',ops)!=(1,2,3,4,5):raise ValueError('Original blend conversion changed')
names=['Zero','One','SourceColor','InverseSourceColor','SourceAlpha','InverseSourceAlpha','DestinationAlpha','InverseDestinationAlpha',
    'DestinationColor','InverseDestinationColor','BlendFactor','InverseBlendFactor','Source1Color','InverseSource1Color','Source1Alpha','InverseSource1Alpha']
publishers=[]
for rva in (0x3A09210,0x3A09F20):
    code,decoded=function(rva);properties=[]
    for name,name_rva,wrapper,value,default in [('pBlendOp',0x551F140,0x1060,0x10E8,0),
            ('pSrcBlend',0x551F150,0x10F0,0x1178,4),('pDestBlend',0x551F160,0x1180,0x1208,1)]:
        if pe.get_data(name_rva,len(name)+1)!=name.encode()+b'\0':raise ValueError('Original material property name changed')
        stores=[i for i in decoded if i.mnemonic=='mov' and len(i.operands)==2 and i.operands[0].type==X86_OP_MEM
            and i.operands[0].mem.disp==value and i.operands[0].size==4]
        if len(stores)!=1:raise ValueError('Original integer publisher default changed')
        store=stores[0];operand=store.operands[1];zero_proof=None
        if operand.type==X86_OP_IMM:
            if operand.imm!=default:raise ValueError('Original integer default immediate changed')
        elif default==0 and operand.type==X86_OP_REG and store.reg_name(operand.reg)=='r12d':
            definitions=[i for i in decoded if i.address<store.address and any(i.reg_name(r) in {'r12','r12d','r12w','r12b'} for r in i.regs_access()[1])]
            last=definitions[-1]
            if last.mnemonic!='xor' or last.op_str!='r12d, r12d':raise ValueError('Original nonvolatile zero definition changed')
            zero_proof=dict(definitionRva=hex(last.address),definitionBytes=last.bytes.hex(),register='r12d',noInterveningWrites=True)
        else:raise ValueError('Original default register requires an independently verified definition')
        properties.append(dict(name=name,nameRva=hex(name_rva),wrapperOffset=hex(wrapper),valueOffset=hex(value),default=default,
            storeRva=hex(store.address),storeBytes=store.bytes.hex(),zeroRegisterProof=zero_proof))
    publishers.append(dict(constructorRva=hex(rva),codeSha256=hashlib.sha256(code).hexdigest(),properties=properties))
root=Path('artifacts/galaxy-query-research/power-outer-sources')
sources=[root/'material-slots/solo-513d70.c',root/'material-slots/solo-4342040.c',
    root/'grid/solo-3a09210.c',root.parent/'constellations/decompiled/solo-3a09f20.c',root.parent/'constellations/decompiled/solo-3aac040.c']
result=dict(imageSha256=sha,gameProcessAccess=False,gameCalls=0,creatorRva='0x513D70',creatorCodeSha256=hashlib.sha256(body).hexdigest(),
    descriptorBytes=264,deviceVirtualSlot='0xA0',createCallRva=hex(create[0].address),createCallBytes=create[0].bytes.hex(),
    flags=dict(sourceField='descriptor+0x20',rgbSourceShift=12,rgbDestinationShift=16,rgbOperationShift=9,
        alphaSourceShift=23,alphaDestinationShift=27,alphaOperationShift=20),
    blendTable=dict(rva='0x4E2E460',stride=8,raw=blend.hex(),sha256=hashlib.sha256(blend).hexdigest(),
        entries=[dict(sourceEnum=i,name=names[i],rgbD3D11=value[0],alphaD3D11=value[1]) for i,value in enumerate(actual)]),
    operationTable=dict(rva='0x4E2DAE8',values=list(struct.unpack('<5I',ops))),publishers=publishers,
    integerPublisher=dict(function='0x3AAC040',registration='0x4342040',registeredType=6,wrapperValueOffset='0x88',unconditional=True),
    sources=[dict(path=str(p).replace('\\','/'),sha256=hashlib.sha256(p.read_bytes()).hexdigest()) for p in sources],
    remainingLimits=['Actual FGM/named-property application to packed state flags','Complete layer states and final HDR output'],
    fullBlendPipelineRestored=False)
target=root/'material-slots/original-blend-state.json';target.write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps(dict(path=str(target),rgbMappings=[r['rgbD3D11'] for r in result['blendTable']['entries']],publishers=publishers),indent=2))
