"""Verify only the original Thargoid panel's bounded old/new PE bodies.
Static file evidence; this does not attach, call a client function, request
metadata, install hooks, or alter the shared executable/hash profile.
"""
import hashlib
import importlib.util
import json
import struct
import difflib
from pathlib import Path
spec = importlib.util.spec_from_file_location('audit', Path(__file__).with_name('Audit-GalaxyClientCompatibility.py'))
audit = importlib.util.module_from_spec(spec)
spec.loader.exec_module(audit)
old = audit.Image(Path('artifacts/ship-model-reference/client-copy/EliteDangerous64.exe'))
new = audit.Image(Path('D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/EliteDangerous64.exe'))
functions = []
for rva in (0x3A0E740, 0x3A05660, 0x3A2B090, 0x3AA9F20, 0x3A161C0, 0x3A57E40):
span = old.function(rva)
size = span[1] - rva if span else 128
row = audit.match(old, new, rva, size)
if row['status'] != 'unique':
raise ValueError('Unclosed original Thargoid function: ' + hex(rva) + ': ' + row['status'])
row['oldRva'] = hex(rva)
functions.append(row)
ctor = functions[0]
references = audit.references(old, new, 0x3A0E740, int(ctor['newRva'], 16), ctor['length'])
tables = {}
for before in (0x55276E8, 0x5527718, 0x5527818, 0x5527840, 0x5527868, 0x55278C0):
candidates = {b for a, b, _, _ in references if a == before}
if len(candidates) != 1:
raise ValueError('Unclosed constructor vtable binding')
tables[before] = candidates.pop()
slots = []
for offset, expected in ((0, 0x3A4C0C0), (0x18, 0x3A565B0), (0x20, 0xEC00F0), (0x28, 0x3A57E40)):
before = struct.unpack('<Q', old.data(0x55278C0 + offset, 8))[0] - old.base
after = struct.unpack('<Q', new.data(tables[0x55278C0] + offset, 8))[0] - new.base
if before != expected:
raise ValueError('Original DS interface changed')
# These three getters are leaf routines without pdata entries. Verify all
# their real instructions through RET, including exact member offsets.
length = next(i.address + i.size - old.base - before for i in old.instructions(before, 128) if i.mnemonic == 'ret')
raw, mask, *_ = audit.signature(old, before, length)
actual = new.data(after, length)
if any(a != b for a, b, keep in zip(raw, actual, mask) if keep):
raise ValueError('Thargoid published getter changed')
refs = audit.references(old, new, before, after, length)
if offset == 0 and not any(a == b == 0x5F5428C for a, b, _, _ in refs):
raise ValueError('Dynamic original component ID moved')
slots.append(dict(slot=hex(offset), oldRva=hex(before), newRva=hex(after), bytes=length,
oldSha256=hashlib.sha256(raw).hexdigest(), newSha256=hashlib.sha256(actual).hexdigest(),
normalizedIdentical=True))
cg_slots = []
for offset, expected in ((0, 0x3A4C090), (0x18, 0x11D3BF0)):
before = struct.unpack('<Q', old.data(0x5527840 + offset, 8))[0] - old.base
after = struct.unpack('<Q', new.data(tables[0x5527840] + offset, 8))[0] - new.base
if before != expected:
raise ValueError('Original shared CG interface changed')
length = next(i.address + i.size - old.base - before for i in old.instructions(before, 128) if i.mnemonic == 'ret')
raw, mask, *_ = audit.signature(old, before, length)
actual = new.data(after, length)
refs = audit.references(old, new, before, after, length)
if any(a != b for a, b, keep in zip(raw, actual, mask) if keep) or (
offset == 0 and not any(a == b == 0x5FA10F0 for a, b, _, _ in refs)):
raise ValueError('Shared original CG getter or dynamic ID changed')
cg_slots.append(dict(slot=hex(offset), oldRva=hex(before), newRva=hex(after), bytes=length,
oldSha256=hashlib.sha256(raw).hexdigest(), newSha256=hashlib.sha256(actual).hexdigest(),
normalizedIdentical=True, references=[list(map(hex, r)) for r in refs]))
caller = next(f for f in functions if f['oldFunctionRange'][0] == '0x3a2b090')
writer_candidates = {b for a, b, _, _ in audit.references(old, new, 0x3A2B090, int(caller['newRva'], 16), caller['length']) if a == 0x3A6E3E0}
if len(writer_candidates) != 1:
raise ValueError('Original parser call no longer uniquely bound')
writer = writer_candidates.pop()
writer_spans = [image.function(rva) for image, rva in ((old, 0x3A6E3E0), (new, writer))]
writer_members = []
writer_instructions = []
for image, span in zip((old, new), writer_spans):
instructions = image.instructions(span[0], span[1]-span[0])
writer_instructions.append(instructions)
writer_members.append([dict(mnemonic=i.mnemonic, operands=i.op_str) for i in instructions
if any(op.type == audit.X86_OP_MEM and image.dis.reg_name(op.mem.base) == 'r13'
and 0x318 <= op.mem.disp <= 0x528 for op in i.operands)])
if writer_members[0] != writer_members[1]:
raise ValueError('Published Thargoid member writes changed')
shape_differences = []
for kind, a, b, c, d in difflib.SequenceMatcher(a=[i.mnemonic for i in writer_instructions[0]],
b=[i.mnemonic for i in writer_instructions[1]], autojunk=False).get_opcodes():
if kind != 'equal':
shape_differences.append(dict(kind=kind, old=[dict(rva=hex(i.address-old.base), text=i.mnemonic+' '+i.op_str)
for i in writer_instructions[0][a:b]],
new=[dict(rva=hex(i.address-new.base), text=i.mnemonic+' '+i.op_str)
for i in writer_instructions[1][c:d]]))
output = dict(scope='Offline bounded Thargoid DS compatibility evidence; runtime values and live freshness unverified',
runtimeNeutral=True, canInstallHooks=False,
oldImage=dict(path=str(old.path), sha256=hashlib.sha256(old.raw).hexdigest()),
newImage=dict(path=str(new.path), sha256=hashlib.sha256(new.raw).hexdigest()),
functions=functions, vtables={hex(a): hex(b) for a, b in tables.items()},
publishedInterfaceSlots=slots, componentIdGlobal='0x5f5428c', sharedCommunityGoalInterfaceSlots=cg_slots,
changedParser=dict(oldRva='0x3a6e3e0', newRva=hex(writer), method='unique-call-from-completely-matched-response-handler',
wholeBodyIdentical=False, note='Compiler stack locals moved and a duplicate JSON cleanup call+lea was removed. No whole-body equivalence claim; adapter does not call it.',
oldInstructionCount=len(writer_instructions[0]), newInstructionCount=len(writer_instructions[1]),
instructionShapeDifferences=shape_differences,
publishedMemberInstructionsIdentical=True, publishedMemberInstructions=writer_members[0]))
path = Path('artifacts/galaxy-query-research/thargoid-account-sources/compatibility.json')
path.write_text(json.dumps(output, indent=2), encoding='utf8')
print(json.dumps(dict(report=str(path), functions=len(functions), vtables=output['vtables'], slots=slots), indent=2))
"""Verify only the original Thargoid panel's bounded old/new PE bodies.
Static file evidence; this does not attach, call a client function, request
metadata, install hooks, or alter the shared executable/hash profile.
"""
import hashlib
import importlib.util
import json
import struct
import difflib
from pathlib import Path
spec = importlib.util.spec_from_file_location('audit', Path(__file__).with_name('Audit-GalaxyClientCompatibility.py'))
audit = importlib.util.module_from_spec(spec)
spec.loader.exec_module(audit)
old = audit.Image(Path('artifacts/ship-model-reference/client-copy/EliteDangerous64.exe'))
new = audit.Image(Path('D:/SteamLibrary/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/EliteDangerous64.exe'))
functions = []
for rva in (0x3A0E740, 0x3A05660, 0x3A2B090, 0x3AA9F20, 0x3A161C0, 0x3A57E40):
span = old.function(rva)
size = span[1] - rva if span else 128
row = audit.match(old, new, rva, size)
if row['status'] != 'unique':
raise ValueError('Unclosed original Thargoid function: ' + hex(rva) + ': ' + row['status'])
row['oldRva'] = hex(rva)
functions.append(row)
ctor = functions[0]
references = audit.references(old, new, 0x3A0E740, int(ctor['newRva'], 16), ctor['length'])
tables = {}
for before in (0x55276E8, 0x5527718, 0x5527818, 0x5527840, 0x5527868, 0x55278C0):
candidates = {b for a, b, _, _ in references if a == before}
if len(candidates) != 1:
raise ValueError('Unclosed constructor vtable binding')
tables[before] = candidates.pop()
slots = []
for offset, expected in ((0, 0x3A4C0C0), (0x18, 0x3A565B0), (0x20, 0xEC00F0), (0x28, 0x3A57E40)):
before = struct.unpack('<Q', old.data(0x55278C0 + offset, 8))[0] - old.base
after = struct.unpack('<Q', new.data(tables[0x55278C0] + offset, 8))[0] - new.base
if before != expected:
raise ValueError('Original DS interface changed')
# These three getters are leaf routines without pdata entries. Verify all
# their real instructions through RET, including exact member offsets.
length = next(i.address + i.size - old.base - before for i in old.instructions(before, 128) if i.mnemonic == 'ret')
raw, mask, *_ = audit.signature(old, before, length)
actual = new.data(after, length)
if any(a != b for a, b, keep in zip(raw, actual, mask) if keep):
raise ValueError('Thargoid published getter changed')
refs = audit.references(old, new, before, after, length)
if offset == 0 and not any(a == b == 0x5F5428C for a, b, _, _ in refs):
raise ValueError('Dynamic original component ID moved')
slots.append(dict(slot=hex(offset), oldRva=hex(before), newRva=hex(after), bytes=length,
oldSha256=hashlib.sha256(raw).hexdigest(), newSha256=hashlib.sha256(actual).hexdigest(),
normalizedIdentical=True))
cg_slots = []
for offset, expected in ((0, 0x3A4C090), (0x18, 0x11D3BF0)):
before = struct.unpack('<Q', old.data(0x5527840 + offset, 8))[0] - old.base
after = struct.unpack('<Q', new.data(tables[0x5527840] + offset, 8))[0] - new.base
if before != expected:
raise ValueError('Original shared CG interface changed')
length = next(i.address + i.size - old.base - before for i in old.instructions(before, 128) if i.mnemonic == 'ret')
raw, mask, *_ = audit.signature(old, before, length)
actual = new.data(after, length)
refs = audit.references(old, new, before, after, length)
if any(a != b for a, b, keep in zip(raw, actual, mask) if keep) or (
offset == 0 and not any(a == b == 0x5FA10F0 for a, b, _, _ in refs)):
raise ValueError('Shared original CG getter or dynamic ID changed')
cg_slots.append(dict(slot=hex(offset), oldRva=hex(before), newRva=hex(after), bytes=length,
oldSha256=hashlib.sha256(raw).hexdigest(), newSha256=hashlib.sha256(actual).hexdigest(),
normalizedIdentical=True, references=[list(map(hex, r)) for r in refs]))
caller = next(f for f in functions if f['oldFunctionRange'][0] == '0x3a2b090')
writer_candidates = {b for a, b, _, _ in audit.references(old, new, 0x3A2B090, int(caller['newRva'], 16), caller['length']) if a == 0x3A6E3E0}
if len(writer_candidates) != 1:
raise ValueError('Original parser call no longer uniquely bound')
writer = writer_candidates.pop()
writer_spans = [image.function(rva) for image, rva in ((old, 0x3A6E3E0), (new, writer))]
writer_members = []
writer_instructions = []
for image, span in zip((old, new), writer_spans):
instructions = image.instructions(span[0], span[1]-span[0])
writer_instructions.append(instructions)
writer_members.append([dict(mnemonic=i.mnemonic, operands=i.op_str) for i in instructions
if any(op.type == audit.X86_OP_MEM and image.dis.reg_name(op.mem.base) == 'r13'
and 0x318 <= op.mem.disp <= 0x528 for op in i.operands)])
if writer_members[0] != writer_members[1]:
raise ValueError('Published Thargoid member writes changed')
shape_differences = []
for kind, a, b, c, d in difflib.SequenceMatcher(a=[i.mnemonic for i in writer_instructions[0]],
b=[i.mnemonic for i in writer_instructions[1]], autojunk=False).get_opcodes():
if kind != 'equal':
shape_differences.append(dict(kind=kind, old=[dict(rva=hex(i.address-old.base), text=i.mnemonic+' '+i.op_str)
for i in writer_instructions[0][a:b]],
new=[dict(rva=hex(i.address-new.base), text=i.mnemonic+' '+i.op_str)
for i in writer_instructions[1][c:d]]))
output = dict(scope='Offline bounded Thargoid DS compatibility evidence; runtime values and live freshness unverified',
runtimeNeutral=True, canInstallHooks=False,
oldImage=dict(path=str(old.path), sha256=hashlib.sha256(old.raw).hexdigest()),
newImage=dict(path=str(new.path), sha256=hashlib.sha256(new.raw).hexdigest()),
functions=functions, vtables={hex(a): hex(b) for a, b in tables.items()},
publishedInterfaceSlots=slots, componentIdGlobal='0x5f5428c', sharedCommunityGoalInterfaceSlots=cg_slots,
changedParser=dict(oldRva='0x3a6e3e0', newRva=hex(writer), method='unique-call-from-completely-matched-response-handler',
wholeBodyIdentical=False, note='Compiler stack locals moved and a duplicate JSON cleanup call+lea was removed. No whole-body equivalence claim; adapter does not call it.',
oldInstructionCount=len(writer_instructions[0]), newInstructionCount=len(writer_instructions[1]),
instructionShapeDifferences=shape_differences,
publishedMemberInstructionsIdentical=True, publishedMemberInstructions=writer_members[0]))
path = Path('artifacts/galaxy-query-research/thargoid-account-sources/compatibility.json')
path.write_text(json.dumps(output, indent=2), encoding='utf8')
print(json.dumps(dict(report=str(path), functions=len(functions), vtables=output['vtables'], slots=slots), indent=2))