"""Pin original GDM/provider/job/DXBC source boundaries; read-only PE analysis."""
from pathlib import Path
import hashlib,json,struct
import pefile

image=Path('artifacts/ship-model-reference/client-copy/EliteDangerous64.exe')
data=image.read_bytes();sha=hashlib.sha256(data).hexdigest()
if sha!='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93':raise ValueError('Pinned game image changed')
pe=pefile.PE(data=data,fast_load=True);base=pe.OPTIONAL_HEADER.ImageBase
slots={0x68:0x3C41ED0,0x70:0x3C427C0,0x78:0x3C41500,0x88:0x3C411C0,0x90:0x3C411F0}
for offset,target in slots.items():
    if struct.unpack('<Q',pe.get_data(0x5539D08+offset,8))[0]!=base+target:raise ValueError('Original sampler interface changed')
provider={0x18:0x3A31D50,0x20:0x3A31B20,0x30:0x3A4E8E0,0x38:0x3A4E8C0,0x40:0x58DF30,0x48:0x3D4C90}
for offset,target in provider.items():
    if struct.unpack('<Q',pe.get_data(0x55272B0+offset,8))[0]!=base+target:raise ValueError('Original cloud provider interface changed')
if pe.get_data(0x3C406B0,6)!=bytes.fromhex('8b0562552c02'):raise ValueError('Original source type getter changed')
if pe.get_data(0x39FB050,5)!=bytes.fromhex('e81b642000'):raise ValueError('Original MW quad constructor call changed')
quad=[]
for rva in (0x54C7D80,0x54C7D70,0x54C7D60,0x54C7D60,0x50BD330,0x54C7D80):
    raw=pe.get_data(rva,16);quad.append(dict(rva=hex(rva),raw=raw.hex(),uvCorner=list(struct.unpack('<4f',raw))))
constants=[]
for address,kind in [(0x4DEAA50,'f'),(0x4DE87DC,'f'),(0x4DEAA54,'f'),(0x50CB494,'f'),(0x5539424,'I'),
                     (0x5192F68,'f'),(0x4E29758,'f'),(0x4E32690,'f'),(0x53875E4,'f'),(0x5A73FB8,'d')]:
    raw=pe.get_data(address,8 if kind=='d' else 4);constants.append(dict(rva=hex(address),raw=raw.hex(),value=struct.unpack('<'+kind,raw)[0]))
sources=[]
for name in ['3a08f00','3a31d50','3a31b20','3a64c60','3a969e0','3a469d0','39f25f0','3c41ed0','3c427c0','3c41500','3c411c0','3c411f0',
             '4ca5240','4ca5370','4ca5940','4ca5a20','4ca5f30','4ca6110','3a22bc0','3a21bc0','3a06740','3ab44e0']:
    candidates=list(Path('artifacts/galaxy-query-research').rglob('solo-'+name+'.c'))
    if len(candidates)!=1:raise ValueError('Missing/ambiguous original source '+name)
    path=candidates[0];sources.append(dict(rva='0x'+name,path=str(path),sha256=hashlib.sha256(path.read_bytes()).hexdigest()))
root=Path('artifacts/galaxy-query-research/power-outer-sources')
gdm=json.loads(Path('XFEEDToolBox.Core/Rendering/original-galaxy-density.json').read_text())
material=json.loads(Path('XFEEDToolBox.Core/Rendering/original-milky-way.json').read_text())
result=dict(gameImageSha256=sha,gameProcessAccess=False,gameCalls=0,networkRequests=0,
    sourceInterface=dict(typeGlobal='0x5F05C18',vtable='0x5539D08',componentOffset='0x60',resourceField='0xF8',
        slots={hex(k):hex(v) for k,v in slots.items()},sameResourceAs='5F05C1C interface+100; both resolve component+158'),
    provider=dict(typeGlobal='0x5FA0A20',vtable='0x55272B0',componentOffset='0x58',constructor='0x3A08F00',
        slots={hex(k):hex(v) for k,v in provider.items()},defaultMilkyWayLayers=1,defaultDustLayers=3),
    generator=dict(job='0x3A469D0',taskDescriptor='0x5FD85C8',taskVtable='0x551E640',executeThunk='0x39EFBA0',
        recordBytes=28,fields=['XYZ float3','raw alpha float','raw size float','rotation float','atlas index uint'],
        step=1066,milkyWayHeight=16000,dustHeight=1280,midplaneY=40955,gaussian='0x39F25F0',
        random='Park-Miller48271/44488/3399; original unsigned cell hash'),
    quad=dict(chain=['3A643D0→39FAEE0','39FAEE0 at39FB050→3C01470(object+88,vertexCount6)','3C01470→3C10980'],
        constructorBytes=pe.get_data(0x39FB013,0x42).hex(),vertices=quad),
    densitySource=gdm,materialSource=material,constants=constants,sourceFunctions=sources,
    implemented=['exact source GDM/RLE expansion','original mass-zero integer/fixed-point sampling','original constructor-default cloud job',
        'actual original atlas/RGB/Y BC1 mip payloads','original DistantMilkyWay VS/PS arithmetic and world quads'],
    unclosed=['original runtime scalar-property slots to material/CB2 binding','shared RGB multiplier and final HDR output',
        'Dust half.x input interpretation; not silently converted from radians to sine','local dust, original MilkyWayPlane/Core geometry',
        'runtime debug/quality overrides of constructor defaults','independent original frame projection'],
    fullOriginalBackgroundRestored=False)
out=root/'milkyway-source-evidence.json';out.write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
print(json.dumps(dict(path=str(out),functions=len(sources),samplerSlots=len(slots),providerSlots=len(provider),gameImageSha256=sha)))
